UNPKG

ox

Version:

Ethereum Standard Library

1,523 lines (1,453 loc) 56.3 kB
import { Hex, Signature } from 'ox' import { UserOperation } from 'ox/erc4337' import { describe, expect, test } from 'vp/test' describe('from', () => { test('default', () => { const input = { callData: '0xdeadbeef', callGasLimit: 0n, maxFeePerGas: 0n, maxPriorityFeePerGas: 0n, nonce: 0n, preVerificationGas: 0n, sender: Hex.random(20), verificationGasLimit: 0n, } as const satisfies UserOperation.UserOperation const userOperation = UserOperation.from(input) expect(userOperation).toEqual(input) }) test('options: signature', () => { const input = { callData: '0xdeadbeef', callGasLimit: 0n, maxFeePerGas: 0n, maxPriorityFeePerGas: 0n, nonce: 0n, preVerificationGas: 0n, sender: Hex.random(20), verificationGasLimit: 0n, } as const satisfies UserOperation.UserOperation const signature = Signature.from({ r: '0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf', s: '0x4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8', yParity: 1, }) const userOperation = UserOperation.from(input, { signature }) expect(userOperation).toEqual({ ...input, signature: Signature.toHex(signature), }) }) test('options: signature (hex)', () => { const input = { callData: '0xdeadbeef', callGasLimit: 0n, maxFeePerGas: 0n, maxPriorityFeePerGas: 0n, nonce: 0n, preVerificationGas: 0n, sender: Hex.random(20), verificationGasLimit: 0n, } as const satisfies UserOperation.UserOperation const signature = Hex.random(64) const userOperation = UserOperation.from(input, { signature }) expect(userOperation).toEqual({ ...input, signature }) }) test('behavior: packed user operation', () => { const packed = { accountGasLimits: '0x000000000000000000000000000186a0000000000000000000000000000493e0', callData: '0xdeadbeef', gasFees: '0x000000000000000000000000000186a0000000000000000000000000000186a0', initCode: '0x1234567890123456789012345678901234567890deadbeef', nonce: 42n, paymasterAndData: '0x12345678901234567890123456789012345678900000000000000000000000000000000000000000000000000000000000000000deadbeef', preVerificationGas: 100_000n, sender: '0x1234567890123456789012345678901234567890', signature: '0xabcdef', } as const satisfies UserOperation.Packed const userOperation = UserOperation.from(packed) expect(userOperation).toEqual({ callData: '0xdeadbeef', callGasLimit: 300_000n, factory: '0x1234567890123456789012345678901234567890', factoryData: '0xdeadbeef', maxFeePerGas: 100_000n, maxPriorityFeePerGas: 100_000n, nonce: 42n, paymaster: '0x1234567890123456789012345678901234567890', paymasterData: '0xdeadbeef', paymasterPostOpGasLimit: 0n, paymasterVerificationGasLimit: 0n, preVerificationGas: 100_000n, sender: '0x1234567890123456789012345678901234567890', signature: '0xabcdef', verificationGasLimit: 100_000n, }) }) test('behavior: packed user operation with signature override', () => { const packed = { accountGasLimits: '0x000000000000000000000000000186a0000000000000000000000000000493e0', callData: '0xdeadbeef', gasFees: '0x000000000000000000000000000186a0000000000000000000000000000186a0', initCode: '0x', nonce: 42n, paymasterAndData: '0x', preVerificationGas: 100_000n, sender: '0x1234567890123456789012345678901234567890', signature: '0xdeadbeef', } as const satisfies UserOperation.Packed const newSignature = '0xcafebabe' const userOperation = UserOperation.from(packed, { signature: newSignature, }) expect(userOperation).toEqual({ callData: '0xdeadbeef', callGasLimit: 300_000n, maxFeePerGas: 100_000n, maxPriorityFeePerGas: 100_000n, nonce: 42n, preVerificationGas: 100_000n, sender: '0x1234567890123456789012345678901234567890', signature: newSignature, verificationGasLimit: 100_000n, }) }) }) describe('fromRpc', () => { test('default', () => { expect( UserOperation.fromRpc({ callData: '0xdeadbeef', callGasLimit: '0x69420', maxFeePerGas: '0x2ca6ae494', maxPriorityFeePerGas: '0x2ca6ae494', nonce: '0x0', preVerificationGas: '0x69420', signature: '0x', sender: '0x1234567890123456789012345678901234567890', verificationGasLimit: '0x69420', }), ).toMatchInlineSnapshot(` { "callData": "0xdeadbeef", "callGasLimit": 431136n, "maxFeePerGas": 11985937556n, "maxPriorityFeePerGas": 11985937556n, "nonce": 0n, "preVerificationGas": 431136n, "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", "verificationGasLimit": 431136n, } `) expect( UserOperation.fromRpc({ callData: '0xdeadbeef', callGasLimit: '0x69420', maxFeePerGas: '0x2ca6ae494', maxPriorityFeePerGas: '0x2ca6ae494', nonce: '0x0', preVerificationGas: '0x69420', signature: '0x', sender: '0x1234567890123456789012345678901234567890', verificationGasLimit: '0x69420', paymasterPostOpGasLimit: '0x69420', paymasterVerificationGasLimit: '0x69420', }), ).toMatchInlineSnapshot(` { "callData": "0xdeadbeef", "callGasLimit": 431136n, "maxFeePerGas": 11985937556n, "maxPriorityFeePerGas": 11985937556n, "nonce": 0n, "paymasterPostOpGasLimit": 431136n, "paymasterVerificationGasLimit": 431136n, "preVerificationGas": 431136n, "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", "verificationGasLimit": 431136n, } `) }) }) describe('getSignPayload', () => { test('default', () => { expect( UserOperation.getSignPayload( { callData: '0xdeadbeef', callGasLimit: 0n, maxFeePerGas: 0n, maxPriorityFeePerGas: 0n, nonce: 0n, preVerificationGas: 0n, sender: '0x0000000000000000000000000000000000000000', verificationGasLimit: 0n, }, { chainId: 1, entryPointAddress: '0x1234567890123456789012345678901234567890', entryPointVersion: '0.7', }, ), ).toMatchInlineSnapshot( `"0xf22cfb24d100997764c478f19c3a007dd972c5e82645a44cf9965c78dbad6ef0"`, ) }) }) describe('hash', () => { describe('v0.9', () => { const userOperation = { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, paymaster: '0x1234567890123456789012345678901234567890', paymasterData: '0xdeadbeef', paymasterPostOpGasLimit: 6942069n, paymasterVerificationGasLimit: 6942069n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, } as const satisfies UserOperation.UserOperation<'0.9', true> test('args: paymasterSignature', () => { const options = { chainId: 1, entryPointAddress: '0x433709009B8330FDa32311DF1C2AFA402eD8D009', entryPointVersion: '0.9', } as const expect( UserOperation.hash( { ...userOperation, paymasterSignature: '0xcafebabe' }, options, ), ).toMatchInlineSnapshot( `"0x802c25af5d98cd349b2118227faa93172fb791c4130a482272095cec45c4fc6e"`, ) expect( UserOperation.hash( { ...userOperation, paymasterSignature: '0xdeadbeef' }, options, ), ).toMatchInlineSnapshot( `"0x802c25af5d98cd349b2118227faa93172fb791c4130a482272095cec45c4fc6e"`, ) expect(UserOperation.hash(userOperation, options)).toMatchInlineSnapshot( `"0xde381ae0368b77df677808c3a2da72abd68a07eab11ef31c35923f56a75bbe6d"`, ) }) }) describe('v0.8', () => { test('default', () => { expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, }, { chainId: 1, entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', entryPointVersion: '0.8', }, ), ).toMatchInlineSnapshot( `"0xa2224e732a1d4e2f923c7c05d586a0aa6cbc42172ec02f31d35fa9a2b8ba9208"`, ) }) test('args: factory + factoryData', () => { expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, factory: '0x1234567890123456789012345678901234567890', factoryData: '0xdeadbeef', }, { chainId: 1, entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', entryPointVersion: '0.8', }, ), ).toMatchInlineSnapshot( `"0x3146c70a9ef7538e9b9aca8b00ad4b127ca7eef7817a557f1801acbf8d68c206"`, ) }) test('args: paymaster', () => { expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, paymaster: '0x1234567890123456789012345678901234567890', }, { chainId: 1, entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', entryPointVersion: '0.8', }, ), ).toMatchInlineSnapshot( `"0x364bff8f9104a3854dce4f61f8479ce3019a3bd23e1c8db4da0d7c22850835b9"`, ) }) test('args: paymasterVerificationGasLimit', () => { expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, paymaster: '0x1234567890123456789012345678901234567890', paymasterVerificationGasLimit: 6942069n, }, { chainId: 1, entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', entryPointVersion: '0.8', }, ), ).toMatchInlineSnapshot( `"0x9cb735eb4278caf0a9f53ad81e5f592e965c9d034f6e2780befa4cd09a990b04"`, ) }) test('args: paymasterPostOpGasLimit', () => { expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, paymaster: '0x1234567890123456789012345678901234567890', paymasterVerificationGasLimit: 6942069n, paymasterPostOpGasLimit: 6942069n, }, { chainId: 1, entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', entryPointVersion: '0.8', }, ), ).toMatchInlineSnapshot( `"0x745602113988c3a6f18215d96eecc85775998dcb190e374a0955e637d40fe018"`, ) }) test('args: paymasterData', () => { expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, paymaster: '0x1234567890123456789012345678901234567890', paymasterVerificationGasLimit: 6942069n, paymasterPostOpGasLimit: 6942069n, paymasterData: '0xdeadbeef', }, { chainId: 1, entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', entryPointVersion: '0.8', }, ), ).toMatchInlineSnapshot( `"0xf10ef9afcf27a4fd17e477cd19f37e588e3ff7d48eade07ab5b7eb8caf75667f"`, ) }) test('args: authorization', () => { expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, paymaster: '0x1234567890123456789012345678901234567890', paymasterVerificationGasLimit: 6942069n, paymasterPostOpGasLimit: 6942069n, paymasterData: '0xdeadbeef', factory: '0x7702', factoryData: '0xdeadbeef', authorization: { address: '0x1234567890123456789012345678901234567890', chainId: 1, nonce: 0n, yParity: 0, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000000', }, }, { chainId: 1, entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', entryPointVersion: '0.8', }, ), ).toMatchInlineSnapshot( `"0xd96232eb5d02f483166b9b23dca3ec2b963d70f09b961fce348c51d306278462"`, ) expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, paymaster: '0x1234567890123456789012345678901234567890', paymasterVerificationGasLimit: 6942069n, paymasterPostOpGasLimit: 6942069n, paymasterData: '0xdeadbeef', factory: '0x7702', authorization: { address: '0x1234567890123456789012345678901234567890', chainId: 1, nonce: 0n, yParity: 0, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000000', }, }, { chainId: 1, entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', entryPointVersion: '0.8', }, ), ).toMatchInlineSnapshot( `"0x66fe6eaaaf1d6727d404384354a720cb24694c697bfbab77b3d02345c2d6e1da"`, ) }) }) describe('v0.7', () => { test('default', () => { const hash = UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, }, { chainId: 1, entryPointAddress: '0x1234567890123456789012345678901234567890', entryPointVersion: '0.7', }, ) expect(hash).toEqual( '0x1903d62bb5dc75af6fed866aa46d8e80063d9e288aa7f2caad0ff1fcae22e40d', ) }) test('args: factory + factoryData', () => { expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, factory: '0x1234567890123456789012345678901234567890', factoryData: '0xdeadbeef', }, { chainId: 1, entryPointAddress: '0x1234567890123456789012345678901234567890', entryPointVersion: '0.7', }, ), ).toMatchInlineSnapshot( `"0x46c1d51e831d50c1a93135f026a7d3f1921ed66e9c81da723dd3817a49f01bc1"`, ) }) test('args: paymaster', () => { expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, paymaster: '0x1234567890123456789012345678901234567890', }, { chainId: 1, entryPointAddress: '0x1234567890123456789012345678901234567890', entryPointVersion: '0.7', }, ), ).toMatchInlineSnapshot( `"0x1f2cf8638ead0fc621c6fb1562b8222c06539efcec09be156191f72418ebb109"`, ) }) test('args: paymasterVerificationGasLimit', () => { expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, paymaster: '0x1234567890123456789012345678901234567890', paymasterVerificationGasLimit: 6942069n, }, { chainId: 1, entryPointAddress: '0x1234567890123456789012345678901234567890', entryPointVersion: '0.7', }, ), ).toMatchInlineSnapshot( `"0x950763d3ef84eb05b6cd1d95f3b736bb02988d643c6e11a76bf8beddc611cc95"`, ) }) test('args: paymasterPostOpGasLimit', () => { expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, paymaster: '0x1234567890123456789012345678901234567890', paymasterVerificationGasLimit: 6942069n, paymasterPostOpGasLimit: 6942069n, }, { chainId: 1, entryPointAddress: '0x1234567890123456789012345678901234567890', entryPointVersion: '0.7', }, ), ).toMatchInlineSnapshot( `"0xd6efc63c28df53b49dd6fa10cec0a92ac61f8c70e9a45265e39c955f9bf821ed"`, ) }) test('args: paymasterData', () => { expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, paymaster: '0x1234567890123456789012345678901234567890', paymasterVerificationGasLimit: 6942069n, paymasterPostOpGasLimit: 6942069n, paymasterData: '0xdeadbeef', }, { chainId: 1, entryPointAddress: '0x1234567890123456789012345678901234567890', entryPointVersion: '0.7', }, ), ).toMatchInlineSnapshot( `"0x265fc1350b3fc016d493f9533354cf1a758c0fb9ddbbfd8b19c987d4e8935eed"`, ) }) }) describe('v0.6', () => { test('default', () => { expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, }, { chainId: 1, entryPointAddress: '0x1234567890123456789012345678901234567890', entryPointVersion: '0.6', }, ), ).toMatchInlineSnapshot( `"0xe331591ab320e956b5e93f04e1dcf706bc128bc7b510602d2e0553f8be25fcba"`, ) }) test('args: initCode', () => { expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, initCode: '0x1234567890123456789012345678901234567890deadbeef', }, { chainId: 1, entryPointAddress: '0x1234567890123456789012345678901234567890', entryPointVersion: '0.6', }, ), ).toMatchInlineSnapshot( `"0xaa4a4fa863b3018e0e23291ca82a8747d06c6a92548eb9198f54f4a63540d06e"`, ) }) test('args: paymasterAndData', () => { expect( UserOperation.hash( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, paymasterAndData: '0x1234567890123456789012345678901234567890', }, { chainId: 1, entryPointAddress: '0x1234567890123456789012345678901234567890', entryPointVersion: '0.6', }, ), ).toMatchInlineSnapshot( `"0x72bb2d82af9e9da2079fab165bc219c967c6ca0a63dfa55f382c5914ba2f77c5"`, ) }) }) }) describe('toPacked', () => { test('default', () => { expect( UserOperation.toPacked({ callData: '0xdeadbeef', callGasLimit: 300_000n, maxFeePerGas: 100_000n, maxPriorityFeePerGas: 100_000n, nonce: 0n, preVerificationGas: 100_000n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 100_000n, }), ).toMatchInlineSnapshot(` { "accountGasLimits": "0x000000000000000000000000000186a0000000000000000000000000000493e0", "callData": "0xdeadbeef", "gasFees": "0x000000000000000000000000000186a0000000000000000000000000000186a0", "initCode": "0x", "nonce": 0n, "paymasterAndData": "0x", "preVerificationGas": 100000n, "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", } `) }) test('args: factory + factoryData', () => { expect( UserOperation.toPacked({ callData: '0xdeadbeef', callGasLimit: 300_000n, factory: '0x1234567890123456789012345678901234567890', factoryData: '0xdeadbeef', maxFeePerGas: 100_000n, maxPriorityFeePerGas: 100_000n, nonce: 0n, preVerificationGas: 100_000n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 100_000n, }), ).toMatchInlineSnapshot(` { "accountGasLimits": "0x000000000000000000000000000186a0000000000000000000000000000493e0", "callData": "0xdeadbeef", "gasFees": "0x000000000000000000000000000186a0000000000000000000000000000186a0", "initCode": "0x1234567890123456789012345678901234567890deadbeef", "nonce": 0n, "paymasterAndData": "0x", "preVerificationGas": 100000n, "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", } `) }) test('args: paymaster', () => { expect( UserOperation.toPacked({ callData: '0xdeadbeef', callGasLimit: 300_000n, factory: '0x1234567890123456789012345678901234567890', factoryData: '0xdeadbeef', maxFeePerGas: 100_000n, maxPriorityFeePerGas: 100_000n, paymaster: '0x1234567890123456789012345678901234567890', nonce: 0n, preVerificationGas: 100_000n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 100_000n, }), ).toMatchInlineSnapshot(` { "accountGasLimits": "0x000000000000000000000000000186a0000000000000000000000000000493e0", "callData": "0xdeadbeef", "gasFees": "0x000000000000000000000000000186a0000000000000000000000000000186a0", "initCode": "0x1234567890123456789012345678901234567890deadbeef", "nonce": 0n, "paymasterAndData": "0x12345678901234567890123456789012345678900000000000000000000000000000000000000000000000000000000000000000", "preVerificationGas": 100000n, "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", } `) }) test('args: paymaster, paymasterData', () => { expect( UserOperation.toPacked({ callData: '0xdeadbeef', callGasLimit: 300_000n, factory: '0x1234567890123456789012345678901234567890', factoryData: '0xdeadbeef', maxFeePerGas: 100_000n, maxPriorityFeePerGas: 100_000n, paymaster: '0x1234567890123456789012345678901234567890', paymasterData: '0xdeadbeef', nonce: 0n, preVerificationGas: 100_000n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 100_000n, }), ).toMatchInlineSnapshot(` { "accountGasLimits": "0x000000000000000000000000000186a0000000000000000000000000000493e0", "callData": "0xdeadbeef", "gasFees": "0x000000000000000000000000000186a0000000000000000000000000000186a0", "initCode": "0x1234567890123456789012345678901234567890deadbeef", "nonce": 0n, "paymasterAndData": "0x12345678901234567890123456789012345678900000000000000000000000000000000000000000000000000000000000000000deadbeef", "preVerificationGas": 100000n, "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", } `) }) describe('v0.9 paymasterSignature', () => { const userOperation = { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, paymaster: '0x1234567890123456789012345678901234567890', paymasterData: '0xdeadbeef', paymasterPostOpGasLimit: 6942069n, paymasterSignature: '0xcafebabe', paymasterVerificationGasLimit: 6942069n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, } as const satisfies UserOperation.UserOperation<'0.9', true> test('default', () => { expect( UserOperation.toPacked(userOperation).paymasterAndData, ).toMatchInlineSnapshot( `"0x12345678901234567890123456789012345678900000000000000000000000000069ed750000000000000000000000000069ed75deadbeefcafebabe000422e325a297439656"`, ) }) test('options: forHash', () => { expect( UserOperation.toPacked(userOperation, { forHash: true }) .paymasterAndData, ).toMatchInlineSnapshot( `"0x12345678901234567890123456789012345678900000000000000000000000000069ed750000000000000000000000000069ed75deadbeef22e325a297439656"`, ) }) test('encodes a uint16 signature length', () => { const paymasterSignature = Hex.fromBytes(new Uint8Array(65).fill(0x11)) const paymasterAndData = UserOperation.toPacked({ ...userOperation, paymasterSignature, }).paymasterAndData expect( Hex.slice(paymasterAndData, Hex.size(paymasterAndData) - 10), ).toMatchInlineSnapshot(`"0x004122e325a297439656"`) }) }) }) describe('toInitCode', () => { test('behavior: no factory', () => { expect( UserOperation.toInitCode({ factory: undefined, factoryData: undefined, authorization: undefined, }), ).toBe('0x') }) test('behavior: regular factory without factoryData', () => { expect( UserOperation.toInitCode({ factory: '0x1234567890123456789012345678901234567890', factoryData: undefined, authorization: undefined, }), ).toBe('0x1234567890123456789012345678901234567890') }) test('behavior: regular factory with factoryData', () => { expect( UserOperation.toInitCode({ factory: '0x1234567890123456789012345678901234567890', factoryData: '0xdeadbeef', authorization: undefined, }), ).toBe('0x1234567890123456789012345678901234567890deadbeef') }) test('behavior: EIP-7702 factory (short form) without authorization', () => { expect( UserOperation.toInitCode({ factory: '0x7702', factoryData: undefined, authorization: undefined, }), ).toBe('0x7702000000000000000000000000000000000000') }) test('behavior: EIP-7702 factory (full form) without authorization', () => { expect( UserOperation.toInitCode({ factory: '0x7702000000000000000000000000000000000000', factoryData: undefined, authorization: undefined, }), ).toBe('0x7702000000000000000000000000000000000000') }) test('behavior: EIP-7702 factory with authorization and no factoryData', () => { expect( UserOperation.toInitCode({ factory: '0x7702', factoryData: undefined, authorization: { address: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357', chainId: 1, nonce: 69n, yParity: 0, r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', }, }), ).toBe('0x9f1fdab6458c5fc642fa0f4c5af7473c46837357') }) test('behavior: EIP-7702 factory with authorization and factoryData', () => { expect( UserOperation.toInitCode({ factory: '0x7702', factoryData: '0xdeadbeef', authorization: { address: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357', chainId: 1, nonce: 69n, yParity: 0, r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', }, }), ).toBe('0x9f1fdab6458c5fc642fa0f4c5af7473c46837357deadbeef') }) test('behavior: EIP-7702 factory (full form) with authorization and factoryData', () => { expect( UserOperation.toInitCode({ factory: '0x7702000000000000000000000000000000000000', factoryData: '0xcafebabe', authorization: { address: '0x1234567890123456789012345678901234567890', chainId: 1, nonce: 42n, yParity: 1, r: '0x000000000000000000000000000000000000000000000000000000000000007b', s: '0x00000000000000000000000000000000000000000000000000000000000001c8', }, }), ).toBe('0x1234567890123456789012345678901234567890cafebabe') }) }) describe('fromPacked', () => { test('default', () => { const packed = { accountGasLimits: '0x000000000000000000000000000186a0000000000000000000000000000493e0', callData: '0xdeadbeef', gasFees: '0x000000000000000000000000000186a0000000000000000000000000000186a0', initCode: '0x', nonce: 0n, paymasterAndData: '0x', preVerificationGas: 100000n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', } as const satisfies UserOperation.Packed expect(UserOperation.fromPacked(packed)).toMatchInlineSnapshot(` { "callData": "0xdeadbeef", "callGasLimit": 300000n, "maxFeePerGas": 100000n, "maxPriorityFeePerGas": 100000n, "nonce": 0n, "preVerificationGas": 100000n, "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", "verificationGasLimit": 100000n, } `) }) test('args: factory + factoryData', () => { const packed = { accountGasLimits: '0x000000000000000000000000000186a0000000000000000000000000000493e0', callData: '0xdeadbeef', gasFees: '0x000000000000000000000000000186a0000000000000000000000000000186a0', initCode: '0x1234567890123456789012345678901234567890deadbeef', nonce: 0n, paymasterAndData: '0x', preVerificationGas: 100000n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', } as const satisfies UserOperation.Packed expect(UserOperation.fromPacked(packed)).toMatchInlineSnapshot(` { "callData": "0xdeadbeef", "callGasLimit": 300000n, "factory": "0x1234567890123456789012345678901234567890", "factoryData": "0xdeadbeef", "maxFeePerGas": 100000n, "maxPriorityFeePerGas": 100000n, "nonce": 0n, "preVerificationGas": 100000n, "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", "verificationGasLimit": 100000n, } `) }) test('args: paymaster', () => { const packed = { accountGasLimits: '0x000000000000000000000000000186a0000000000000000000000000000493e0', callData: '0xdeadbeef', gasFees: '0x000000000000000000000000000186a0000000000000000000000000000186a0', initCode: '0x1234567890123456789012345678901234567890deadbeef', nonce: 0n, paymasterAndData: '0x12345678901234567890123456789012345678900000000000000000000000000000000000000000000000000000000000000000', preVerificationGas: 100000n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', } as const satisfies UserOperation.Packed expect(UserOperation.fromPacked(packed)).toMatchInlineSnapshot(` { "callData": "0xdeadbeef", "callGasLimit": 300000n, "factory": "0x1234567890123456789012345678901234567890", "factoryData": "0xdeadbeef", "maxFeePerGas": 100000n, "maxPriorityFeePerGas": 100000n, "nonce": 0n, "paymaster": "0x1234567890123456789012345678901234567890", "paymasterPostOpGasLimit": 0n, "paymasterVerificationGasLimit": 0n, "preVerificationGas": 100000n, "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", "verificationGasLimit": 100000n, } `) }) test('args: paymaster, paymasterData', () => { const packed = { accountGasLimits: '0x000000000000000000000000000186a0000000000000000000000000000493e0', callData: '0xdeadbeef', gasFees: '0x000000000000000000000000000186a0000000000000000000000000000186a0', initCode: '0x1234567890123456789012345678901234567890deadbeef', nonce: 0n, paymasterAndData: '0x12345678901234567890123456789012345678900000000000000000000000000000000000000000000000000000000000000000deadbeef', preVerificationGas: 100000n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', } as const satisfies UserOperation.Packed expect(UserOperation.fromPacked(packed)).toMatchInlineSnapshot(` { "callData": "0xdeadbeef", "callGasLimit": 300000n, "factory": "0x1234567890123456789012345678901234567890", "factoryData": "0xdeadbeef", "maxFeePerGas": 100000n, "maxPriorityFeePerGas": 100000n, "nonce": 0n, "paymaster": "0x1234567890123456789012345678901234567890", "paymasterData": "0xdeadbeef", "paymasterPostOpGasLimit": 0n, "paymasterVerificationGasLimit": 0n, "preVerificationGas": 100000n, "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", "verificationGasLimit": 100000n, } `) }) test('behavior: round-trip: fromPacked(toPacked(userOperation)) === userOperation', () => { const userOperation = { callData: '0xdeadbeef', callGasLimit: 300_000n, factory: '0x1234567890123456789012345678901234567890', factoryData: '0xdeadbeef', maxFeePerGas: 100_000n, maxPriorityFeePerGas: 50_000n, nonce: 42n, paymaster: '0x9876543210987654321098765432109876543210', paymasterData: '0xcafebabe', paymasterPostOpGasLimit: 150_000n, paymasterVerificationGasLimit: 200_000n, preVerificationGas: 100_000n, sender: '0x1234567890123456789012345678901234567890', signature: '0xabcdef', verificationGasLimit: 250_000n, } as const satisfies UserOperation.UserOperation<'0.7', true> const packed = UserOperation.toPacked(userOperation) const unpacked = UserOperation.fromPacked(packed) expect(unpacked).toEqual(userOperation) }) test('behavior: round-trip: minimal userOperation', () => { const userOperation = { callData: '0x', callGasLimit: 0n, maxFeePerGas: 0n, maxPriorityFeePerGas: 0n, nonce: 0n, preVerificationGas: 0n, sender: '0x0000000000000000000000000000000000000000', signature: '0x', verificationGasLimit: 0n, } as const satisfies UserOperation.UserOperation<'0.7', true> const packed = UserOperation.toPacked(userOperation) const unpacked = UserOperation.fromPacked(packed) expect(unpacked).toEqual(userOperation) }) }) describe('toRpc', () => { test('default', () => { expect( UserOperation.toRpc({ callData: '0xdeadbeef', callGasLimit: 300_000n, maxFeePerGas: 100_000n, maxPriorityFeePerGas: 100_000n, nonce: 0n, preVerificationGas: 100_000n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 100_000n, }), ).toMatchInlineSnapshot(` { "callData": "0xdeadbeef", "callGasLimit": "0x493e0", "maxFeePerGas": "0x186a0", "maxPriorityFeePerGas": "0x186a0", "nonce": "0x0", "preVerificationGas": "0x186a0", "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", "verificationGasLimit": "0x186a0", } `) expect( UserOperation.toRpc({ callData: '0xdeadbeef', callGasLimit: 300_000n, factory: '0x1234567890123456789012345678901234567890', factoryData: '0xdeadbeef', maxFeePerGas: 100_000n, maxPriorityFeePerGas: 100_000n, nonce: 0n, paymaster: '0x1234567890123456789012345678901234567890', paymasterData: '0xdeadbeef', paymasterPostOpGasLimit: 100_000n, paymasterVerificationGasLimit: 100_000n, preVerificationGas: 100_000n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 100_000n, }), ).toMatchInlineSnapshot(` { "callData": "0xdeadbeef", "callGasLimit": "0x493e0", "factory": "0x1234567890123456789012345678901234567890", "factoryData": "0xdeadbeef", "maxFeePerGas": "0x186a0", "maxPriorityFeePerGas": "0x186a0", "nonce": "0x0", "paymaster": "0x1234567890123456789012345678901234567890", "paymasterData": "0xdeadbeef", "paymasterPostOpGasLimit": "0x186a0", "paymasterVerificationGasLimit": "0x186a0", "preVerificationGas": "0x186a0", "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", "verificationGasLimit": "0x186a0", } `) }) test('v0.6 preserves paymasterAndData', () => { expect( UserOperation.toRpc({ callData: '0x', callGasLimit: 0n, maxFeePerGas: 0n, maxPriorityFeePerGas: 0n, nonce: 0n, paymasterAndData: '0x1234567890123456789012345678901234567890deadbeef', preVerificationGas: 0n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 0n, } satisfies UserOperation.UserOperation<'0.6', true>), ).toMatchInlineSnapshot(` { "callData": "0x", "callGasLimit": "0x0", "maxFeePerGas": "0x0", "maxPriorityFeePerGas": "0x0", "nonce": "0x0", "paymasterAndData": "0x1234567890123456789012345678901234567890deadbeef", "preVerificationGas": "0x0", "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", "verificationGasLimit": "0x0", } `) }) test('v0.6 preserves empty paymasterAndData', () => { expect( UserOperation.toRpc({ callData: '0x', callGasLimit: 0n, maxFeePerGas: 0n, maxPriorityFeePerGas: 0n, nonce: 0n, paymasterAndData: '0x', preVerificationGas: 0n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 0n, } satisfies UserOperation.UserOperation<'0.6', true>), ).toMatchInlineSnapshot(` { "callData": "0x", "callGasLimit": "0x0", "maxFeePerGas": "0x0", "maxPriorityFeePerGas": "0x0", "nonce": "0x0", "paymasterAndData": "0x", "preVerificationGas": "0x0", "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", "verificationGasLimit": "0x0", } `) }) test('v0.9 preserves paymasterSignature', () => { expect( UserOperation.toRpc({ callData: '0x', callGasLimit: 0n, maxFeePerGas: 0n, maxPriorityFeePerGas: 0n, nonce: 0n, paymasterSignature: '0xcafebabe', preVerificationGas: 0n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 0n, } satisfies UserOperation.UserOperation<'0.9', true>), ).toMatchInlineSnapshot(` { "callData": "0x", "callGasLimit": "0x0", "maxFeePerGas": "0x0", "maxPriorityFeePerGas": "0x0", "nonce": "0x0", "paymasterSignature": "0xcafebabe", "preVerificationGas": "0x0", "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", "verificationGasLimit": "0x0", } `) }) }) describe('toTypedData', () => { describe('v0.8', () => { test('default', () => { expect( UserOperation.toTypedData( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, }, { chainId: 1, entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', }, ), ).toMatchInlineSnapshot( ` { "domain": { "chainId": 1, "name": "ERC4337", "verifyingContract": "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108", "version": "1", }, "message": { "accountGasLimits": "0x0000000000000000000000000069ed750000000000000000000000000069ed75", "callData": "0x", "gasFees": "0x0000000000000000000000000000004500000000000000000000000000010f2c", "initCode": "0x", "nonce": 0n, "paymasterAndData": "0x", "preVerificationGas": 6942069n, "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", }, "primaryType": "PackedUserOperation", "types": { "PackedUserOperation": [ { "name": "sender", "type": "address", }, { "name": "nonce", "type": "uint256", }, { "name": "initCode", "type": "bytes", }, { "name": "callData", "type": "bytes", }, { "name": "accountGasLimits", "type": "bytes32", }, { "name": "preVerificationGas", "type": "uint256", }, { "name": "gasFees", "type": "bytes32", }, { "name": "paymasterAndData", "type": "bytes", }, ], }, } `, ) }) test('args: authorization', () => { expect( UserOperation.toTypedData( { callData: '0x', callGasLimit: 6942069n, maxFeePerGas: 69420n, maxPriorityFeePerGas: 69n, nonce: 0n, preVerificationGas: 6942069n, sender: '0x1234567890123456789012345678901234567890', signature: '0x', verificationGasLimit: 6942069n, paymaster: '0x1234567890123456789012345678901234567890', paymasterVerificationGasLimit: 6942069n, paymasterPostOpGasLimit: 6942069n, paymasterData: '0xdeadbeef', factory: '0x7702', factoryData: '0xdeadbeef', authorization: { address: '0x1234567890123456789012345678901234567890', chainId: 1, nonce: 0n, yParity: 0, r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', }, }, { chainId: 1, entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', }, ), ).toMatchInlineSnapshot( ` { "domain": { "chainId": 1, "name": "ERC4337", "verifyingContract": "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108", "version": "1", }, "message": { "accountGasLimits": "0x0000000000000000000000000069ed750000000000000000000000000069ed75", "callData": "0x", "gasFees": "0x0000000000000000000000000000004500000000000000000000000000010f2c", "initCode": "0x1234567890123456789012345678901234567890deadbeef", "nonce": 0n, "paymasterAndData": "0x12345678901234567890123456789012345678900000000000000000000000000069ed750000000000000000000000000069ed75deadbeef", "preVerificationGas": 6942069n, "sender": "0x1234567890123456789012345678901234567890", "signature": "0x", }, "primaryType": "PackedUserOperation", "types": { "PackedUserOperation": [ { "name": "sender", "type": "address", }, { "name": "nonce", "type": "uint256", }, { "name": "initCode", "type": "bytes", }, { "name": "callData", "type": "bytes", }, { "name": "accountGasLimits", "type": "bytes32", }, { "name": "preVerificationGas", "type": "uint256", }, { "name":