@@ -33,9 +33,39 @@ jest.mock('viem', () => {
3333 ...actual ,
3434 isAddress : jest . fn ( ) ,
3535 parseEther : jest . fn ( ) ,
36+ toHex : jest . fn ( ( val ) => {
37+ if ( val === undefined || val === null ) return '0x0' ;
38+ if ( typeof val === 'bigint' ) return `0x${ val . toString ( 16 ) } ` ;
39+ if ( typeof val === 'number' ) return `0x${ val . toString ( 16 ) } ` ;
40+ return `0x${ val . toString ( 16 ) } ` ;
41+ } ) ,
42+ toRlp : jest . fn (
43+ ( val ) => `0x${ Buffer . from ( JSON . stringify ( val ) ) . toString ( 'hex' ) } `
44+ ) ,
45+ encodeAbiParameters : jest . fn ( ( params , values ) => {
46+ // Mock ABI encoding: simple concatenation for testing
47+ // In reality, this would properly ABI encode the tuple
48+ const factoryAddress = values [ 0 ] ;
49+ const factoryCalldata = values [ 1 ] ;
50+ const originalSignature = values [ 2 ] ;
51+ // Simulate ABI encoding by concatenating (this is simplified for tests)
52+ return `0x${ factoryAddress . slice ( 2 ) } ${ factoryCalldata . slice ( 2 ) } ${ originalSignature . slice ( 2 ) } ` as `0x${string } `;
53+ } ) ,
54+ zeroAddress : '0x0000000000000000000000000000000000000000' ,
3655 } ;
3756} ) ;
3857
58+ jest . mock ( 'viem/accounts' , ( ) => {
59+ const actual = jest . requireActual ( 'viem/accounts' ) ;
60+ const mockSignMessage = jest . fn ( ) . mockResolvedValue ( '0x' + '1' . repeat ( 130 ) ) ;
61+ return {
62+ ...actual ,
63+ signMessage : mockSignMessage ,
64+ } ;
65+ } ) ;
66+
67+ const { signMessage : viemSignMessage } = require ( 'viem/accounts' ) ;
68+
3969// Move mockConfig and mockSdk to a higher scope for batch tests
4070let mockConfig : any ;
4171let mockSdk : any ;
@@ -2335,6 +2365,222 @@ describe('DelegatedEoa Mode Integration', () => {
23352365 } ) ;
23362366 } ) ;
23372367
2368+ describe ( 'signMessage' , ( ) => {
2369+ const { toRlp } = require ( 'viem' ) ;
2370+
2371+ beforeEach ( ( ) => {
2372+ jest . clearAllMocks ( ) ;
2373+ ( toRlp as jest . Mock ) . mockClear ( ) ;
2374+ } ) ;
2375+
2376+ it ( 'should create EIP-6492 signature when EOA is not yet installed' , async ( ) => {
2377+ const mockOwner = {
2378+ address : '0xowner123456789012345678901234567890' ,
2379+ } as any ;
2380+ const mockBundlerClient = {
2381+ signAuthorization : jest . fn ( ) . mockResolvedValue ( {
2382+ address : '0xdelegate123456789012345678901234567890' ,
2383+ data : '0xabcdef1234567890abcdef1234567890abcdef12' ,
2384+ } ) ,
2385+ } as any ;
2386+ const mockPublicClient = {
2387+ getCode : jest
2388+ . fn ( )
2389+ . mockResolvedValueOnce ( '0x' ) // For isDelegateSmartAccountToEoa check
2390+ . mockResolvedValue ( '0x' ) , // For other calls
2391+ getTransactionCount : jest . fn ( ) . mockResolvedValue ( 5 ) ,
2392+ } as any ;
2393+ const mockWalletClient = {
2394+ signMessage : jest . fn ( ) . mockResolvedValue ( '0x' + '1' . repeat ( 130 ) ) , // Standard signature
2395+ } as any ;
2396+
2397+ mockProvider . getOwnerAccount . mockResolvedValue ( mockOwner ) ;
2398+ mockProvider . getBundlerClient . mockResolvedValue ( mockBundlerClient ) ;
2399+ mockProvider . getPublicClient . mockResolvedValue ( mockPublicClient ) ;
2400+ mockProvider . getWalletClient . mockResolvedValue ( mockWalletClient ) ;
2401+ ( toRlp as jest . Mock ) . mockReturnValue ( '0xdeadbeef1234567890abcdef' ) ;
2402+
2403+ const result = await transactionKit . signMessage ( 'Hello, World!' , 1 ) ;
2404+
2405+ // EIP-6492 format: encodedWrapper || magicBytes (32-byte suffix)
2406+ // Magic bytes should be at the end: 0x6492649264926492649264926492649264926492649264926492649264926492
2407+ expect ( result ) . toMatch (
2408+ / 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 $ /
2409+ ) ;
2410+ expect ( result . length ) . toBeGreaterThan ( 200 ) ; // At least encoded wrapper + 32-byte magic suffix
2411+ // The wrapper account's signMessage will call walletClient.signMessage with the original owner
2412+ expect ( mockWalletClient . signMessage ) . toHaveBeenCalled ( ) ;
2413+ expect ( mockBundlerClient . signAuthorization ) . toHaveBeenCalled ( ) ;
2414+ } ) ;
2415+
2416+ it ( 'should create EIP-6492 signature when EOA is already installed' , async ( ) => {
2417+ const mockOwner = {
2418+ address : '0xowner123456789012345678901234567890' ,
2419+ } as any ;
2420+ const mockBundlerClient = {
2421+ signAuthorization : jest . fn ( ) . mockResolvedValue ( {
2422+ address : '0xdelegate123456789012345678901234567890' ,
2423+ data : '0xabcdef1234567890abcdef1234567890abcdef12' ,
2424+ } ) ,
2425+ } as any ;
2426+ const mockPublicClient = {
2427+ getCode : jest
2428+ . fn ( )
2429+ . mockResolvedValueOnce ( '0xef01001234' ) // Already installed
2430+ . mockResolvedValue ( '0xef01001234' ) ,
2431+ getTransactionCount : jest . fn ( ) . mockResolvedValue ( 5 ) ,
2432+ } as any ;
2433+ const mockWalletClient = {
2434+ signMessage : jest . fn ( ) . mockResolvedValue ( '0x' + '2' . repeat ( 130 ) ) , // Standard signature
2435+ } as any ;
2436+
2437+ mockProvider . getOwnerAccount . mockResolvedValue ( mockOwner ) ;
2438+ mockProvider . getBundlerClient . mockResolvedValue ( mockBundlerClient ) ;
2439+ mockProvider . getPublicClient . mockResolvedValue ( mockPublicClient ) ;
2440+ mockProvider . getWalletClient . mockResolvedValue ( mockWalletClient ) ;
2441+ ( toRlp as jest . Mock ) . mockReturnValue ( '0xdeadbeef1234567890abcdef' ) ;
2442+
2443+ const result = await transactionKit . signMessage ( 'Test message' , 1 ) ;
2444+
2445+ // EIP-6492 format: encodedWrapper || magicBytes (32-byte suffix at end)
2446+ expect ( result ) . toMatch (
2447+ / 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 $ /
2448+ ) ;
2449+ expect ( mockWalletClient . signMessage ) . toHaveBeenCalled ( ) ;
2450+ expect ( mockBundlerClient . signAuthorization ) . toHaveBeenCalled ( ) ;
2451+ } ) ;
2452+
2453+ it ( 'should throw error for non-delegatedEoa wallet mode' , async ( ) => {
2454+ mockProvider . getWalletMode . mockReturnValue ( 'modular' ) ;
2455+
2456+ await expect (
2457+ transactionKit . signMessage ( 'Test message' , 1 )
2458+ ) . rejects . toThrow (
2459+ "signMessage() is only available in 'delegatedEoa' wallet mode"
2460+ ) ;
2461+ } ) ;
2462+
2463+ it ( 'should handle authorization creation failure' , async ( ) => {
2464+ const mockOwner = {
2465+ address : '0xowner123456789012345678901234567890' ,
2466+ } as any ;
2467+ const mockBundlerClient = {
2468+ signAuthorization : jest
2469+ . fn ( )
2470+ . mockRejectedValue ( new Error ( 'Authorization failed' ) ) ,
2471+ } as any ;
2472+ const mockPublicClient = {
2473+ getCode : jest . fn ( ) . mockResolvedValue ( '0x' ) , // Not installed
2474+ } as any ;
2475+
2476+ mockProvider . getOwnerAccount . mockResolvedValue ( mockOwner ) ;
2477+ mockProvider . getBundlerClient . mockResolvedValue ( mockBundlerClient ) ;
2478+ mockProvider . getPublicClient . mockResolvedValue ( mockPublicClient ) ;
2479+ ( viemSignMessage as jest . Mock ) . mockResolvedValue ( '0x' + '1' . repeat ( 130 ) ) ;
2480+
2481+ // This will fail when trying to delegate
2482+ await expect (
2483+ transactionKit . signMessage ( 'Test message' , 1 )
2484+ ) . rejects . toThrow ( ) ;
2485+ } ) ;
2486+
2487+ it ( 'should handle message signing failure' , async ( ) => {
2488+ const mockOwner = {
2489+ address : '0xowner123456789012345678901234567890' ,
2490+ } as any ;
2491+ const mockBundlerClient = {
2492+ signAuthorization : jest . fn ( ) . mockResolvedValue ( {
2493+ address : '0xdelegate123456789012345678901234567890' ,
2494+ data : '0xabcdef1234567890abcdef1234567890abcdef12' ,
2495+ } ) ,
2496+ } as any ;
2497+ const mockPublicClient = {
2498+ getCode : jest . fn ( ) . mockResolvedValue ( '0x' ) ,
2499+ getTransactionCount : jest . fn ( ) . mockResolvedValue ( 5 ) ,
2500+ } as any ;
2501+ const mockWalletClient = {
2502+ signMessage : jest . fn ( ) . mockRejectedValue ( new Error ( 'Signing failed' ) ) ,
2503+ } as any ;
2504+
2505+ mockProvider . getOwnerAccount . mockResolvedValue ( mockOwner ) ;
2506+ mockProvider . getBundlerClient . mockResolvedValue ( mockBundlerClient ) ;
2507+ mockProvider . getPublicClient . mockResolvedValue ( mockPublicClient ) ;
2508+ mockProvider . getWalletClient . mockResolvedValue ( mockWalletClient ) ;
2509+
2510+ await expect (
2511+ transactionKit . signMessage ( 'Test message' , 1 )
2512+ ) . rejects . toThrow ( 'Signing failed' ) ;
2513+ } ) ;
2514+
2515+ it ( 'should use default chainId when not provided' , async ( ) => {
2516+ const mockOwner = {
2517+ address : '0xowner123456789012345678901234567890' ,
2518+ } as any ;
2519+ const mockBundlerClient = {
2520+ signAuthorization : jest . fn ( ) . mockResolvedValue ( {
2521+ address : '0xdelegate123456789012345678901234567890' ,
2522+ data : '0xabcdef1234567890abcdef1234567890abcdef12' ,
2523+ } ) ,
2524+ } as any ;
2525+ const mockPublicClient = {
2526+ getCode : jest . fn ( ) . mockResolvedValue ( '0x' ) ,
2527+ getTransactionCount : jest . fn ( ) . mockResolvedValue ( 5 ) ,
2528+ } as any ;
2529+ const mockWalletClient = {
2530+ signMessage : jest . fn ( ) . mockResolvedValue ( '0x' + '1' . repeat ( 130 ) ) ,
2531+ } as any ;
2532+
2533+ mockProvider . getOwnerAccount . mockResolvedValue ( mockOwner ) ;
2534+ mockProvider . getBundlerClient . mockResolvedValue ( mockBundlerClient ) ;
2535+ mockProvider . getPublicClient . mockResolvedValue ( mockPublicClient ) ;
2536+ mockProvider . getWalletClient . mockResolvedValue ( mockWalletClient ) ;
2537+ mockProvider . getChainId . mockReturnValue ( 1 ) ;
2538+ ( toRlp as jest . Mock ) . mockReturnValue ( '0xdeadbeef1234567890abcdef' ) ;
2539+
2540+ await transactionKit . signMessage ( 'Test message' ) ;
2541+
2542+ expect ( mockProvider . getOwnerAccount ) . toHaveBeenCalledWith ( 1 ) ;
2543+ expect ( mockProvider . getBundlerClient ) . toHaveBeenCalledWith ( 1 ) ;
2544+ expect ( mockProvider . getWalletClient ) . toHaveBeenCalledWith ( 1 ) ;
2545+ } ) ;
2546+
2547+ it ( 'should handle hex string messages' , async ( ) => {
2548+ const mockOwner = {
2549+ address : '0xowner123456789012345678901234567890' ,
2550+ } as any ;
2551+ const mockBundlerClient = {
2552+ signAuthorization : jest . fn ( ) . mockResolvedValue ( {
2553+ address : '0xdelegate123456789012345678901234567890' ,
2554+ data : '0xabcdef1234567890abcdef1234567890abcdef12' ,
2555+ } ) ,
2556+ } as any ;
2557+ const mockPublicClient = {
2558+ getCode : jest . fn ( ) . mockResolvedValue ( '0x' ) ,
2559+ getTransactionCount : jest . fn ( ) . mockResolvedValue ( 5 ) ,
2560+ } as any ;
2561+ const mockWalletClient = {
2562+ signMessage : jest . fn ( ) . mockResolvedValue ( '0x' + '1' . repeat ( 130 ) ) ,
2563+ } as any ;
2564+
2565+ mockProvider . getOwnerAccount . mockResolvedValue ( mockOwner ) ;
2566+ mockProvider . getBundlerClient . mockResolvedValue ( mockBundlerClient ) ;
2567+ mockProvider . getPublicClient . mockResolvedValue ( mockPublicClient ) ;
2568+ mockProvider . getWalletClient . mockResolvedValue ( mockWalletClient ) ;
2569+ ( toRlp as jest . Mock ) . mockReturnValue ( '0xdeadbeef1234567890abcdef' ) ;
2570+
2571+ const result = await transactionKit . signMessage (
2572+ '0x48656c6c6f' as `0x${string } `,
2573+ 1
2574+ ) ;
2575+
2576+ // EIP-6492 format: encodedWrapper || magicBytes (32-byte suffix at end)
2577+ expect ( result ) . toMatch (
2578+ / 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 6 4 9 2 $ /
2579+ ) ;
2580+ expect ( mockWalletClient . signMessage ) . toHaveBeenCalled ( ) ;
2581+ } ) ;
2582+ } ) ;
2583+
23382584 describe ( 'estimate with delegatedEoa mode' , ( ) => {
23392585 it ( 'should estimate transaction in delegatedEoa mode when EOA is designated' , async ( ) => {
23402586 const mockAccount = {
0 commit comments