diff --git a/.gitmodules b/.gitmodules index 18b462b652..4fc1a0c858 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "contracts/lib/solady-6c2d0da"] path = contracts/lib/solady-6c2d0da url = https://github.com/Vectorized/solady +[submodule "contracts/lib/account-abstraction-4cbc060"] + path = contracts/lib/account-abstraction-4cbc060 + url = https://github.com/eth-infinitism/account-abstraction diff --git a/contracts/foundry.toml b/contracts/foundry.toml index 7331fb1cf8..fbce4d9152 100644 --- a/contracts/foundry.toml +++ b/contracts/foundry.toml @@ -6,6 +6,7 @@ remappings = [ "solady/=lib/solady/src/", "solady-dc09481/=lib/solady-dc09481/src/", "solady-6c2d0da/=lib/solady-6c2d0da/src/", + "account-abstraction-4cbc060/=lib/account-abstraction-4cbc060/contracts/", ] src = "src" out = "out" diff --git a/contracts/lib/account-abstraction-4cbc060 b/contracts/lib/account-abstraction-4cbc060 new file mode 160000 index 0000000000..4cbc06072c --- /dev/null +++ b/contracts/lib/account-abstraction-4cbc060 @@ -0,0 +1 @@ +Subproject commit 4cbc06072cdc19fd60f285c5997f4f7f57a588de diff --git a/contracts/src/accounts/Simple7702Account_08.sol b/contracts/src/accounts/Simple7702Account_08.sol new file mode 100644 index 0000000000..19f6b56cf5 --- /dev/null +++ b/contracts/src/accounts/Simple7702Account_08.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.13; + +import {Simple7702Account} from "account-abstraction-4cbc060/accounts/Simple7702Account.sol"; + +contract Simple7702AccountV08 is Simple7702Account {} diff --git a/contracts/src/paymasters/VerifyingPaymaster.sol b/contracts/src/paymasters/VerifyingPaymaster.sol index 10415e6f81..cbb4c4d941 100644 --- a/contracts/src/paymasters/VerifyingPaymaster.sol +++ b/contracts/src/paymasters/VerifyingPaymaster.sol @@ -5,8 +5,5 @@ import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; import {VerifyingPaymaster as VerifyingPaymaster_} from "account-abstraction/samples/VerifyingPaymaster.sol"; contract VerifyingPaymaster is VerifyingPaymaster_ { - constructor( - IEntryPoint entryPoint, - address verifyingSigner - ) VerifyingPaymaster_(entryPoint, verifyingSigner) {} + constructor(IEntryPoint entryPoint, address verifyingSigner) VerifyingPaymaster_(entryPoint, verifyingSigner) {} } diff --git a/package.json b/package.json index 548e613036..b5010f9009 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ { "name": "import * from 'viem/account-abstraction'", "path": "./src/_esm/account-abstraction/index.js", - "limit": "50 kB", + "limit": "55 kB", "import": "*" }, { diff --git a/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.test.ts b/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.test.ts index 1cbe59c6ac..d8df786f04 100644 --- a/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.test.ts +++ b/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.test.ts @@ -750,7 +750,6 @@ describe('smoke', async () => { client, owners: [owner], }) - await sendTransaction(client, { account: accounts[9].address, to: account.address, diff --git a/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.ts b/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.ts index 296b903af4..79579714b9 100644 --- a/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.ts +++ b/src/account-abstraction/accounts/implementations/toCoinbaseSmartAccount.ts @@ -4,7 +4,6 @@ import type * as WebAuthnP256 from 'ox/WebAuthnP256' import type { LocalAccount } from '../../../accounts/types.js' import { readContract } from '../../../actions/public/readContract.js' -import type { Client } from '../../../clients/createClient.js' import { entryPoint06Address } from '../../../constants/address.js' import { BaseError } from '../../../errors/base.js' import type { Hash, Hex } from '../../../types/misc.js' @@ -32,7 +31,7 @@ import type { export type ToCoinbaseSmartAccountParameters = { address?: Address | undefined - client: Client + client: CoinbaseSmartAccountImplementation['client'] ownerIndex?: number | undefined owners: readonly (Address | OneOf)[] nonce?: bigint | undefined diff --git a/src/account-abstraction/accounts/implementations/toSimple7702SmartAccount.test.ts b/src/account-abstraction/accounts/implementations/toSimple7702SmartAccount.test.ts new file mode 100644 index 0000000000..9ce68f1e17 --- /dev/null +++ b/src/account-abstraction/accounts/implementations/toSimple7702SmartAccount.test.ts @@ -0,0 +1,363 @@ +import type { Address } from 'abitype' +import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest' + +import { anvilMainnet } from '../../../../test/src/anvil.js' +import { accounts, typedData } from '../../../../test/src/constants.js' +import { deploySimple7702Account_08 } from '../../../../test/src/utils.js' +import { privateKeyToAccount } from '../../../accounts/privateKeyToAccount.js' +import { + mine, + sendTransaction, + signAuthorization, + verifyMessage, + verifyTypedData, +} from '../../../actions/index.js' +import { zeroAddress } from '../../../constants/address.js' +import { toSimple7702SmartAccount } from './toSimple7702SmartAccount.js' + +const client = anvilMainnet.getClient({ account: true }) + +let implementation: Address +beforeAll(async () => { + const { implementationAddress: _implementation } = + await deploySimple7702Account_08() + implementation = _implementation +}) + +test('default', async () => { + const account = await toSimple7702SmartAccount({ + client, + owner: privateKeyToAccount(accounts[1].privateKey), + }) + + expect({ + ...account, + _internal: null, + abi: null, + entryPoint: null, + client: null, + factory: null, + }).toMatchInlineSnapshot(` + { + "_internal": null, + "abi": null, + "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "authorization": { + "account": { + "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "nonceManager": undefined, + "publicKey": "0x04ba5734d8f7091719471e7f7ed6b9df170dc70cc661ca05e688601ad984f068b0d67351e5f06073092499336ab0839ef8a521afd334e53807205fa2f08eec74f4", + "sign": [Function], + "signAuthorization": [Function], + "signMessage": [Function], + "signTransaction": [Function], + "signTypedData": [Function], + "source": "privateKey", + "type": "local", + }, + "address": "0xe6Cae83BdE06E4c305530e199D7217f42808555B", + }, + "client": null, + "decodeCalls": [Function], + "encodeCalls": [Function], + "entryPoint": null, + "factory": null, + "getAddress": [Function], + "getFactoryArgs": [Function], + "getNonce": [Function], + "getStubSignature": [Function], + "isDeployed": [Function], + "owner": { + "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "nonceManager": undefined, + "publicKey": "0x04ba5734d8f7091719471e7f7ed6b9df170dc70cc661ca05e688601ad984f068b0d67351e5f06073092499336ab0839ef8a521afd334e53807205fa2f08eec74f4", + "sign": [Function], + "signAuthorization": [Function], + "signMessage": [Function], + "signTransaction": [Function], + "signTypedData": [Function], + "source": "privateKey", + "type": "local", + }, + "signMessage": [Function], + "signTypedData": [Function], + "signUserOperation": [Function], + "type": "smart", + } + `) +}) + +describe('return value: getAddress', () => { + test('default', async () => { + const account = await toSimple7702SmartAccount({ + client, + implementation, + owner: privateKeyToAccount(accounts[1].privateKey), + }) + + const address = await account.getAddress() + expect(address).toMatchInlineSnapshot( + `"0x70997970C51812dc3A010C7d01b50e0d17dc79C8"`, + ) + }) +}) + +describe('return value: decodeCalls', () => { + test('single', async () => { + const account = await toSimple7702SmartAccount({ + client, + implementation, + owner: privateKeyToAccount(accounts[1].privateKey), + }) + + const calls = [ + { + to: '0x0000000000000000000000000000000000000000', + value: 69n, + data: '0xdeadbeef', + }, + ] as const + + const data = await account.encodeCalls(calls) + const decoded = await account.decodeCalls?.(data) + expect(decoded).toEqual(calls) + }) + + test('batch', async () => { + const account = await toSimple7702SmartAccount({ + client, + implementation, + owner: privateKeyToAccount(accounts[1].privateKey), + }) + + const calls = [ + { + data: '0x', + to: '0x0000000000000000000000000000000000000000', + value: 0n, + }, + { + data: '0x', + to: '0x0000000000000000000000000000000000000000', + value: 69n, + }, + { + to: '0x0000000000000000000000000000000000000000', + value: 69n, + data: '0xdeadbeef', + }, + ] as const + + const data = await account.encodeCalls(calls) + const decoded = await account.decodeCalls?.(data) + expect(decoded).toEqual(calls) + }) +}) + +describe('return value: encodeCalls', () => { + test('single', async () => { + const account = await toSimple7702SmartAccount({ + client, + implementation, + owner: privateKeyToAccount(accounts[1].privateKey), + }) + + const callData_1 = await account.encodeCalls([ + { to: '0x0000000000000000000000000000000000000000' }, + ]) + const callData_2 = await account.encodeCalls([ + { to: '0x0000000000000000000000000000000000000000', value: 69n }, + ]) + const callData_3 = await account.encodeCalls([ + { + to: '0x0000000000000000000000000000000000000000', + value: 69n, + data: '0xdeadbeef', + }, + ]) + + expect(callData_1).toMatchInlineSnapshot( + `"0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000"`, + ) + expect(callData_2).toMatchInlineSnapshot( + `"0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000"`, + ) + expect(callData_3).toMatchInlineSnapshot( + `"0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004deadbeef00000000000000000000000000000000000000000000000000000000"`, + ) + }) + + test('batch', async () => { + const account = await toSimple7702SmartAccount({ + client, + implementation, + owner: privateKeyToAccount(accounts[1].privateKey), + }) + + const callData = await account.encodeCalls([ + { to: '0x0000000000000000000000000000000000000000' }, + { to: '0x0000000000000000000000000000000000000000', value: 69n }, + { + to: '0x0000000000000000000000000000000000000000', + value: 69n, + data: '0xdeadbeef', + }, + ]) + + expect(callData).toMatchInlineSnapshot( + `"0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004deadbeef00000000000000000000000000000000000000000000000000000000"`, + ) + }) +}) + +describe('return value: getFactoryArgs', () => { + test('default', async () => { + const account = await toSimple7702SmartAccount({ + client, + implementation, + owner: privateKeyToAccount(accounts[1].privateKey), + }) + + const signature = await account.getFactoryArgs() + expect(signature).toMatchInlineSnapshot( + ` + { + "factory": "0x7702", + "factoryData": "0x", + } + `, + ) + }) +}) + +describe('return value: getSignature', () => { + test('default', async () => { + const account = await toSimple7702SmartAccount({ + client, + implementation, + owner: privateKeyToAccount(accounts[1].privateKey), + }) + + const signature = await account.getStubSignature() + expect(signature).toMatchInlineSnapshot( + `"0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c"`, + ) + }) +}) + +describe('return value: getNonce', () => { + beforeEach(() => { + vi.useFakeTimers() + vi.setSystemTime(new Date(Date.UTC(2023, 1, 1))) + return () => vi.useRealTimers() + }) + + test('default', async () => { + const account = await toSimple7702SmartAccount({ + client, + implementation, + owner: privateKeyToAccount(accounts[1].privateKey), + }) + + const nonce = await account.getNonce() + expect(nonce).toMatchInlineSnapshot('30902162761021348478818713600000n') + }) + + test('args: key', async () => { + const account = await toSimple7702SmartAccount({ + client, + implementation, + owner: privateKeyToAccount(accounts[1].privateKey), + }) + + const nonce = await account.getNonce({ key: 0n }) + expect(nonce).toMatchInlineSnapshot('0n') + }) +}) + +describe('return value: signMessage', () => { + test('default', async () => { + const account = await toSimple7702SmartAccount({ + client, + implementation, + owner: privateKeyToAccount(accounts[1].privateKey), + }) + + const authorization = await signAuthorization(client, { + address: implementation, + account: privateKeyToAccount(accounts[1].privateKey), + }) + + await sendTransaction(client, { + account: privateKeyToAccount(accounts[1].privateKey), + to: zeroAddress, + value: 0n, + data: '0x', + authorizationList: [authorization], + }) + + await mine(client, { + blocks: 1, + }) + + const signature = await account.signMessage({ + message: 'hello world', + }) + + const result = await verifyMessage(client, { + address: await account.getAddress(), + message: 'hello world', + signature, + }) + + expect(result).toBeTruthy() + }) +}) + +describe('return value: signTypedData', () => { + test('default', async () => { + const account = await toSimple7702SmartAccount({ + client, + implementation, + owner: privateKeyToAccount(accounts[1].privateKey), + }) + + const signature = await account.signTypedData({ + ...typedData.basic, + primaryType: 'Mail', + }) + + const result = await verifyTypedData(client, { + address: await account.getAddress(), + signature, + ...typedData.basic, + primaryType: 'Mail', + }) + expect(result).toBeTruthy() + }) +}) + +describe('return value: signUserOperation', () => { + test('default', async () => { + const account = await toSimple7702SmartAccount({ + client, + implementation, + owner: privateKeyToAccount(accounts[1].privateKey), + }) + + const signature = await account.signUserOperation({ + callData: '0xdeadbeef', + callGasLimit: 69n, + maxFeePerGas: 69n, + maxPriorityFeePerGas: 69n, + nonce: 0n, + preVerificationGas: 69n, + signature: '0xdeadbeef', + verificationGasLimit: 69n, + }) + + expect(signature).toMatchInlineSnapshot( + `"0xf29d9b44ec09b8542328c9f75a6e36976ac3507b43fa2d86f06b5157e60db7207bafccde8e7a308019dce8b540642e6134a5aebd69bfacb1778928c7f7c774711c"`, + ) + }) +}) diff --git a/src/account-abstraction/accounts/implementations/toSimple7702SmartAccount.ts b/src/account-abstraction/accounts/implementations/toSimple7702SmartAccount.ts new file mode 100644 index 0000000000..3efdab0956 --- /dev/null +++ b/src/account-abstraction/accounts/implementations/toSimple7702SmartAccount.ts @@ -0,0 +1,382 @@ +import type { Address, TypedData } from 'abitype' + +import type { PrivateKeyAccount } from '../../../accounts/types.js' +import { entryPoint08Address } from '../../../constants/address.js' +import { BaseError } from '../../../errors/base.js' +import type { TypedDataDefinition } from '../../../types/typedData.js' +import type { Prettify } from '../../../types/utils.js' +import { decodeFunctionData } from '../../../utils/abi/decodeFunctionData.js' +import { encodeFunctionData } from '../../../utils/abi/encodeFunctionData.js' +import { concat, encodePacked, numberToHex, pad } from '../../../utils/index.js' +import { entryPoint08Abi } from '../../constants/abis.js' +import { toSmartAccount } from '../toSmartAccount.js' +import type { SmartAccount, SmartAccountImplementation } from '../types.js' + +export type ToSimple7702SmartAccountParameters = { + client: Simple7702SmartAccountImplementation['client'] + implementation?: Address | undefined + getNonce?: SmartAccountImplementation['getNonce'] | undefined + owner: PrivateKeyAccount +} + +export type ToSimple7702SmartAccountReturnType = Prettify< + SmartAccount +> + +export type Simple7702SmartAccountImplementation = SmartAccountImplementation< + typeof entryPoint08Abi, + '0.8', + { abi: typeof abi; owner: PrivateKeyAccount }, + true +> + +/** + * @description Create a Simple7702 Smart Account. + * + * @param parameters - {@link ToSimple7702SmartAccountParameters} + * @returns Simple7702 Smart Account. {@link ToSimple7702SmartAccountReturnType} + * + * @example + * import { toSimple7702SmartAccount } from 'viem/account-abstraction' + * import { client } from './client.js' + * + * const implementation = toSimple7702SmartAccount({ + * client, + * owner: '0x...', + * }) + */ +export async function toSimple7702SmartAccount( + parameters: ToSimple7702SmartAccountParameters, +): Promise { + const { + client, + implementation = '0xe6Cae83BdE06E4c305530e199D7217f42808555B', + getNonce, + owner, + } = parameters + + const entryPoint = { + abi: entryPoint08Abi, + address: entryPoint08Address, + version: '0.8', + } as const + + return toSmartAccount({ + authorization: { account: owner, address: implementation }, + abi, + client, + extend: { abi, owner }, // not removing abi from here as this will be a breaking change + entryPoint, + getNonce, + + async decodeCalls(data) { + const result = decodeFunctionData({ + abi, + data, + }) + + if (result.functionName === 'execute') + return [ + { to: result.args[0], value: result.args[1], data: result.args[2] }, + ] + if (result.functionName === 'executeBatch') + return result.args[0].map((arg) => ({ + to: arg.target, + value: arg.value, + data: arg.data, + })) + throw new BaseError(`unable to decode calls for "${result.functionName}"`) + }, + + async encodeCalls(calls) { + if (calls.length === 1) + return encodeFunctionData({ + abi, + functionName: 'execute', + args: [calls[0].to, calls[0].value ?? 0n, calls[0].data ?? '0x'], + }) + return encodeFunctionData({ + abi, + functionName: 'executeBatch', + args: [ + calls.map((call) => ({ + data: call.data ?? '0x', + target: call.to, + value: call.value ?? 0n, + })), + ], + }) + }, + + async getAddress() { + return owner.address + }, + + async getFactoryArgs() { + return { factory: '0x7702', factoryData: '0x' } + }, + + async getStubSignature() { + return '0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c' + }, + + async signMessage(parameters) { + const { message } = parameters + return await owner.signMessage({ message }) + }, + + async signTypedData(parameters) { + const { domain, types, primaryType, message } = + parameters as TypedDataDefinition + return await owner.signTypedData({ + domain, + message, + primaryType, + types, + }) + }, + + async signUserOperation(parameters) { + const { chainId = client.chain!.id, ...userOperation } = parameters + + const address = await this.getAddress() + + const isEip7702 = + userOperation.factory && + userOperation.factory === '0x7702' && + userOperation.authorization + + const delegation = isEip7702 + ? userOperation.authorization?.address + : undefined + + const initCode = delegation + ? userOperation.factoryData + ? encodePacked( + ['address', 'bytes'], + [delegation, userOperation.factoryData], + ) + : encodePacked(['address'], [delegation]) + : userOperation.factory && userOperation.factoryData + ? concat([userOperation.factory, userOperation.factoryData]) + : '0x' + + const accountGasLimits = concat([ + pad(numberToHex(userOperation.verificationGasLimit), { size: 16 }), + pad(numberToHex(userOperation.callGasLimit), { size: 16 }), + ]) + const gasFees = concat([ + pad(numberToHex(userOperation.maxPriorityFeePerGas), { size: 16 }), + pad(numberToHex(userOperation.maxFeePerGas), { size: 16 }), + ]) + const paymasterAndData = userOperation.paymaster + ? concat([ + userOperation.paymaster, + pad(numberToHex(userOperation.paymasterVerificationGasLimit || 0), { + size: 16, + }), + pad(numberToHex(userOperation.paymasterPostOpGasLimit || 0), { + size: 16, + }), + userOperation.paymasterData || '0x', + ]) + : '0x' + + const signature = await owner.signTypedData({ + types: { + PackedUserOperation: [ + { type: 'address', name: 'sender' }, + { type: 'uint256', name: 'nonce' }, + { type: 'bytes', name: 'initCode' }, + { type: 'bytes', name: 'callData' }, + { type: 'bytes32', name: 'accountGasLimits' }, + { type: 'uint256', name: 'preVerificationGas' }, + { type: 'bytes32', name: 'gasFees' }, + { type: 'bytes', name: 'paymasterAndData' }, + ], + }, + primaryType: 'PackedUserOperation', + domain: { + name: 'ERC4337', + version: '1', + chainId, + verifyingContract: entryPoint.address, + }, + message: { + sender: address, + nonce: userOperation.nonce, + initCode, + callData: userOperation.callData, + accountGasLimits, + preVerificationGas: userOperation.preVerificationGas, + gasFees, + paymasterAndData, + }, + }) + return signature + }, + }) +} + +///////////////////////////////////////////////////////////////////////////////////////////// +// Constants + +const abi = [ + { inputs: [], name: 'ECDSAInvalidSignature', type: 'error' }, + { + inputs: [{ internalType: 'uint256', name: 'length', type: 'uint256' }], + name: 'ECDSAInvalidSignatureLength', + type: 'error', + }, + { + inputs: [{ internalType: 'bytes32', name: 's', type: 'bytes32' }], + name: 'ECDSAInvalidSignatureS', + type: 'error', + }, + { + inputs: [ + { internalType: 'uint256', name: 'index', type: 'uint256' }, + { internalType: 'bytes', name: 'error', type: 'bytes' }, + ], + name: 'ExecuteError', + type: 'error', + }, + { stateMutability: 'payable', type: 'fallback' }, + { + inputs: [], + name: 'entryPoint', + outputs: [ + { internalType: 'contract IEntryPoint', name: '', type: 'address' }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'target', type: 'address' }, + { internalType: 'uint256', name: 'value', type: 'uint256' }, + { internalType: 'bytes', name: 'data', type: 'bytes' }, + ], + name: 'execute', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'target', type: 'address' }, + { internalType: 'uint256', name: 'value', type: 'uint256' }, + { internalType: 'bytes', name: 'data', type: 'bytes' }, + ], + internalType: 'struct BaseAccount.Call[]', + name: 'calls', + type: 'tuple[]', + }, + ], + name: 'executeBatch', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'getNonce', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'bytes32', name: 'hash', type: 'bytes32' }, + { internalType: 'bytes', name: 'signature', type: 'bytes' }, + ], + name: 'isValidSignature', + outputs: [{ internalType: 'bytes4', name: 'magicValue', type: 'bytes4' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: '', type: 'address' }, + { internalType: 'address', name: '', type: 'address' }, + { internalType: 'uint256[]', name: '', type: 'uint256[]' }, + { internalType: 'uint256[]', name: '', type: 'uint256[]' }, + { internalType: 'bytes', name: '', type: 'bytes' }, + ], + name: 'onERC1155BatchReceived', + outputs: [{ internalType: 'bytes4', name: '', type: 'bytes4' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: '', type: 'address' }, + { internalType: 'address', name: '', type: 'address' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'bytes', name: '', type: 'bytes' }, + ], + name: 'onERC1155Received', + outputs: [{ internalType: 'bytes4', name: '', type: 'bytes4' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: '', type: 'address' }, + { internalType: 'address', name: '', type: 'address' }, + { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'bytes', name: '', type: 'bytes' }, + ], + name: 'onERC721Received', + outputs: [{ internalType: 'bytes4', name: '', type: 'bytes4' }], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes4', name: 'id', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'sender', type: 'address' }, + { internalType: 'uint256', name: 'nonce', type: 'uint256' }, + { internalType: 'bytes', name: 'initCode', type: 'bytes' }, + { internalType: 'bytes', name: 'callData', type: 'bytes' }, + { + internalType: 'bytes32', + name: 'accountGasLimits', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'preVerificationGas', + type: 'uint256', + }, + { internalType: 'bytes32', name: 'gasFees', type: 'bytes32' }, + { internalType: 'bytes', name: 'paymasterAndData', type: 'bytes' }, + { internalType: 'bytes', name: 'signature', type: 'bytes' }, + ], + internalType: 'struct PackedUserOperation', + name: 'userOp', + type: 'tuple', + }, + { internalType: 'bytes32', name: 'userOpHash', type: 'bytes32' }, + { internalType: 'uint256', name: 'missingAccountFunds', type: 'uint256' }, + ], + name: 'validateUserOp', + outputs: [ + { internalType: 'uint256', name: 'validationData', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { stateMutability: 'payable', type: 'receive' }, +] as const diff --git a/src/account-abstraction/accounts/implementations/toSoladySmartAccount.ts b/src/account-abstraction/accounts/implementations/toSoladySmartAccount.ts index 8a54851789..aa32f7eb4b 100644 --- a/src/account-abstraction/accounts/implementations/toSoladySmartAccount.ts +++ b/src/account-abstraction/accounts/implementations/toSoladySmartAccount.ts @@ -3,7 +3,6 @@ import type { Abi, Address, TypedData } from 'abitype' import { parseAccount } from '../../../accounts/utils/parseAccount.js' import { readContract } from '../../../actions/public/readContract.js' import { signMessage as signMessage_ } from '../../../actions/wallet/signMessage.js' -import type { Client } from '../../../clients/createClient.js' import { entryPoint07Address } from '../../../constants/address.js' import { BaseError } from '../../../errors/base.js' import { signMessage } from '../../../experimental/erc7739/actions/signMessage.js' @@ -27,7 +26,7 @@ export type ToSoladySmartAccountParameters< entryPointVersion extends EntryPointVersion = EntryPointVersion, > = { address?: Address | undefined - client: Client + client: SoladySmartAccountImplementation['client'] entryPoint?: | { abi: entryPointAbi diff --git a/src/account-abstraction/accounts/toSmartAccount.ts b/src/account-abstraction/accounts/toSmartAccount.ts index e3ca0c2a0f..a4537229dd 100644 --- a/src/account-abstraction/accounts/toSmartAccount.ts +++ b/src/account-abstraction/accounts/toSmartAccount.ts @@ -115,7 +115,7 @@ export async function toSmartAccount< this.getFactoryArgs(), implementation.signMessage(parameters), ]) - if (factory && factoryData) + if (factory && factoryData && factory !== '0x7702') return serializeErc6492Signature({ address: factory, data: factoryData, @@ -128,7 +128,7 @@ export async function toSmartAccount< this.getFactoryArgs(), implementation.signTypedData(parameters), ]) - if (factory && factoryData) + if (factory && factoryData && factory !== '0x7702') return serializeErc6492Signature({ address: factory, data: factoryData, diff --git a/src/account-abstraction/accounts/types.ts b/src/account-abstraction/accounts/types.ts index 7005de9fdc..7d4348782a 100644 --- a/src/account-abstraction/accounts/types.ts +++ b/src/account-abstraction/accounts/types.ts @@ -1,7 +1,14 @@ import type { Abi, Address, TypedData } from 'abitype' import type * as WebAuthnP256 from 'ox/WebAuthnP256' +import type { + JsonRpcAccount, + LocalAccount, + PrivateKeyAccount, +} from '../../accounts/types.js' import type { Client } from '../../clients/createClient.js' +import type { Transport } from '../../clients/transports/createTransport.js' +import type { Chain } from '../../types/chain.js' import type { Hash, Hex, SignableMessage } from '../../types/misc.js' import type { TypedDataDefinition } from '../../types/typedData.js' import type { Assign, ExactPartial, UnionPartialBy } from '../../types/utils.js' @@ -23,9 +30,14 @@ export type SmartAccountImplementation< entryPointAbi extends Abi | readonly unknown[] = Abi, entryPointVersion extends EntryPointVersion = EntryPointVersion, extend extends object = object, + eip7702 extends boolean = boolean, > = { /** Client used to retrieve Smart Account data, and perform signing (if owner is a JSON-RPC Account). */ - client: Client + client: Client< + Transport, + Chain | undefined, + JsonRpcAccount | LocalAccount | undefined + > /** Compatible EntryPoint of the Smart Account. */ entryPoint: { /** Compatible EntryPoint ABI. */ @@ -189,7 +201,19 @@ export type SmartAccountImplementation< | undefined } | undefined -} +} & (eip7702 extends true + ? { + /** EIP-7702 authorization properties, if applicable. */ + authorization: { + /** EOA to delegate to. */ + account: PrivateKeyAccount + /** Delegation address. */ + address: Address + } + } + : { + authorization?: undefined + }) export type SmartAccount< implementation extends diff --git a/src/account-abstraction/actions/bundler/estimateUserOperationGas.test.ts b/src/account-abstraction/actions/bundler/estimateUserOperationGas.test.ts index c11dd2f51a..27e506ed51 100644 --- a/src/account-abstraction/actions/bundler/estimateUserOperationGas.test.ts +++ b/src/account-abstraction/actions/bundler/estimateUserOperationGas.test.ts @@ -5,6 +5,7 @@ import { createVerifyingPaymasterServer, getSmartAccounts_06, getSmartAccounts_07, + getSmartAccounts_08, getVerifyingPaymaster_07, } from '../../../../test/src/account-abstraction.js' import { anvilMainnet } from '../../../../test/src/anvil.js' @@ -35,6 +36,531 @@ beforeEach(async () => { return () => vi.useRealTimers() }) +describe('entryPointVersion: 0.8', async () => { + const [account] = await getSmartAccounts_08() + + test('default', async () => { + const gas = await estimateUserOperationGas(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + to: wagmiContractConfig.address, + abi: wagmiContractConfig.abi, + functionName: 'mint', + }, + ], + ...fees, + }) + expect(gas.callGasLimit).toBeGreaterThanOrEqual(70000n) + expect(gas.preVerificationGas).toBeGreaterThanOrEqual(53000n) + expect(gas.verificationGasLimit).toBeGreaterThanOrEqual(95000n) + }) + + test.todo('args: paymaster (client)') + + test.todo('behavior: client.paymaster (client)') + + test('behavior: prepared user operation', async () => { + const userOp = await prepareUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + ], + ...fees, + }) + + const request = { + ...userOp, + account: undefined, + } as const + + expectTypeOf(request).toMatchTypeOf() + + const gas = await estimateUserOperationGas(bundlerClient, { + ...request, + entryPointAddress: account.entryPoint?.address, + }) + + expect(gas.callGasLimit).toBeGreaterThanOrEqual(16000n) + expect(gas.preVerificationGas).toBeGreaterThanOrEqual(51000n) + expect(gas.verificationGasLimit).toBeGreaterThanOrEqual(95000n) + expect(gas.paymasterVerificationGasLimit).toBeGreaterThanOrEqual(0n) + expect(gas.paymasterPostOpGasLimit).toBeGreaterThanOrEqual(0n) + }) + + test('error: insufficient funds', async () => { + await expect(async () => + estimateUserOperationGas(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1000000'), + }, + ], + ...fees, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserOperationExecutionError: Execution reverted with reason: UserOperation reverted during simulation with reason: 0x. + + Request Arguments: + callData: 0xb61d27f6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d3c21bcecceda100000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000 + factory: 0x7702 + factoryData: 0x + maxFeePerGas: 15 gwei + maxPriorityFeePerGas: 2 gwei + nonce: 30902162761058241966966132703232 + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c + + Details: UserOperation reverted during simulation with reason: 0x + Version: viem@x.y.z] + `) + }) + + test('error: contract revert', async () => { + await expect(async () => + estimateUserOperationGas(bundlerClient, { + account, + calls: [ + { + to: wagmiContractConfig.address, + abi: wagmiContractConfig.abi, + functionName: 'mint', + args: [420n], + }, + ], + ...fees, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserOperationExecutionError: The contract function "mint" reverted with the following reason: + Token ID is taken + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: mint(uint256 tokenId) + args: (420) + + Request Arguments: + callData: 0xb61d27f6000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a0712d6800000000000000000000000000000000000000000000000000000000000001a400000000000000000000000000000000000000000000000000000000 + factory: 0x7702 + factoryData: 0x + maxFeePerGas: 15 gwei + maxPriorityFeePerGas: 2 gwei + nonce: 30902162761076688711039842254848 + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c + + Version: viem@x.y.z] + `) + }) + + test('error: contract revert', async () => { + await expect(async () => + estimateUserOperationGas(bundlerClient, { + account, + calls: [ + { + to: wagmiContractConfig.address, + abi: wagmiContractConfig.abi, + functionName: 'approve', + args: ['0x0000000000000000000000000000000000000000', 420n], + }, + ], + ...fees, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserOperationExecutionError: The contract function "approve" reverted with the following reason: + ERC721: approve caller is not owner nor approved for all + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: approve(address to, uint256 tokenId) + args: (0x0000000000000000000000000000000000000000, 420) + + Request Arguments: + callData: 0xb61d27f6000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a400000000000000000000000000000000000000000000000000000000 + factory: 0x7702 + factoryData: 0x + maxFeePerGas: 15 gwei + maxPriorityFeePerGas: 2 gwei + nonce: 30902162761095135455113551806464 + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c + + Version: viem@x.y.z] + `) + }) + + test('error: contract revert (multiple calls)', async () => { + await expect(async () => + estimateUserOperationGas(bundlerClient, { + account, + calls: [ + { + to: wagmiContractConfig.address, + abi: wagmiContractConfig.abi, + functionName: 'mint', + }, + { + to: wagmiContractConfig.address, + abi: wagmiContractConfig.abi, + functionName: 'approve', + args: ['0x0000000000000000000000000000000000000000', 420n], + }, + ], + ...fees, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserOperationExecutionError: The contract function "mint | approve" reverted with the following signature: + 0x5a154675 + + Unable to decode signature "0x5a154675" as it was not found on the provided ABI. + Make sure you are using the correct ABI and that the error exists on it. + You can look up the decoded signature here: https://openchain.xyz/signatures?query=0x5a154675. + + + Request Arguments: + callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a400000000000000000000000000000000000000000000000000000000 + factory: 0x7702 + factoryData: 0x + maxFeePerGas: 15 gwei + maxPriorityFeePerGas: 2 gwei + nonce: 30902162761113582199187261358080 + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c + + Docs: https://viem.sh/docs/contract/decodeErrorResult + Version: viem@x.y.z] + `) + }) + + test('error: function does not exist', async () => { + await expect(async () => + estimateUserOperationGas(bundlerClient, { + account, + calls: [ + { + // 7702 implementation has a fallback so sending it to uniswap factory instead + to: '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f', + abi: wagmiContractConfig.abi, + functionName: 'mint', + args: [420n], + }, + ], + ...fees, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserOperationExecutionError: The contract function "mint" returned no data ("0x"). + + This could be due to any of the following: + - The contract does not have the function "mint", + - The parameters passed to the contract function may be invalid, or + - The address is not a contract. + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: mint(uint256 tokenId) + args: (420) + + Request Arguments: + callData: 0xb61d27f60000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a0712d6800000000000000000000000000000000000000000000000000000000000001a400000000000000000000000000000000000000000000000000000000 + factory: 0x7702 + factoryData: 0x + maxFeePerGas: 15 gwei + maxPriorityFeePerGas: 2 gwei + nonce: 30902162761132028943260970909696 + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c + + Version: viem@x.y.z] + `) + }) + + test('error: generic revert', async () => { + const { contractAddress } = await deployErrorExample() + + await expect(async () => + estimateUserOperationGas(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + abi: ErrorsExample.abi, + to: contractAddress!, + functionName: 'revertWrite', + }, + ], + ...fees, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserOperationExecutionError: The contract function "revertWrite" reverted with the following signature: + 0x5a154675 + + Unable to decode signature "0x5a154675" as it was not found on the provided ABI. + Make sure you are using the correct ABI and that the error exists on it. + You can look up the decoded signature here: https://openchain.xyz/signatures?query=0x5a154675. + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: revertWrite() + + Request Arguments: + callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000831c6c334f8ddee62246a5c81b82c8e18008b38f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004940b880200000000000000000000000000000000000000000000000000000000 + factory: 0x7702 + factoryData: 0x + maxFeePerGas: 15 gwei + maxPriorityFeePerGas: 2 gwei + nonce: 30902162761150475687334680461312 + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c + + Docs: https://viem.sh/docs/contract/decodeErrorResult + Version: viem@x.y.z] + `) + }) + + test('error: assert', async () => { + const { contractAddress } = await deployErrorExample() + + await expect(async () => + estimateUserOperationGas(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + abi: ErrorsExample.abi, + to: contractAddress!, + functionName: 'assertWrite', + }, + ], + ...fees, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserOperationExecutionError: The contract function "assertWrite" reverted with the following signature: + 0x5a154675 + + Unable to decode signature "0x5a154675" as it was not found on the provided ABI. + Make sure you are using the correct ABI and that the error exists on it. + You can look up the decoded signature here: https://openchain.xyz/signatures?query=0x5a154675. + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: assertWrite() + + Request Arguments: + callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f47e3b0a1952a81f1afc41172762cb7ce87001330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000040469615200000000000000000000000000000000000000000000000000000000 + factory: 0x7702 + factoryData: 0x + maxFeePerGas: 15 gwei + maxPriorityFeePerGas: 2 gwei + nonce: 30902162761168922431408390012928 + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c + + Docs: https://viem.sh/docs/contract/decodeErrorResult + Version: viem@x.y.z] + `) + }) + + test('error: overflow', async () => { + const { contractAddress } = await deployErrorExample() + + await expect(async () => + estimateUserOperationGas(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + abi: ErrorsExample.abi, + to: contractAddress!, + functionName: 'overflowWrite', + }, + ], + ...fees, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserOperationExecutionError: The contract function "overflowWrite" reverted with the following signature: + 0x5a154675 + + Unable to decode signature "0x5a154675" as it was not found on the provided ABI. + Make sure you are using the correct ABI and that the error exists on it. + You can look up the decoded signature here: https://openchain.xyz/signatures?query=0x5a154675. + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: overflowWrite() + + Request Arguments: + callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c63db9682ff11707cadbd72bf1a0354a7fef143b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004d44de86600000000000000000000000000000000000000000000000000000000 + factory: 0x7702 + factoryData: 0x + maxFeePerGas: 15 gwei + maxPriorityFeePerGas: 2 gwei + nonce: 30902162761187369175482099564544 + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c + + Docs: https://viem.sh/docs/contract/decodeErrorResult + Version: viem@x.y.z] + `) + }) + + test('error: divide by zero', async () => { + const { contractAddress } = await deployErrorExample() + + await expect(async () => + estimateUserOperationGas(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + abi: ErrorsExample.abi, + to: contractAddress!, + functionName: 'divideByZeroWrite', + }, + ], + ...fees, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserOperationExecutionError: The contract function "divideByZeroWrite" reverted with the following signature: + 0x5a154675 + + Unable to decode signature "0x5a154675" as it was not found on the provided ABI. + Make sure you are using the correct ABI and that the error exists on it. + You can look up the decoded signature here: https://openchain.xyz/signatures?query=0x5a154675. + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: divideByZeroWrite() + + Request Arguments: + callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fc3983de3f7cbe1ba01084469779470ad0bbeffa000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004c66cf13300000000000000000000000000000000000000000000000000000000 + factory: 0x7702 + factoryData: 0x + maxFeePerGas: 15 gwei + maxPriorityFeePerGas: 2 gwei + nonce: 30902162761205815919555809116160 + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c + + Docs: https://viem.sh/docs/contract/decodeErrorResult + Version: viem@x.y.z] + `) + }) + + test('error: custom error', async () => { + const { contractAddress } = await deployErrorExample() + + await expect(async () => + estimateUserOperationGas(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + abi: ErrorsExample.abi, + to: contractAddress!, + functionName: 'simpleCustomWrite', + }, + ], + ...fees, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserOperationExecutionError: The contract function "simpleCustomWrite" reverted with the following signature: + 0x5a154675 + + Unable to decode signature "0x5a154675" as it was not found on the provided ABI. + Make sure you are using the correct ABI and that the error exists on it. + You can look up the decoded signature here: https://openchain.xyz/signatures?query=0x5a154675. + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: simpleCustomWrite() + + Request Arguments: + callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8b1d4d0a2dd9dd53200a4c6783a69c15e3a25f4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004a997732e00000000000000000000000000000000000000000000000000000000 + factory: 0x7702 + factoryData: 0x + maxFeePerGas: 15 gwei + maxPriorityFeePerGas: 2 gwei + nonce: 30902162761224262663629518667776 + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c + + Docs: https://viem.sh/docs/contract/decodeErrorResult + Version: viem@x.y.z] + `) + }) + + test('error: custom error', async () => { + const { contractAddress } = await deployErrorExample() + + await expect(async () => + estimateUserOperationGas(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + abi: ErrorsExample.abi, + to: contractAddress!, + functionName: 'complexCustomWrite', + }, + ], + ...fees, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserOperationExecutionError: The contract function "complexCustomWrite" reverted with the following signature: + 0x5a154675 + + Unable to decode signature "0x5a154675" as it was not found on the provided ABI. + Make sure you are using the correct ABI and that the error exists on it. + You can look up the decoded signature here: https://openchain.xyz/signatures?query=0x5a154675. + + Contract Call: + address: 0x0000000000000000000000000000000000000000 + function: complexCustomWrite() + + Request Arguments: + callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d6b8eb34413f07a1a67a469345cfea6633efd58d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000048de18b9100000000000000000000000000000000000000000000000000000000 + factory: 0x7702 + factoryData: 0x + maxFeePerGas: 15 gwei + maxPriorityFeePerGas: 2 gwei + nonce: 30902162761242709407703228219392 + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c + + Docs: https://viem.sh/docs/contract/decodeErrorResult + Version: viem@x.y.z] + `) + }) +}) + describe('entryPointVersion: 0.7', async () => { const [account, account_2, account_3] = await getSmartAccounts_07() @@ -127,17 +653,19 @@ describe('entryPointVersion: 0.7', async () => { }) test('behavior: prepared user operation', async () => { + const userOp = await prepareUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + ], + ...fees, + }) + const request = { - ...(await prepareUserOperation(bundlerClient, { - account, - calls: [ - { - to: '0x0000000000000000000000000000000000000000', - value: parseEther('1'), - }, - ], - ...fees, - })), + ...userOp, account: undefined, } as const @@ -172,12 +700,12 @@ describe('entryPointVersion: 0.7', async () => { Request Arguments: callData: 0xb61d27f6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d3c21bcecceda100000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000 - factory: 0xf102f0173707c6726543d65fa38025eb72026c37 + factory: 0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688 factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei nonce: 30902162761095135455113551806464 - sender: 0x7473Fcb76634352e4CbA37CFad3783B059792b44 + sender: 0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c Details: UserOperation reverted during simulation with reason: 0x @@ -210,12 +738,12 @@ describe('entryPointVersion: 0.7', async () => { Request Arguments: callData: 0xb61d27f6000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a0712d6800000000000000000000000000000000000000000000000000000000000001a400000000000000000000000000000000000000000000000000000000 - factory: 0xf102f0173707c6726543d65fa38025eb72026c37 + factory: 0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688 factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei nonce: 30902162761113582199187261358080 - sender: 0x7473Fcb76634352e4CbA37CFad3783B059792b44 + sender: 0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c Version: viem@x.y.z] @@ -247,12 +775,12 @@ describe('entryPointVersion: 0.7', async () => { Request Arguments: callData: 0xb61d27f6000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a400000000000000000000000000000000000000000000000000000000 - factory: 0xf102f0173707c6726543d65fa38025eb72026c37 + factory: 0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688 factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei nonce: 30902162761132028943260970909696 - sender: 0x7473Fcb76634352e4CbA37CFad3783B059792b44 + sender: 0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c Version: viem@x.y.z] @@ -285,12 +813,12 @@ describe('entryPointVersion: 0.7', async () => { Request Arguments: callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a400000000000000000000000000000000000000000000000000000000 - factory: 0xf102f0173707c6726543d65fa38025eb72026c37 + factory: 0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688 factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei nonce: 30902162761150475687334680461312 - sender: 0x7473Fcb76634352e4CbA37CFad3783B059792b44 + sender: 0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c Version: viem@x.y.z] @@ -325,13 +853,13 @@ describe('entryPointVersion: 0.7', async () => { args: (420) Request Arguments: - callData: 0xb61d27f60000000000000000000000007473fcb76634352e4cba37cfad3783b059792b44000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a0712d6800000000000000000000000000000000000000000000000000000000000001a400000000000000000000000000000000000000000000000000000000 - factory: 0xf102f0173707c6726543d65fa38025eb72026c37 + callData: 0xb61d27f6000000000000000000000000f2f83eb89c48abd7ad93ba42c3ce904895337cea000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024a0712d6800000000000000000000000000000000000000000000000000000000000001a400000000000000000000000000000000000000000000000000000000 + factory: 0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688 factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei nonce: 30902162761168922431408390012928 - sender: 0x7473Fcb76634352e4CbA37CFad3783B059792b44 + sender: 0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c Version: viem@x.y.z] @@ -366,13 +894,13 @@ describe('entryPointVersion: 0.7', async () => { function: revertWrite() Request Arguments: - callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f47e3b0a1952a81f1afc41172762cb7ce8700133000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004940b880200000000000000000000000000000000000000000000000000000000 - factory: 0xf102f0173707c6726543d65fa38025eb72026c37 + callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000453439300b6c5c645737324b990f2d51137027bc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004940b880200000000000000000000000000000000000000000000000000000000 + factory: 0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688 factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei nonce: 30902162761187369175482099564544 - sender: 0x7473Fcb76634352e4CbA37CFad3783B059792b44 + sender: 0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c Version: viem@x.y.z] @@ -407,13 +935,13 @@ describe('entryPointVersion: 0.7', async () => { function: assertWrite() Request Arguments: - callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c63db9682ff11707cadbd72bf1a0354a7fef143b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000040469615200000000000000000000000000000000000000000000000000000000 - factory: 0xf102f0173707c6726543d65fa38025eb72026c37 + callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007306a649b451ae08781108445425bd4e8acf1e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000040469615200000000000000000000000000000000000000000000000000000000 + factory: 0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688 factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei nonce: 30902162761205815919555809116160 - sender: 0x7473Fcb76634352e4CbA37CFad3783B059792b44 + sender: 0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c Version: viem@x.y.z] @@ -448,13 +976,13 @@ describe('entryPointVersion: 0.7', async () => { function: overflowWrite() Request Arguments: - callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fc3983de3f7cbe1ba01084469779470ad0bbeffa000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004d44de86600000000000000000000000000000000000000000000000000000000 - factory: 0xf102f0173707c6726543d65fa38025eb72026c37 + callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f4c5c29b14f0237131f7510a51684c8191f98e06000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004d44de86600000000000000000000000000000000000000000000000000000000 + factory: 0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688 factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei nonce: 30902162761224262663629518667776 - sender: 0x7473Fcb76634352e4CbA37CFad3783B059792b44 + sender: 0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c Version: viem@x.y.z] @@ -489,13 +1017,13 @@ describe('entryPointVersion: 0.7', async () => { function: divideByZeroWrite() Request Arguments: - callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f8b1d4d0a2dd9dd53200a4c6783a69c15e3a25f4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004c66cf13300000000000000000000000000000000000000000000000000000000 - factory: 0xf102f0173707c6726543d65fa38025eb72026c37 + callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000934a389cabfb84cdb3f0260b2a4fd575b8b345a3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004c66cf13300000000000000000000000000000000000000000000000000000000 + factory: 0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688 factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei nonce: 30902162761242709407703228219392 - sender: 0x7473Fcb76634352e4CbA37CFad3783B059792b44 + sender: 0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c Version: viem@x.y.z] @@ -532,13 +1060,13 @@ describe('entryPointVersion: 0.7', async () => { function: simpleCustomWrite() Request Arguments: - callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d6b8eb34413f07a1a67a469345cfea6633efd58d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004a997732e00000000000000000000000000000000000000000000000000000000 - factory: 0xf102f0173707c6726543d65fa38025eb72026c37 + callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c91b651f770ed996a223a16da9ccd6f7df56c987000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004a997732e00000000000000000000000000000000000000000000000000000000 + factory: 0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688 factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei nonce: 30902162761261156151776937771008 - sender: 0x7473Fcb76634352e4CbA37CFad3783B059792b44 + sender: 0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c Version: viem@x.y.z] @@ -575,13 +1103,13 @@ describe('entryPointVersion: 0.7', async () => { function: complexCustomWrite() Request Arguments: - callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009cc87998ba85d81e017e6b7662ac00ee2ab8fe130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000048de18b9100000000000000000000000000000000000000000000000000000000 - factory: 0xf102f0173707c6726543d65fa38025eb72026c37 + callData: 0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b90acf57c3bfe8e0e8215defc282b5f48b3edc740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000048de18b9100000000000000000000000000000000000000000000000000000000 + factory: 0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688 factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei nonce: 30902162761279602895850647322624 - sender: 0x7473Fcb76634352e4CbA37CFad3783B059792b44 + sender: 0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c Version: viem@x.y.z] @@ -619,12 +1147,12 @@ describe('entryPointVersion: 0.7', async () => { Request Arguments: callData: 0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000 - factory: 0xf102f0173707c6726543d65fa38025eb72026c37 + factory: 0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688 factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000001 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei nonce: 30902162761021348478818713600000 - sender: 0xc1b7F8B47312df97bdd97A6A4e322335F684A485 + sender: 0xC6B426A3272a812dD1B3EDB601447bbAA8C1294C signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c Details: UserOperation reverted during simulation with reason: AA10 sender already constructed @@ -659,7 +1187,7 @@ describe('entryPointVersion: 0.7', async () => { maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei nonce: 30902162761298049639924356874240 - sender: 0x7473Fcb76634352e4CbA37CFad3783B059792b44 + sender: 0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c Details: UserOperation reverted during simulation with reason: AA13 initCode failed or OOG @@ -695,18 +1223,18 @@ describe('entryPointVersion: 0.7', async () => { This could arise when: Smart Account initialization implementation does not return a sender address - factory: 0xf102f0173707c6726543d65fa38025eb72026c37 + factory: 0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688 factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000002 - sender: 0x7473Fcb76634352e4CbA37CFad3783B059792b44 + sender: 0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea Request Arguments: callData: 0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000 - factory: 0xf102f0173707c6726543d65fa38025eb72026c37 + factory: 0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688 factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000002 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei nonce: 30902162761316496383998066425856 - sender: 0x7473Fcb76634352e4CbA37CFad3783B059792b44 + sender: 0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c Details: UserOperation reverted during simulation with reason: AA14 initCode must return sender @@ -731,7 +1259,7 @@ describe('entryPointVersion: 0.6', async () => { }) expect(gas.callGasLimit).toBeGreaterThanOrEqual(80000n) expect(gas.preVerificationGas).toBeGreaterThanOrEqual(55000n) - expect(gas.verificationGasLimit).toBeGreaterThanOrEqual(258000n) + expect(gas.verificationGasLimit).toBeGreaterThanOrEqual(256000n) }) test('behavior: prepared user operation', async () => { @@ -757,7 +1285,7 @@ describe('entryPointVersion: 0.6', async () => { }) expect(gas.callGasLimit).toBeGreaterThanOrEqual(80000n) expect(gas.preVerificationGas).toBeGreaterThanOrEqual(55000n) - expect(gas.verificationGasLimit).toBeGreaterThanOrEqual(258000n) + expect(gas.verificationGasLimit).toBeGreaterThanOrEqual(256000n) }) test('error: aa13', async () => { @@ -785,7 +1313,7 @@ describe('entryPointVersion: 0.6', async () => { maxPriorityFeePerGas: 2 gwei nonce: 30902162761058241966966132703232 paymasterAndData: 0x - sender: 0x5699D6e507f3f3E017b863e42C7dc8276BcFb57B + sender: 0xc312a51324F449CF2389749B84Df3617373F2397 signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c Details: UserOperation reverted during simulation with reason: AA13 initCode failed or OOG diff --git a/src/account-abstraction/actions/bundler/estimateUserOperationGas.ts b/src/account-abstraction/actions/bundler/estimateUserOperationGas.ts index b6eb4d5970..5649a10485 100644 --- a/src/account-abstraction/actions/bundler/estimateUserOperationGas.ts +++ b/src/account-abstraction/actions/bundler/estimateUserOperationGas.ts @@ -174,7 +174,13 @@ export async function estimateUserOperationGas< 'prepareUserOperation', )({ ...parameters, - parameters: ['factory', 'nonce', 'paymaster', 'signature'], + parameters: [ + 'authorization', + 'factory', + 'nonce', + 'paymaster', + 'signature', + ], } as unknown as PrepareUserOperationParameters) : parameters @@ -183,6 +189,7 @@ export async function estimateUserOperationGas< formatUserOperationRequest(request as UserOperation), (entryPointAddress ?? account?.entryPoint?.address)!, ] as const + const result = await client.request({ method: 'eth_estimateUserOperationGas', params: rpcStateOverride ? [...params, rpcStateOverride] : [...params], diff --git a/src/account-abstraction/actions/bundler/getSupportedEntryPoints.test.ts b/src/account-abstraction/actions/bundler/getSupportedEntryPoints.test.ts index 34bda9af7a..419dec6f8e 100644 --- a/src/account-abstraction/actions/bundler/getSupportedEntryPoints.test.ts +++ b/src/account-abstraction/actions/bundler/getSupportedEntryPoints.test.ts @@ -2,13 +2,14 @@ import { expect, test } from 'vitest' import { bundlerMainnet } from '../../../../test/src/bundler.js' import { getSupportedEntryPoints } from './getSupportedEntryPoints.js' -const client = bundlerMainnet.getBundlerClient() +const bundlerClient = bundlerMainnet.getBundlerClient() test('default', async () => { - expect(await getSupportedEntryPoints(client)).toMatchInlineSnapshot(` + expect(await getSupportedEntryPoints(bundlerClient)).toMatchInlineSnapshot(` [ "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", "0x0000000071727De22E5E9d8BAf0edAc6f37da032", + "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108", ] `) }) diff --git a/src/account-abstraction/actions/bundler/getUserOperation.test.ts b/src/account-abstraction/actions/bundler/getUserOperation.test.ts index f0d1a8788a..57d37f6556 100644 --- a/src/account-abstraction/actions/bundler/getUserOperation.test.ts +++ b/src/account-abstraction/actions/bundler/getUserOperation.test.ts @@ -2,9 +2,11 @@ import { beforeEach, describe, expect, test } from 'vitest' import { getSmartAccounts_06, getSmartAccounts_07, + getSmartAccounts_08, } from '../../../../test/src/account-abstraction.js' import { anvilMainnet } from '../../../../test/src/anvil.js' import { bundlerMainnet } from '../../../../test/src/bundler.js' +import { signAuthorization } from '../../../actions/index.js' import { mine } from '../../../actions/test/mine.js' import { parseEther, parseGwei } from '../../../utils/index.js' import { getUserOperation } from './getUserOperation.js' @@ -21,6 +23,61 @@ const fees = { beforeEach(async () => { await bundlerMainnet.restart() }) +describe('entryPointVersion: 0.8', async () => { + const [account] = await getSmartAccounts_08() + + test('default', async () => { + const authorization = await signAuthorization( + account.client, + account.authorization, + ) + const hash = await sendUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + ], + authorization, + ...fees, + }) + + await bundlerClient.request({ + method: 'debug_bundler_sendBundleNow', + }) + await mine(client, { + blocks: 1, + }) + + const result = await getUserOperation(bundlerClient, { + hash, + }) + + expect(result).toBeDefined() + }) + + test('error: user operation not found', async () => { + const authorization = await signAuthorization(client, account.authorization) + const hash = await sendUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + ], + authorization, + ...fees, + }) + + await expect(() => + getUserOperation(bundlerClient, { + hash, + }), + ).rejects.toThrow('User Operation with hash') + }) +}) describe('entryPointVersion: 0.7', async () => { const [account] = await getSmartAccounts_07() diff --git a/src/account-abstraction/actions/bundler/getUserOperationReceipt.test.ts b/src/account-abstraction/actions/bundler/getUserOperationReceipt.test.ts index 1858b6cb73..5c261d8c50 100644 --- a/src/account-abstraction/actions/bundler/getUserOperationReceipt.test.ts +++ b/src/account-abstraction/actions/bundler/getUserOperationReceipt.test.ts @@ -2,10 +2,11 @@ import { beforeEach, describe, expect, test } from 'vitest' import { getSmartAccounts_06, getSmartAccounts_07, + getSmartAccounts_08, } from '../../../../test/src/account-abstraction.js' import { anvilMainnet } from '../../../../test/src/anvil.js' import { bundlerMainnet } from '../../../../test/src/bundler.js' -import { mine } from '../../../actions/index.js' +import { mine, signAuthorization } from '../../../actions/index.js' import { parseEther, parseGwei } from '../../../utils/index.js' import { getUserOperationReceipt } from './getUserOperationReceipt.js' import { sendUserOperation } from './sendUserOperation.js' @@ -22,6 +23,64 @@ beforeEach(async () => { await bundlerMainnet.restart() }) +describe('entryPointVersion: 0.8', async () => { + const [account] = await getSmartAccounts_08() + + test('default', async () => { + const authorization = await signAuthorization(client, account.authorization) + const hash = await sendUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + ], + authorization, + ...fees, + }) + + await bundlerClient.request({ + method: 'debug_bundler_sendBundleNow', + }) + await mine(client, { + blocks: 1, + }) + + const receipt = await getUserOperationReceipt(bundlerClient, { + hash, + }) + + expect(receipt.success).toBeTruthy() + }) + + test('error: receipt not found', async () => { + const authorization = await signAuthorization( + account.client, + account.authorization, + ) + const hash = await sendUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + ], + authorization, + ...fees, + }) + + await expect(() => + getUserOperationReceipt(bundlerClient, { + hash, + }), + ).rejects.toThrowError( + 'The User Operation may not have been processed yet.', + ) + }) +}) + describe('entryPointVersion: 0.7', async () => { const [account] = await getSmartAccounts_07() diff --git a/src/account-abstraction/actions/bundler/prepareUserOperation.test-d.ts b/src/account-abstraction/actions/bundler/prepareUserOperation.test-d.ts index 61c19640ec..df2b385ddf 100644 --- a/src/account-abstraction/actions/bundler/prepareUserOperation.test-d.ts +++ b/src/account-abstraction/actions/bundler/prepareUserOperation.test-d.ts @@ -2,6 +2,7 @@ import { describe, expectTypeOf, test } from 'vitest' import { getSmartAccounts_06, getSmartAccounts_07, + getSmartAccounts_08, } from '../../../../test/src/account-abstraction.js' import { bundlerMainnet } from '../../../../test/src/bundler.js' import type { Hex } from '../../../types/misc.js' @@ -10,6 +11,177 @@ import { prepareUserOperation } from './prepareUserOperation.js' const bundlerClient = bundlerMainnet.getBundlerClient() +describe('entryPointVersion: 0.8', async () => { + const [, , account] = await getSmartAccounts_08() + + test('default', async () => { + const result = await prepareUserOperation(bundlerClient, { + account, + calls: [{ to: '0x' }], + }) + + expectTypeOf(result.account).toMatchTypeOf(account) + expectTypeOf(result.callData).toMatchTypeOf() + expectTypeOf(result.callGasLimit).toMatchTypeOf() + expectTypeOf(result.factory).toMatchTypeOf() + expectTypeOf(result.factoryData).toMatchTypeOf() + expectTypeOf(result.nonce).toMatchTypeOf() + expectTypeOf(result.paymasterPostOpGasLimit).toMatchTypeOf< + bigint | undefined + >() + expectTypeOf(result.paymasterVerificationGasLimit).toMatchTypeOf< + bigint | undefined + >() + expectTypeOf(result.verificationGasLimit).toMatchTypeOf() + expectTypeOf(result.sender).toMatchTypeOf() + }) + + test('cast (widened)', async () => { + const result = await prepareUserOperation(bundlerClient, { + account, + ...({} as UserOperation), + }) + + expectTypeOf(result.initCode).toMatchTypeOf() + expectTypeOf(result.factory).toMatchTypeOf() + expectTypeOf(result.factoryData).toMatchTypeOf() + expectTypeOf(result.paymasterAndData).toMatchTypeOf() + + expectTypeOf(result.account).toMatchTypeOf(account) + expectTypeOf(result.callData).toMatchTypeOf() + expectTypeOf(result.callGasLimit).toMatchTypeOf() + expectTypeOf(result.nonce).toMatchTypeOf() + expectTypeOf(result.paymasterPostOpGasLimit).toMatchTypeOf< + bigint | undefined + >() + expectTypeOf(result.paymasterVerificationGasLimit).toMatchTypeOf< + bigint | undefined + >() + expectTypeOf(result.verificationGasLimit).toMatchTypeOf() + expectTypeOf(result.sender).toMatchTypeOf() + }) + + test('cast (narrowed)', async () => { + const result = await prepareUserOperation(bundlerClient, { + account, + ...({} as UserOperation<'0.8'>), + }) + + // @ts-expect-error + result.initCode + + expectTypeOf(result.factory).toMatchTypeOf() + expectTypeOf(result.factoryData).toMatchTypeOf() + + expectTypeOf(result.account).toMatchTypeOf(account) + expectTypeOf(result.callData).toMatchTypeOf() + expectTypeOf(result.callGasLimit).toMatchTypeOf() + expectTypeOf(result.nonce).toMatchTypeOf() + expectTypeOf(result.paymasterPostOpGasLimit).toMatchTypeOf< + bigint | undefined + >() + expectTypeOf(result.paymasterVerificationGasLimit).toMatchTypeOf< + bigint | undefined + >() + expectTypeOf(result.verificationGasLimit).toMatchTypeOf() + expectTypeOf(result.sender).toMatchTypeOf() + }) + + test('args: parameters', async () => { + const result = await prepareUserOperation(bundlerClient, { + account, + calls: [{ to: '0x' }], + parameters: ['gas'], + }) + + // @ts-expect-error + result.factory + // @ts-expect-error + result.factoryData + // @ts-expect-error + result.nonce + // @ts-expect-error + result.signature + + expectTypeOf(result.callGasLimit).toMatchTypeOf() + expectTypeOf(result.verificationGasLimit).toMatchTypeOf() + expectTypeOf(result.preVerificationGas).toMatchTypeOf() + expectTypeOf(result.paymasterPostOpGasLimit).toMatchTypeOf< + bigint | undefined + >() + expectTypeOf(result.paymasterVerificationGasLimit).toMatchTypeOf< + bigint | undefined + >() + + const result_2 = await prepareUserOperation(bundlerClient, { + account, + calls: [{ to: '0x' }], + parameters: ['gas', 'nonce'], + }) + + // @ts-expect-error + result_2.factory + // @ts-expect-error + result_2.factoryData + // @ts-expect-error + result_2.signature + + expectTypeOf(result_2.nonce).toMatchTypeOf() + expectTypeOf(result_2.callGasLimit).toMatchTypeOf() + expectTypeOf(result_2.verificationGasLimit).toMatchTypeOf() + expectTypeOf(result_2.preVerificationGas).toMatchTypeOf() + expectTypeOf(result_2.paymasterPostOpGasLimit).toMatchTypeOf< + bigint | undefined + >() + expectTypeOf(result_2.paymasterVerificationGasLimit).toMatchTypeOf< + bigint | undefined + >() + + const result_3 = await prepareUserOperation(bundlerClient, { + account, + calls: [{ to: '0x' }], + parameters: ['gas', 'nonce', 'signature'], + }) + + // @ts-expect-error + result_3.factory + // @ts-expect-error + result_3.factoryData + + expectTypeOf(result_3.nonce).toMatchTypeOf() + expectTypeOf(result_3.callGasLimit).toMatchTypeOf() + expectTypeOf(result_3.verificationGasLimit).toMatchTypeOf() + expectTypeOf(result_3.preVerificationGas).toMatchTypeOf() + expectTypeOf(result_3.paymasterPostOpGasLimit).toMatchTypeOf< + bigint | undefined + >() + expectTypeOf(result_3.paymasterVerificationGasLimit).toMatchTypeOf< + bigint | undefined + >() + expectTypeOf(result_3.signature).toMatchTypeOf() + + const result_4 = await prepareUserOperation(bundlerClient, { + account, + calls: [{ to: '0x' }], + parameters: ['factory', 'gas', 'nonce', 'signature'], + }) + + expectTypeOf(result_4.factory).toMatchTypeOf() + expectTypeOf(result_4.factoryData).toMatchTypeOf() + expectTypeOf(result_4.nonce).toMatchTypeOf() + expectTypeOf(result_4.callGasLimit).toMatchTypeOf() + expectTypeOf(result_4.verificationGasLimit).toMatchTypeOf() + expectTypeOf(result_4.preVerificationGas).toMatchTypeOf() + expectTypeOf(result_4.paymasterPostOpGasLimit).toMatchTypeOf< + bigint | undefined + >() + expectTypeOf(result_4.paymasterVerificationGasLimit).toMatchTypeOf< + bigint | undefined + >() + expectTypeOf(result_4.signature).toMatchTypeOf() + }) +}) + describe('entryPointVersion: 0.7', async () => { const [account] = await getSmartAccounts_07() diff --git a/src/account-abstraction/actions/bundler/prepareUserOperation.test.ts b/src/account-abstraction/actions/bundler/prepareUserOperation.test.ts index 4ffe51dd74..1a26c3c3f0 100644 --- a/src/account-abstraction/actions/bundler/prepareUserOperation.test.ts +++ b/src/account-abstraction/actions/bundler/prepareUserOperation.test.ts @@ -4,6 +4,7 @@ import { createVerifyingPaymasterServer, getSmartAccounts_06, getSmartAccounts_07, + getSmartAccounts_08, getVerifyingPaymaster_07, } from '../../../../test/src/account-abstraction.js' import { anvilMainnet } from '../../../../test/src/anvil.js' @@ -34,6 +35,708 @@ beforeEach(async () => { return () => vi.useRealTimers() }) +describe('entryPointVersion: 0.8', async () => { + const [account] = await getSmartAccounts_08() + + test('default', async () => { + const { + account: account_, + callGasLimit, + maxFeePerGas, + verificationGasLimit, + ...request + } = await prepareUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + to: wagmiContractConfig.address, + abi: wagmiContractConfig.abi, + functionName: 'mint', + }, + ], + ...fees, + }) + + expect(callGasLimit).toBeGreaterThanOrEqual(70000n) + expect(maxFeePerGas).toBeGreaterThanOrEqual(0n) + expect(verificationGasLimit).toBeGreaterThanOrEqual(90000n) + + expect(request).toMatchInlineSnapshot(` + { + "authorization": { + "address": "0x081f08945fd17c5470f7bcee23fb57ab1099428e", + "chainId": 1, + "nonce": 958, + "r": "0xfffffffffffffffffffffffffffffff000000000000000000000000000000000", + "s": "0x7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "yParity": 1, + }, + "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", + "factory": "0x7702", + "factoryData": "0x", + "maxPriorityFeePerGas": 2000000000n, + "nonce": 30902162761021348478818713600000n, + "paymasterPostOpGasLimit": 0n, + "paymasterVerificationGasLimit": 0n, + "preVerificationGas": 93882n, + "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", + } + `) + }) + + test('args: callData', async () => { + const { + account: account_, + callGasLimit, + maxFeePerGas, + verificationGasLimit, + ...request + } = await prepareUserOperation(bundlerClient, { + account, + callData: + '0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000', + ...fees, + }) + + expect(callGasLimit).toBeGreaterThanOrEqual(16000n) + expect(maxFeePerGas).toBeGreaterThanOrEqual(0n) + expect(verificationGasLimit).toBeGreaterThanOrEqual(90000n) + expect(request).toMatchInlineSnapshot(` + { + "authorization": { + "address": "0x081f08945fd17c5470f7bcee23fb57ab1099428e", + "chainId": 1, + "nonce": 958, + "r": "0xfffffffffffffffffffffffffffffff000000000000000000000000000000000", + "s": "0x7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "yParity": 1, + }, + "callData": "0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "factory": "0x7702", + "factoryData": "0x", + "maxPriorityFeePerGas": 2000000000n, + "nonce": 30902162761039795222892423151616n, + "paymasterPostOpGasLimit": 0n, + "paymasterVerificationGasLimit": 0n, + "preVerificationGas": 92087n, + "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", + } + `) + }) + + test('args: parameters (no factory)', async () => { + const { + account: _, + callGasLimit, + maxFeePerGas, + verificationGasLimit, + ...request + } = await prepareUserOperation(bundlerClient, { + account, + calls: [{ to: '0x0000000000000000000000000000000000000000' }], + parameters: ['authorization', 'gas', 'nonce'], + ...fees, + }) + + expect(callGasLimit).toBeGreaterThanOrEqual(16000n) + expect(maxFeePerGas).toBeGreaterThanOrEqual(0n) + expect(verificationGasLimit).toBeGreaterThanOrEqual(90000n) + expect(request).toMatchInlineSnapshot(` + { + "authorization": { + "address": "0x081f08945fd17c5470f7bcee23fb57ab1099428e", + "chainId": 1, + "nonce": 958, + "r": "0xfffffffffffffffffffffffffffffff000000000000000000000000000000000", + "s": "0x7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "yParity": 1, + }, + "callData": "0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "maxPriorityFeePerGas": 2000000000n, + "nonce": 30902162761058241966966132703232n, + "paymasterPostOpGasLimit": 0n, + "paymasterVerificationGasLimit": 0n, + "preVerificationGas": 92087n, + "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + } + `) + }) + + test('args: parameters (no nonce)', async () => { + const { + account: _, + callGasLimit, + maxFeePerGas, + verificationGasLimit, + ...request + } = await prepareUserOperation(bundlerClient, { + account, + calls: [{ to: '0x0000000000000000000000000000000000000000' }], + parameters: ['authorization', 'gas', 'factory'], + ...fees, + }) + + expect(callGasLimit).toBeGreaterThanOrEqual(16000n) + expect(maxFeePerGas).toBeGreaterThanOrEqual(0n) + expect(verificationGasLimit).toBeGreaterThanOrEqual(90000n) + expect(request).toMatchInlineSnapshot(` + { + "authorization": { + "address": "0x081f08945fd17c5470f7bcee23fb57ab1099428e", + "chainId": 1, + "nonce": 958, + "r": "0xfffffffffffffffffffffffffffffff000000000000000000000000000000000", + "s": "0x7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "yParity": 1, + }, + "callData": "0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "factory": "0x7702", + "factoryData": "0x", + "maxPriorityFeePerGas": 2000000000n, + "paymasterPostOpGasLimit": 0n, + "paymasterVerificationGasLimit": 0n, + "preVerificationGas": 92087n, + "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + } + `) + }) + + test('args: nonce', async () => { + const { + account: _, + callGasLimit, + maxFeePerGas, + verificationGasLimit, + ...request + } = await prepareUserOperation(bundlerClient, { + account, + calls: [{ to: '0x0000000000000000000000000000000000000000' }], + nonce: 0n, + ...fees, + }) + + expect(callGasLimit).toBeGreaterThanOrEqual(16000n) + expect(maxFeePerGas).toBeGreaterThanOrEqual(0n) + expect(verificationGasLimit).toBeGreaterThanOrEqual(90000n) + expect(request).toMatchInlineSnapshot(` + { + "authorization": { + "address": "0x081f08945fd17c5470f7bcee23fb57ab1099428e", + "chainId": 1, + "nonce": 958, + "r": "0xfffffffffffffffffffffffffffffff000000000000000000000000000000000", + "s": "0x7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "yParity": 1, + }, + "callData": "0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "factory": "0x7702", + "factoryData": "0x", + "maxPriorityFeePerGas": 2000000000n, + "nonce": 0n, + "paymasterPostOpGasLimit": 0n, + "paymasterVerificationGasLimit": 0n, + "preVerificationGas": 92087n, + "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", + } + `) + }) + + test('args: fees', async () => { + { + const { + account: _, + callGasLimit, + maxFeePerGas, + verificationGasLimit, + ...request + } = await prepareUserOperation(bundlerClient, { + account, + calls: [{ to: '0x0000000000000000000000000000000000000000' }], + maxFeePerGas: 2n, + maxPriorityFeePerGas: 1n, + }) + + expect(callGasLimit).toBeGreaterThanOrEqual(16000n) + expect(maxFeePerGas).toBeGreaterThanOrEqual(0n) + expect(verificationGasLimit).toBeGreaterThanOrEqual(90000n) + expect(request).toMatchInlineSnapshot(` + { + "authorization": { + "address": "0x081f08945fd17c5470f7bcee23fb57ab1099428e", + "chainId": 1, + "nonce": 958, + "r": "0xfffffffffffffffffffffffffffffff000000000000000000000000000000000", + "s": "0x7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "yParity": 1, + }, + "callData": "0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "factory": "0x7702", + "factoryData": "0x", + "maxPriorityFeePerGas": 1n, + "nonce": 30902162761095135455113551806464n, + "paymasterPostOpGasLimit": 0n, + "paymasterVerificationGasLimit": 0n, + "preVerificationGas": 92087n, + "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", + } + `) + } + + { + const { + account: _, + callGasLimit, + maxFeePerGas, + verificationGasLimit, + ...request + } = await prepareUserOperation(bundlerClient, { + account, + calls: [{ to: '0x0000000000000000000000000000000000000000' }], + maxFeePerGas: 2n, + }) + + expect(callGasLimit).toBeGreaterThanOrEqual(16000n) + expect(maxFeePerGas).toBeGreaterThanOrEqual(0n) + expect(verificationGasLimit).toBeGreaterThanOrEqual(90000n) + expect(request).toMatchInlineSnapshot(` + { + "authorization": { + "address": "0x081f08945fd17c5470f7bcee23fb57ab1099428e", + "chainId": 1, + "nonce": 958, + "r": "0xfffffffffffffffffffffffffffffff000000000000000000000000000000000", + "s": "0x7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "yParity": 1, + }, + "callData": "0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "factory": "0x7702", + "factoryData": "0x", + "maxPriorityFeePerGas": 2000000000n, + "nonce": 30902162761113582199187261358080n, + "paymasterPostOpGasLimit": 0n, + "paymasterVerificationGasLimit": 0n, + "preVerificationGas": 92087n, + "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", + } + `) + } + + { + const { + account: _, + callGasLimit, + maxFeePerGas, + verificationGasLimit, + ...request + } = await prepareUserOperation(bundlerClient, { + account, + calls: [{ to: '0x0000000000000000000000000000000000000000' }], + maxPriorityFeePerGas: 2n, + }) + + expect(callGasLimit).toBeGreaterThanOrEqual(16000n) + expect(maxFeePerGas).toBeGreaterThanOrEqual(0n) + expect(verificationGasLimit).toBeGreaterThanOrEqual(90000n) + expect(request).toMatchInlineSnapshot(` + { + "authorization": { + "address": "0x081f08945fd17c5470f7bcee23fb57ab1099428e", + "chainId": 1, + "nonce": 958, + "r": "0xfffffffffffffffffffffffffffffff000000000000000000000000000000000", + "s": "0x7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "yParity": 1, + }, + "callData": "0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "factory": "0x7702", + "factoryData": "0x", + "maxPriorityFeePerGas": 2n, + "nonce": 30902162761132028943260970909696n, + "paymasterPostOpGasLimit": 0n, + "paymasterVerificationGasLimit": 0n, + "preVerificationGas": 92087n, + "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", + } + `) + } + }) + + test('args: paymaster (address)', async () => { + await expect(async () => + prepareUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + to: wagmiContractConfig.address, + abi: wagmiContractConfig.abi, + functionName: 'mint', + }, + ], + paymaster: '0x0000000000000000000000000000000000000000', + + ...fees, + }), + ).rejects.toThrowError() + }) + + test('args: paymaster (true)', async () => { + await expect(async () => + prepareUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + to: wagmiContractConfig.address, + abi: wagmiContractConfig.abi, + functionName: 'mint', + }, + ], + paymaster: true, + + ...fees, + }), + ).rejects.toThrowError() + }) + + test.todo('args: paymaster (client)') + + test.todo('args: paymaster (client w/ no chain)') + + test.todo('args: paymaster.getPaymasterData') + + test.todo('args: paymaster.getPaymasterStubData + paymaster.getPaymasterData') + + test.todo('args: paymasterContext') + + test('args: signature', async () => { + const { + callGasLimit, + maxFeePerGas, + preVerificationGas, + verificationGasLimit, + ...request + } = await prepareUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + to: wagmiContractConfig.address, + abi: wagmiContractConfig.abi, + functionName: 'mint', + }, + ], + signature: + '0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c', + ...fees, + }) + + expect(account).toBeDefined() + expect(callGasLimit).toBeGreaterThanOrEqual(70000n) + expect(maxFeePerGas).toBeGreaterThanOrEqual(0n) + expect(preVerificationGas).toBeGreaterThanOrEqual(50000n) + expect(verificationGasLimit).toBeGreaterThanOrEqual(90000n) + expect({ ...request, account: undefined }).toMatchInlineSnapshot(` + { + "account": undefined, + "authorization": { + "address": "0x081f08945fd17c5470f7bcee23fb57ab1099428e", + "chainId": 1, + "nonce": 958, + "r": "0xfffffffffffffffffffffffffffffff000000000000000000000000000000000", + "s": "0x7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "yParity": 1, + }, + "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", + "factory": "0x7702", + "factoryData": "0x", + "maxPriorityFeePerGas": 2000000000n, + "nonce": 30902162761187369175482099564544n, + "paymasterPostOpGasLimit": 0n, + "paymasterVerificationGasLimit": 0n, + "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", + } + `) + }) + + test('behavior: account.userOperation.estimateGas', async () => { + const { + account: _, + callGasLimit, + preVerificationGas, + verificationGasLimit, + ...request + } = await prepareUserOperation(bundlerClient, { + account: { + ...account, + userOperation: { + async estimateGas() { + return { verificationGasLimit: 1_000_000n } + }, + }, + }, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + to: wagmiContractConfig.address, + abi: wagmiContractConfig.abi, + functionName: 'mint', + }, + ], + ...fees, + }) + + expect(callGasLimit).toBeGreaterThanOrEqual(70000n) + expect(preVerificationGas).toBeGreaterThanOrEqual(50000n) + expect(verificationGasLimit).toBeGreaterThanOrEqual(90000n) + expect(request).toMatchInlineSnapshot(` + { + "authorization": { + "address": "0x081f08945fd17c5470f7bcee23fb57ab1099428e", + "chainId": 1, + "nonce": 958, + "r": "0xfffffffffffffffffffffffffffffff000000000000000000000000000000000", + "s": "0x7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "yParity": 1, + }, + "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", + "factory": "0x7702", + "factoryData": "0x", + "maxFeePerGas": 15000000000n, + "maxPriorityFeePerGas": 2000000000n, + "nonce": 30902162761205815919555809116160n, + "paymasterPostOpGasLimit": 0n, + "paymasterVerificationGasLimit": 0n, + "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", + } + `) + }) + + test('behavior: account.userOperation.estimateGas (all filled)', async () => { + const { + account: _, + callGasLimit, + preVerificationGas, + verificationGasLimit, + ...request + } = await prepareUserOperation(bundlerClient, { + account: { + ...account, + userOperation: { + async estimateGas() { + return { + callGasLimit: 1_000_000n, + preVerificationGas: 1_000_000n, + verificationGasLimit: 1_000_000n, + } + }, + }, + }, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + to: wagmiContractConfig.address, + abi: wagmiContractConfig.abi, + functionName: 'mint', + }, + ], + ...fees, + }) + + expect(callGasLimit).toBeGreaterThanOrEqual(1000000n) + expect(preVerificationGas).toBeGreaterThanOrEqual(1000000n) + expect(verificationGasLimit).toBeGreaterThanOrEqual(1000000n) + expect(request).toMatchInlineSnapshot(` + { + "authorization": { + "address": "0x081f08945fd17c5470f7bcee23fb57ab1099428e", + "chainId": 1, + "nonce": 958, + "r": "0xfffffffffffffffffffffffffffffff000000000000000000000000000000000", + "s": "0x7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "yParity": 1, + }, + "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", + "factory": "0x7702", + "factoryData": "0x", + "maxFeePerGas": 15000000000n, + "maxPriorityFeePerGas": 2000000000n, + "nonce": 30902162761224262663629518667776n, + "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", + } + `) + }) + + test('behavior: account.userOperation.estimateGas (all filled – paymaster)', async () => { + await prepareUserOperation(bundlerClient, { + account: { + ...account, + userOperation: { + async estimateGas() { + return { + callGasLimit: 1_000_000n, + preVerificationGas: 1_000_000n, + verificationGasLimit: 1_000_000n, + paymasterPostOpGasLimit: 1_000_000n, + paymasterVerificationGasLimit: 1_000_000n, + } + }, + }, + }, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + to: wagmiContractConfig.address, + abi: wagmiContractConfig.abi, + functionName: 'mint', + }, + ], + paymaster: '0x0000000000000000000000000000000000000000', + ...fees, + }) + }) + + test('behavior: client.userOperation.estimateFeesPerGas', async () => { + const bundlerClient = bundlerMainnet.getBundlerClient({ + client, + userOperation: { + async estimateFeesPerGas() { + return { maxFeePerGas: 3_000_000n, maxPriorityFeePerGas: 1_000_000n } + }, + }, + }) + + const { + account: _, + callGasLimit, + preVerificationGas, + verificationGasLimit, + ...request + } = await prepareUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + to: wagmiContractConfig.address, + abi: wagmiContractConfig.abi, + functionName: 'mint', + }, + ], + }) + + expect(callGasLimit).toBeGreaterThanOrEqual(70000n) + expect(preVerificationGas).toBeGreaterThanOrEqual(50000n) + expect(verificationGasLimit).toBeGreaterThanOrEqual(90000n) + expect(request).toMatchInlineSnapshot(` + { + "authorization": { + "address": "0x081f08945fd17c5470f7bcee23fb57ab1099428e", + "chainId": 1, + "nonce": 958, + "r": "0xfffffffffffffffffffffffffffffff000000000000000000000000000000000", + "s": "0x7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "yParity": 1, + }, + "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", + "factory": "0x7702", + "factoryData": "0x", + "maxFeePerGas": 3000000n, + "maxPriorityFeePerGas": 1000000n, + "nonce": 30902162761261156151776937771008n, + "paymasterPostOpGasLimit": 0n, + "paymasterVerificationGasLimit": 0n, + "sender": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", + } + `) + }) + + test('behavior: bundlerClient.paymaster', async () => { + const bundlerClient = bundlerMainnet.getBundlerClient({ + client, + paymaster: true, + }) + + await expect(async () => + prepareUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + { + to: wagmiContractConfig.address, + abi: wagmiContractConfig.abi, + functionName: 'mint', + }, + ], + + ...fees, + }), + ).rejects.toThrowError() + }) + + test.todo('behavior: bundlerClient.paymaster (client)') + + test.todo('behavior: client.paymaster.getPaymasterData') + + test.todo( + 'behavior: client.paymaster.getPaymasterStubData + client.paymaster.getPaymasterData', + ) + + test.todo('behavior: bundlerClient.paymasterContext') + + test('error: no account', async () => { + await expect(() => + // @ts-expect-error + prepareUserOperation(bundlerClient, { + calls: [{ to: '0x0000000000000000000000000000000000000000' }], + ...fees, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [AccountNotFoundError: Could not find an Account to execute with this Action. + Please provide an Account with the \`account\` argument on the Action, or by supplying an \`account\` to the Client. + + Version: viem@x.y.z] + `) + }) +}) + describe('entryPointVersion: 0.7', async () => { const [account] = await getSmartAccounts_07() @@ -69,13 +772,13 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxPriorityFeePerGas": 2000000000n, "paymasterPostOpGasLimit": 0n, "paymasterVerificationGasLimit": 0n, "preVerificationGas": 53477n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -103,13 +806,13 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxPriorityFeePerGas": 2000000000n, "paymasterPostOpGasLimit": 0n, "paymasterVerificationGasLimit": 0n, "preVerificationGas": 51682n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -140,7 +843,7 @@ describe('entryPointVersion: 0.7', async () => { "paymasterPostOpGasLimit": 0n, "paymasterVerificationGasLimit": 0n, "preVerificationGas": 51682n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", } `) }) @@ -165,13 +868,13 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxPriorityFeePerGas": 2000000000n, "paymasterPostOpGasLimit": 0n, "paymasterVerificationGasLimit": 0n, "preVerificationGas": 51682n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", } `) }) @@ -197,13 +900,13 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxPriorityFeePerGas": 2000000000n, "paymasterPostOpGasLimit": 0n, "paymasterVerificationGasLimit": 0n, "preVerificationGas": 51682n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -232,13 +935,13 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxPriorityFeePerGas": 1n, "paymasterPostOpGasLimit": 0n, "paymasterVerificationGasLimit": 0n, "preVerificationGas": 51682n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -265,13 +968,13 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxPriorityFeePerGas": 2000000000n, "paymasterPostOpGasLimit": 0n, "paymasterVerificationGasLimit": 0n, "preVerificationGas": 51682n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -298,13 +1001,13 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxPriorityFeePerGas": 2n, "paymasterPostOpGasLimit": 0n, "paymasterVerificationGasLimit": 0n, "preVerificationGas": 51682n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -396,15 +1099,15 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxFeePerGas": 15000000000n, "maxPriorityFeePerGas": 2000000000n, - "paymaster": "0x98f74b7c96497070ba5052e02832ef9892962e62", + "paymaster": "0x831c6c334f8ddee62246a5c81b82c8e18008b38f", "paymasterPostOpGasLimit": 1000000n, "paymasterVerificationGasLimit": 1000000n, "preVerificationGas": 59866n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -492,15 +1195,15 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxFeePerGas": 15000000000n, "maxPriorityFeePerGas": 2000000000n, - "paymaster": "0xf47e3b0a1952a81f1afc41172762cb7ce8700133", + "paymaster": "0xc63db9682ff11707cadbd72bf1a0354a7fef143b", "paymasterPostOpGasLimit": 1000000n, "paymasterVerificationGasLimit": 1000000n, "preVerificationGas": 59866n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -558,14 +1261,14 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxFeePerGas": 15000000000n, "maxPriorityFeePerGas": 2000000000n, - "paymaster": "0xc63db9682ff11707cadbd72bf1a0354a7fef143b", + "paymaster": "0xfc3983de3f7cbe1ba01084469779470ad0bbeffa", "paymasterPostOpGasLimit": 1000000n, "paymasterVerificationGasLimit": 1000000n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -617,14 +1320,14 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxFeePerGas": 15000000000n, "maxPriorityFeePerGas": 2000000000n, - "paymaster": "0xfc3983de3f7cbe1ba01084469779470ad0bbeffa", + "paymaster": "0xf8b1d4d0a2dd9dd53200a4c6783a69c15e3a25f4", "paymasterPostOpGasLimit": 1000000n, "paymasterVerificationGasLimit": 1000000n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -665,12 +1368,12 @@ describe('entryPointVersion: 0.7', async () => { { "account": undefined, "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxPriorityFeePerGas": 2000000000n, "paymasterPostOpGasLimit": 0n, "paymasterVerificationGasLimit": 0n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xdeadbeef", } `) @@ -714,13 +1417,13 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxFeePerGas": 15000000000n, "maxPriorityFeePerGas": 2000000000n, "paymasterPostOpGasLimit": 0n, "paymasterVerificationGasLimit": 0n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -768,11 +1471,11 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxFeePerGas": 15000000000n, "maxPriorityFeePerGas": 2000000000n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -849,13 +1552,13 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxFeePerGas": 3000000n, "maxPriorityFeePerGas": 1000000n, "paymasterPostOpGasLimit": 0n, "paymasterVerificationGasLimit": 0n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -931,14 +1634,14 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxFeePerGas": 15000000000n, "maxPriorityFeePerGas": 2000000000n, - "paymaster": "0xf8b1d4d0a2dd9dd53200a4c6783a69c15e3a25f4", + "paymaster": "0xd6b8eb34413f07a1a67a469345cfea6633efd58d", "paymasterPostOpGasLimit": 1000000n, "paymasterVerificationGasLimit": 1000000n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -993,14 +1696,14 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxFeePerGas": 15000000000n, "maxPriorityFeePerGas": 2000000000n, - "paymaster": "0xd6b8eb34413f07a1a67a469345cfea6633efd58d", + "paymaster": "0x9cc87998ba85d81e017e6b7662ac00ee2ab8fe13", "paymasterPostOpGasLimit": 1000000n, "paymasterVerificationGasLimit": 1000000n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -1058,14 +1761,14 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxFeePerGas": 15000000000n, "maxPriorityFeePerGas": 2000000000n, - "paymaster": "0x9cc87998ba85d81e017e6b7662ac00ee2ab8fe13", + "paymaster": "0xb1fc11f03b084fff8dae95fa08e8d69ad2547ec1", "paymasterPostOpGasLimit": 1000000n, "paymasterVerificationGasLimit": 1000000n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -1117,14 +1820,14 @@ describe('entryPointVersion: 0.7', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0x34fcd5be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fba3912ca04dd458c843e2ee08967fc04f3579c20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000041249c58b00000000000000000000000000000000000000000000000000000000", - "factory": "0xf102f0173707c6726543d65fa38025eb72026c37", + "factory": "0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688", "factoryData": "0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxFeePerGas": 15000000000n, "maxPriorityFeePerGas": 2000000000n, - "paymaster": "0xb1fc11f03b084fff8dae95fa08e8d69ad2547ec1", + "paymaster": "0x453439300b6c5c645737324b990f2d51137027bc", "paymasterPostOpGasLimit": 1000000n, "paymasterVerificationGasLimit": 1000000n, - "sender": "0x7473Fcb76634352e4CbA37CFad3783B059792b44", + "sender": "0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -1171,12 +1874,12 @@ describe('entryPointVersion: 0.6', async () => { expect(request).toMatchInlineSnapshot(` { "callData": "0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "initCode": "0x81a5186946ce055a5ceec93cd97c7e7ede7da922f14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", + "initCode": "0x98f74b7c96497070ba5052e02832ef9892962e62f14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000000", "maxPriorityFeePerGas": 2000000000n, "paymasterAndData": "0x", "paymasterPostOpGasLimit": undefined, "paymasterVerificationGasLimit": undefined, - "sender": "0x5699D6e507f3f3E017b863e42C7dc8276BcFb57B", + "sender": "0xc312a51324F449CF2389749B84Df3617373F2397", "signature": "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c", } `) @@ -1218,7 +1921,7 @@ describe('entryPointVersion: 0.6', async () => { "paymasterAndData": "0x", "paymasterPostOpGasLimit": undefined, "paymasterVerificationGasLimit": undefined, - "sender": "0x5699D6e507f3f3E017b863e42C7dc8276BcFb57B", + "sender": "0xc312a51324F449CF2389749B84Df3617373F2397", } `) }) diff --git a/src/account-abstraction/actions/bundler/prepareUserOperation.ts b/src/account-abstraction/actions/bundler/prepareUserOperation.ts index f5705663b1..d8b90d459d 100644 --- a/src/account-abstraction/actions/bundler/prepareUserOperation.ts +++ b/src/account-abstraction/actions/bundler/prepareUserOperation.ts @@ -3,6 +3,7 @@ import { type ParseAccountErrorType, parseAccount, } from '../../../accounts/utils/parseAccount.js' +import { prepareAuthorization } from '../../../actions/index.js' import { type EstimateFeesPerGasErrorType, estimateFeesPerGas, @@ -12,6 +13,7 @@ import type { Client } from '../../../clients/createClient.js' import type { Transport } from '../../../clients/transports/createTransport.js' import { AccountNotFoundError } from '../../../errors/account.js' import type { ErrorType } from '../../../errors/utils.js' +import type { SignedAuthorization } from '../../../types/authorization.js' import type { Call, Calls } from '../../../types/calls.js' import type { Chain } from '../../../types/chain.js' import type { Hex } from '../../../types/misc.js' @@ -63,6 +65,7 @@ const defaultParameters = [ 'paymaster', 'nonce', 'signature', + 'authorization', ] as const export type PrepareUserOperationParameterType = @@ -72,10 +75,17 @@ export type PrepareUserOperationParameterType = | 'paymaster' | 'nonce' | 'signature' + | 'authorization' type FactoryProperties< entryPointVersion extends EntryPointVersion = EntryPointVersion, > = + | (entryPointVersion extends '0.8' + ? { + factory: UserOperation['factory'] + factoryData: UserOperation['factoryData'] + } + : never) | (entryPointVersion extends '0.7' ? { factory: UserOperation['factory'] @@ -91,6 +101,15 @@ type FactoryProperties< type GasProperties< entryPointVersion extends EntryPointVersion = EntryPointVersion, > = + | (entryPointVersion extends '0.8' + ? { + callGasLimit: UserOperation['callGasLimit'] + preVerificationGas: UserOperation['preVerificationGas'] + verificationGasLimit: UserOperation['verificationGasLimit'] + paymasterPostOpGasLimit: UserOperation['paymasterPostOpGasLimit'] + paymasterVerificationGasLimit: UserOperation['paymasterVerificationGasLimit'] + } + : never) | (entryPointVersion extends '0.7' ? { callGasLimit: UserOperation['callGasLimit'] @@ -120,6 +139,14 @@ type NonceProperties = { type PaymasterProperties< entryPointVersion extends EntryPointVersion = EntryPointVersion, > = + | (entryPointVersion extends '0.8' + ? { + paymaster: UserOperation['paymaster'] + paymasterData: UserOperation['paymasterData'] + paymasterPostOpGasLimit: UserOperation['paymasterPostOpGasLimit'] + paymasterVerificationGasLimit: UserOperation['paymasterVerificationGasLimit'] + } + : never) | (entryPointVersion extends '0.7' ? { paymaster: UserOperation['paymaster'] @@ -138,6 +165,10 @@ type SignatureProperties = { signature: UserOperation['signature'] } +type AuthorizationProperties = { + authorization: UserOperation['authorization'] +} + export type PrepareUserOperationRequest< account extends SmartAccount | undefined = SmartAccount | undefined, accountOverride extends SmartAccount | undefined = SmartAccount | undefined, @@ -208,7 +239,10 @@ export type PrepareUserOperationReturnType< callData: Hex paymasterAndData: _derivedVersion extends '0.6' ? Hex : undefined sender: UserOperation['sender'] - } & (Extract<_parameters, 'factory'> extends never + } & (Extract<_parameters, 'authorization'> extends never + ? {} + : AuthorizationProperties) & + (Extract<_parameters, 'factory'> extends never ? {} : FactoryProperties<_derivedVersion>) & (Extract<_parameters, 'nonce'> extends never ? {} : NonceProperties) & @@ -359,7 +393,7 @@ export async function prepareUserOperation< // Concurrently prepare properties required to fill the User Operation. //////////////////////////////////////////////////////////////////////////////// - const [callData, factory, fees, nonce] = await Promise.all([ + const [callData, factory, fees, nonce, authorization] = await Promise.all([ (async () => { if (parameters.calls) return account.encodeCalls( @@ -457,6 +491,24 @@ export async function prepareUserOperation< if (typeof parameters.nonce === 'bigint') return parameters.nonce return account.getNonce() })(), + (async () => { + if (!properties.includes('authorization')) return undefined + if (typeof parameters.authorization === 'object') + return parameters.authorization + if (account.authorization && !(await account.isDeployed())) { + const authorization = await prepareAuthorization( + account.client, + account.authorization, + ) + return { + ...authorization, + r: '0xfffffffffffffffffffffffffffffff000000000000000000000000000000000', + s: '0x7aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + yParity: 1, + } satisfies SignedAuthorization + } + return undefined + })(), ]) //////////////////////////////////////////////////////////////////////////////// @@ -468,6 +520,8 @@ export async function prepareUserOperation< request = { ...request, ...(factory as any) } if (typeof fees !== 'undefined') request = { ...request, ...(fees as any) } if (typeof nonce !== 'undefined') request.nonce = nonce + if (typeof authorization !== 'undefined') + request.authorization = authorization //////////////////////////////////////////////////////////////////////////////// // Fill User Operation with the `signature` property. diff --git a/src/account-abstraction/actions/bundler/sendUserOperation.test.ts b/src/account-abstraction/actions/bundler/sendUserOperation.test.ts index 5fb6d5df78..b4fbd1f45c 100644 --- a/src/account-abstraction/actions/bundler/sendUserOperation.test.ts +++ b/src/account-abstraction/actions/bundler/sendUserOperation.test.ts @@ -4,6 +4,7 @@ import { createVerifyingPaymasterServer, getSmartAccounts_06, getSmartAccounts_07, + getSmartAccounts_08, getVerifyingPaymaster_07, } from '../../../../test/src/account-abstraction.js' import { anvilMainnet } from '../../../../test/src/anvil.js' @@ -15,6 +16,7 @@ import { mine, readContract, setBalance, + signAuthorization, writeContract, } from '../../../actions/index.js' import { sepolia } from '../../../chains/index.js' @@ -50,12 +52,14 @@ beforeEach(async () => { return () => vi.useRealTimers() }) -describe('entryPointVersion: 0.7', async () => { - const [account, account_2, account_3] = await getSmartAccounts_07() +describe('entryPointVersion: 0.8', async () => { + const [account] = await getSmartAccounts_08() test('default', async () => { + const authorization = await signAuthorization(client, account.authorization) const hash = await sendUserOperation(bundlerClient, { account, + authorization, calls: [ { to: alice, @@ -92,6 +96,183 @@ describe('entryPointVersion: 0.7', async () => { args: [69420451n], }), ).toBe(account.address) + + await sendUserOperation(bundlerClient, { + account, + calls: [ + { + to: alice, + value: parseEther('1'), + }, + ], + ...fees, + }) + + await bundlerClient.request({ method: 'debug_bundler_sendBundleNow' }) + await mine(client, { blocks: 1 }) + + expect(await getBalance(client, { address: alice })).toMatchInlineSnapshot( + '10002000000000000000000n', + ) + }) + + test.todo('args: paymaster (client)') + + test.todo('behavior: client.paymaster (client)') + + test('behavior: prepared user operation', async () => { + const request = { + ...(await prepareUserOperation(bundlerClient, { + account, + calls: [ + { + to: alice, + value: parseEther('1'), + }, + { + to: bob, + value: parseEther('2'), + }, + ], + ...fees, + })), + account: undefined, + } as const + const signature = await account.signUserOperation(request) + + expectTypeOf(request).toMatchTypeOf() + + const hash = await sendUserOperation(bundlerClient, { + ...request, + entryPointAddress: account.entryPoint.address, + signature, + }) + expect(hash).toBeDefined() + + await bundlerClient.request({ method: 'debug_bundler_sendBundleNow' }) + await mine(client, { blocks: 1 }) + + expect(await getBalance(client, { address: alice })).toMatchInlineSnapshot( + '10001000000000000000000n', + ) + expect(await getBalance(client, { address: bob })).toMatchInlineSnapshot( + '10002000000000000000000n', + ) + expect( + await readContract(client, { + ...wagmiContractConfig, + functionName: 'ownerOf', + args: [69420451n], + }), + ).toBe(account.address) + }) + + test('error: no account', async () => { + await expect(() => + // @ts-expect-error + sendUserOperation(bundlerClient, { + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + ], + signature: '0xdeadbeef', + ...fees, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [AccountNotFoundError: Could not find an Account to execute with this Action. + Please provide an Account with the \`account\` argument on the Action, or by supplying an \`account\` to the Client. + + Version: viem@x.y.z] + `) + }) + + test('error: aa24', async () => { + const authorization = await signAuthorization(client, account.authorization) + await expect(() => + sendUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + ], + signature: + '0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c', + callGasLimit: 80000n, + verificationGasLimit: 79141n, + authorization, + ...fees, + }), + ).rejects.toThrowErrorMatchingInlineSnapshot(` + [UserOperationExecutionError: Signature provided for the User Operation is invalid. + + This could arise when: + - the \`signature\` for the User Operation is incorrectly computed, and unable to be verified by the Smart Account + + Request Arguments: + callData: 0xb61d27f600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000 + callGasLimit: 80000 + maxFeePerGas: 15 gwei + maxPriorityFeePerGas: 2 gwei + nonce: 30902162761076688711039842254848 + paymasterPostOpGasLimit: 0 + paymasterVerificationGasLimit: 0 + preVerificationGas: 91968 + sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c + verificationGasLimit: 79141 + + Details: UserOperation reverted with reason: AA24 signature error + Version: viem@x.y.z] + `) + }) +}) + +describe('entryPointVersion: 0.7', async () => { + const [account, account_2, account_3] = await getSmartAccounts_07() + + test('default', async () => { + const hash = await sendUserOperation(bundlerClient, { + account, + calls: [ + { + to: alice, + value: parseEther('1'), + }, + { + to: bob, + value: parseEther('2'), + }, + { + abi: wagmiContractConfig.abi, + functionName: 'mint', + to: wagmiContractConfig.address, + args: [69420511n], + }, + ], + ...fees, + }) + expect(hash).toBeDefined() + + await bundlerClient.request({ method: 'debug_bundler_sendBundleNow' }) + await mine(client, { blocks: 1 }) + + expect(await getBalance(client, { address: alice })).toMatchInlineSnapshot( + '10001000000000000000000n', + ) + expect(await getBalance(client, { address: bob })).toMatchInlineSnapshot( + '10002000000000000000000n', + ) + expect( + await readContract(client, { + ...wagmiContractConfig, + functionName: 'ownerOf', + args: [69420511n], + }), + ).toBe(account.address) }) test('args: paymaster (client)', async () => { @@ -194,13 +375,6 @@ describe('entryPointVersion: 0.7', async () => { expect(await getBalance(client, { address: bob })).toMatchInlineSnapshot( '10002000000000000000000n', ) - expect( - await readContract(client, { - ...wagmiContractConfig, - functionName: 'ownerOf', - args: [69420451n], - }), - ).toBe(account.address) }) test('error: no account', async () => { @@ -256,13 +430,13 @@ describe('entryPointVersion: 0.7', async () => { Request Arguments: callData: 0xb61d27f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000 callGasLimit: 0 - factory: 0xf102f0173707c6726543d65fa38025eb72026c37 + factory: 0x5edb3ff1ea450d1ff6d614f24f5c760761f7f688 factoryData: 0xf14ddffc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000001 maxFeePerGas: 15 gwei maxPriorityFeePerGas: 2 gwei nonce: 30902162761021348478818713600000 preVerificationGas: 0 - sender: 0xc1b7F8B47312df97bdd97A6A4e322335F684A485 + sender: 0xC6B426A3272a812dD1B3EDB601447bbAA8C1294C signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c verificationGasLimit: 0 @@ -305,7 +479,7 @@ describe('entryPointVersion: 0.7', async () => { maxPriorityFeePerGas: 2 gwei nonce: 30902162761021348478818713600000 preVerificationGas: 0 - sender: 0x820576f0A704fE307eDcd62839C1de2818D860d5 + sender: 0xd90Fd455cB571186372581209e0491337B736Ad4 signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c verificationGasLimit: 0 @@ -344,7 +518,7 @@ describe('entryPointVersion: 0.7', async () => { paymasterPostOpGasLimit: 0 paymasterVerificationGasLimit: 0 preVerificationGas: 48527 - sender: 0x7473Fcb76634352e4CbA37CFad3783B059792b44 + sender: 0xF2F83Eb89C48abd7aD93bA42C3ce904895337cea signature: 0xdeadbeef verificationGasLimit: 79141 @@ -423,7 +597,7 @@ describe('entryPointVersion: 0.6', async () => { nonce: 30902162761021348478818713600000 paymasterAndData: 0x preVerificationGas: 0 - sender: 0x2e9F95d2E79bc16bDC8CEAa2e555Ef8b213ae478 + sender: 0x9e445784532eb3534bF7259bd9C1Df92DE5d65D1 signature: 0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c verificationGasLimit: 0 diff --git a/src/account-abstraction/actions/bundler/sendUserOperation.ts b/src/account-abstraction/actions/bundler/sendUserOperation.ts index 1ea0e79122..463356ca3a 100644 --- a/src/account-abstraction/actions/bundler/sendUserOperation.ts +++ b/src/account-abstraction/actions/bundler/sendUserOperation.ts @@ -135,7 +135,7 @@ export async function sendUserOperation< : parameters const signature = (parameters.signature || - (await account?.signUserOperation(request as UserOperation)))! + (await account?.signUserOperation?.(request as UserOperation)))! const rpcParameters = formatUserOperationRequest({ ...request, @@ -148,7 +148,7 @@ export async function sendUserOperation< method: 'eth_sendUserOperation', params: [ rpcParameters, - (entryPointAddress ?? account?.entryPoint.address)!, + (entryPointAddress ?? account?.entryPoint?.address)!, ], }, { retryCount: 0 }, diff --git a/src/account-abstraction/actions/bundler/waitForUserOperationReceipt.test.ts b/src/account-abstraction/actions/bundler/waitForUserOperationReceipt.test.ts index b21fb47a5d..8ab91eaff2 100644 --- a/src/account-abstraction/actions/bundler/waitForUserOperationReceipt.test.ts +++ b/src/account-abstraction/actions/bundler/waitForUserOperationReceipt.test.ts @@ -2,10 +2,11 @@ import { beforeEach, describe, expect, test, vi } from 'vitest' import { getSmartAccounts_06, getSmartAccounts_07, + getSmartAccounts_08, } from '../../../../test/src/account-abstraction.js' import { anvilMainnet } from '../../../../test/src/anvil.js' import { bundlerMainnet } from '../../../../test/src/bundler.js' -import { mine } from '../../../actions/index.js' +import { mine, signAuthorization } from '../../../actions/index.js' import { parseEther, parseGwei } from '../../../utils/index.js' import { wait } from '../../../utils/wait.js' import * as getUserOperationReceipt from './getUserOperationReceipt.js' @@ -24,6 +25,173 @@ beforeEach(async () => { await bundlerMainnet.restart() }) +describe('entryPointVersion: 0.8', async () => { + const [account] = await getSmartAccounts_08() + + test('default', async () => { + const authorization = await signAuthorization(client, account.authorization) + const hash = await sendUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + ], + authorization, + ...fees, + }) + + const [receipt] = await Promise.all([ + waitForUserOperationReceipt(bundlerClient, { + hash, + }), + (async () => { + // Simulate some delay to send the bundle + mine block. + await wait(500) + await bundlerClient.request({ + method: 'debug_bundler_sendBundleNow', + }) + await mine(client, { + blocks: 1, + }) + })(), + ]) + + expect(receipt.success).toBeTruthy() + }) + + test('args: pollingInterval', async () => { + const authorization = await signAuthorization(client, account.authorization) + const hash = await sendUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + ], + authorization, + ...fees, + }) + + const [receipt] = await Promise.all([ + waitForUserOperationReceipt(bundlerClient, { + hash, + pollingInterval: 100, + }), + (async () => { + // Simulate some delay to send the bundle + mine block. + await wait(100) + await bundlerClient.request({ + method: 'debug_bundler_sendBundleNow', + }) + await mine(client, { + blocks: 1, + }) + })(), + ]) + + expect(receipt.success).toBeTruthy() + }) + + test('error: retryCount exceeded', async () => { + const authorization = await signAuthorization(client, account.authorization) + const hash = await sendUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + ], + authorization, + ...fees, + }) + + await expect(() => + Promise.all([ + waitForUserOperationReceipt(bundlerClient, { + hash, + retryCount: 6, + }), + (async () => { + // Simulate some delay + await wait(500) + })(), + ]), + ).rejects.toThrowError('Timed out while waiting for User Operation') + }) + + test('error: timeout exceeded', async () => { + const authorization = await signAuthorization(client, account.authorization) + const hash = await sendUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + ], + authorization, + ...fees, + }) + + await expect(() => + Promise.all([ + waitForUserOperationReceipt(bundlerClient, { + hash, + timeout: 100, + }), + (async () => { + // Simulate some delay + await wait(500) + })(), + ]), + ).rejects.toThrowError('Timed out while waiting for User Operation') + }) + + test('error: generic error', async () => { + const authorization = await signAuthorization(client, account.authorization) + const hash = await sendUserOperation(bundlerClient, { + account, + calls: [ + { + to: '0x0000000000000000000000000000000000000000', + value: parseEther('1'), + }, + ], + authorization, + ...fees, + }) + + vi.spyOn( + getUserOperationReceipt, + 'getUserOperationReceipt', + ).mockRejectedValueOnce(new Error('test')) + + await expect(() => + Promise.all([ + waitForUserOperationReceipt(bundlerClient, { + hash, + }), + (async () => { + // Simulate some delay to send the bundle + mine block. + await wait(100) + await bundlerClient.request({ + method: 'debug_bundler_sendBundleNow', + }) + await mine(client, { + blocks: 1, + }) + })(), + ]), + ).rejects.toMatchInlineSnapshot(` + [Error: test] + `) + }) +}) + describe('entryPointVersion: 0.7', async () => { const [account] = await getSmartAccounts_07() diff --git a/src/account-abstraction/actions/paymaster/getPaymasterData.ts b/src/account-abstraction/actions/paymaster/getPaymasterData.ts index 16685698d0..77e15d2d29 100644 --- a/src/account-abstraction/actions/paymaster/getPaymasterData.ts +++ b/src/account-abstraction/actions/paymaster/getPaymasterData.ts @@ -57,6 +57,30 @@ export type GetPaymasterDataParameters = OneOf< | 'preVerificationGas' | 'verificationGasLimit' > + | PartialBy< + Pick< + UserOperation<'0.8'>, + | 'callData' + | 'callGasLimit' + | 'factory' + | 'factoryData' + | 'maxFeePerGas' + | 'maxPriorityFeePerGas' + | 'nonce' + | 'sender' + | 'preVerificationGas' + | 'verificationGasLimit' + | 'paymasterPostOpGasLimit' + | 'paymasterVerificationGasLimit' + >, + | 'callGasLimit' + | 'factory' + | 'factoryData' + | 'maxFeePerGas' + | 'maxPriorityFeePerGas' + | 'preVerificationGas' + | 'verificationGasLimit' + > > & { context?: unknown | undefined chainId: number diff --git a/src/account-abstraction/actions/paymaster/getPaymasterStubData.test.ts b/src/account-abstraction/actions/paymaster/getPaymasterStubData.test.ts index 06741529b0..d5d7f58025 100644 --- a/src/account-abstraction/actions/paymaster/getPaymasterStubData.test.ts +++ b/src/account-abstraction/actions/paymaster/getPaymasterStubData.test.ts @@ -15,7 +15,6 @@ import { getPaymasterStubData } from './getPaymasterStubData.js' const client = anvilMainnet.getClient({ account: true }) const bundlerClient = bundlerMainnet.getBundlerClient({ client }) - describe('entryPointVersion: 0.7', async () => { const [account] = await getSmartAccounts_07() const paymaster = await getVerifyingPaymaster_07() diff --git a/src/account-abstraction/constants/abis.ts b/src/account-abstraction/constants/abis.ts index 518f192fe0..5ea816997b 100644 --- a/src/account-abstraction/constants/abis.ts +++ b/src/account-abstraction/constants/abis.ts @@ -1402,3 +1402,694 @@ export const entryPoint07Abi = [ }, { stateMutability: 'payable', type: 'receive' }, ] as const + +export const entryPoint08Abi = [ + { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, + { + inputs: [ + { internalType: 'bool', name: 'success', type: 'bool' }, + { internalType: 'bytes', name: 'ret', type: 'bytes' }, + ], + name: 'DelegateAndRevert', + type: 'error', + }, + { + inputs: [ + { internalType: 'uint256', name: 'opIndex', type: 'uint256' }, + { internalType: 'string', name: 'reason', type: 'string' }, + ], + name: 'FailedOp', + type: 'error', + }, + { + inputs: [ + { internalType: 'uint256', name: 'opIndex', type: 'uint256' }, + { internalType: 'string', name: 'reason', type: 'string' }, + { internalType: 'bytes', name: 'inner', type: 'bytes' }, + ], + name: 'FailedOpWithRevert', + type: 'error', + }, + { inputs: [], name: 'InvalidShortString', type: 'error' }, + { + inputs: [{ internalType: 'bytes', name: 'returnData', type: 'bytes' }], + name: 'PostOpReverted', + type: 'error', + }, + { inputs: [], name: 'ReentrancyGuardReentrantCall', type: 'error' }, + { + inputs: [{ internalType: 'address', name: 'sender', type: 'address' }], + name: 'SenderAddressResult', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'aggregator', type: 'address' }], + name: 'SignatureValidationFailed', + type: 'error', + }, + { + inputs: [{ internalType: 'string', name: 'str', type: 'string' }], + name: 'StringTooLong', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'userOpHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'factory', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'paymaster', + type: 'address', + }, + ], + name: 'AccountDeployed', + type: 'event', + }, + { anonymous: false, inputs: [], name: 'BeforeExecution', type: 'event' }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'account', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'totalDeposit', + type: 'uint256', + }, + ], + name: 'Deposited', + type: 'event', + }, + { anonymous: false, inputs: [], name: 'EIP712DomainChanged', type: 'event' }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'userOpHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes', + name: 'revertReason', + type: 'bytes', + }, + ], + name: 'PostOpRevertReason', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'aggregator', + type: 'address', + }, + ], + name: 'SignatureAggregatorChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'account', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'totalStaked', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'unstakeDelaySec', + type: 'uint256', + }, + ], + name: 'StakeLocked', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'account', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'withdrawTime', + type: 'uint256', + }, + ], + name: 'StakeUnlocked', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'account', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'withdrawAddress', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'StakeWithdrawn', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'userOpHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'paymaster', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { indexed: false, internalType: 'bool', name: 'success', type: 'bool' }, + { + indexed: false, + internalType: 'uint256', + name: 'actualGasCost', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'actualGasUsed', + type: 'uint256', + }, + ], + name: 'UserOperationEvent', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'userOpHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + ], + name: 'UserOperationPrefundTooLow', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'userOpHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes', + name: 'revertReason', + type: 'bytes', + }, + ], + name: 'UserOperationRevertReason', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'account', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'withdrawAddress', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Withdrawn', + type: 'event', + }, + { + inputs: [ + { internalType: 'uint32', name: 'unstakeDelaySec', type: 'uint32' }, + ], + name: 'addStake', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'account', type: 'address' }], + name: 'balanceOf', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'target', type: 'address' }, + { internalType: 'bytes', name: 'data', type: 'bytes' }, + ], + name: 'delegateAndRevert', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'account', type: 'address' }], + name: 'depositTo', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [], + name: 'eip712Domain', + outputs: [ + { internalType: 'bytes1', name: 'fields', type: 'bytes1' }, + { internalType: 'string', name: 'name', type: 'string' }, + { internalType: 'string', name: 'version', type: 'string' }, + { internalType: 'uint256', name: 'chainId', type: 'uint256' }, + { internalType: 'address', name: 'verifyingContract', type: 'address' }, + { internalType: 'bytes32', name: 'salt', type: 'bytes32' }, + { internalType: 'uint256[]', name: 'extensions', type: 'uint256[]' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'account', type: 'address' }], + name: 'getDepositInfo', + outputs: [ + { + components: [ + { internalType: 'uint256', name: 'deposit', type: 'uint256' }, + { internalType: 'bool', name: 'staked', type: 'bool' }, + { internalType: 'uint112', name: 'stake', type: 'uint112' }, + { internalType: 'uint32', name: 'unstakeDelaySec', type: 'uint32' }, + { internalType: 'uint48', name: 'withdrawTime', type: 'uint48' }, + ], + internalType: 'struct IStakeManager.DepositInfo', + name: 'info', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getDomainSeparatorV4', + outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'sender', type: 'address' }, + { internalType: 'uint192', name: 'key', type: 'uint192' }, + ], + name: 'getNonce', + outputs: [{ internalType: 'uint256', name: 'nonce', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getPackedUserOpTypeHash', + outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes', name: 'initCode', type: 'bytes' }], + name: 'getSenderAddress', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'sender', type: 'address' }, + { internalType: 'uint256', name: 'nonce', type: 'uint256' }, + { internalType: 'bytes', name: 'initCode', type: 'bytes' }, + { internalType: 'bytes', name: 'callData', type: 'bytes' }, + { + internalType: 'bytes32', + name: 'accountGasLimits', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'preVerificationGas', + type: 'uint256', + }, + { internalType: 'bytes32', name: 'gasFees', type: 'bytes32' }, + { internalType: 'bytes', name: 'paymasterAndData', type: 'bytes' }, + { internalType: 'bytes', name: 'signature', type: 'bytes' }, + ], + internalType: 'struct PackedUserOperation', + name: 'userOp', + type: 'tuple', + }, + ], + name: 'getUserOpHash', + outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { internalType: 'address', name: 'sender', type: 'address' }, + { internalType: 'uint256', name: 'nonce', type: 'uint256' }, + { internalType: 'bytes', name: 'initCode', type: 'bytes' }, + { internalType: 'bytes', name: 'callData', type: 'bytes' }, + { + internalType: 'bytes32', + name: 'accountGasLimits', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'preVerificationGas', + type: 'uint256', + }, + { internalType: 'bytes32', name: 'gasFees', type: 'bytes32' }, + { + internalType: 'bytes', + name: 'paymasterAndData', + type: 'bytes', + }, + { internalType: 'bytes', name: 'signature', type: 'bytes' }, + ], + internalType: 'struct PackedUserOperation[]', + name: 'userOps', + type: 'tuple[]', + }, + { + internalType: 'contract IAggregator', + name: 'aggregator', + type: 'address', + }, + { internalType: 'bytes', name: 'signature', type: 'bytes' }, + ], + internalType: 'struct IEntryPoint.UserOpsPerAggregator[]', + name: 'opsPerAggregator', + type: 'tuple[]', + }, + { internalType: 'address payable', name: 'beneficiary', type: 'address' }, + ], + name: 'handleAggregatedOps', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'address', name: 'sender', type: 'address' }, + { internalType: 'uint256', name: 'nonce', type: 'uint256' }, + { internalType: 'bytes', name: 'initCode', type: 'bytes' }, + { internalType: 'bytes', name: 'callData', type: 'bytes' }, + { + internalType: 'bytes32', + name: 'accountGasLimits', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'preVerificationGas', + type: 'uint256', + }, + { internalType: 'bytes32', name: 'gasFees', type: 'bytes32' }, + { internalType: 'bytes', name: 'paymasterAndData', type: 'bytes' }, + { internalType: 'bytes', name: 'signature', type: 'bytes' }, + ], + internalType: 'struct PackedUserOperation[]', + name: 'ops', + type: 'tuple[]', + }, + { internalType: 'address payable', name: 'beneficiary', type: 'address' }, + ], + name: 'handleOps', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint192', name: 'key', type: 'uint192' }], + name: 'incrementNonce', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'bytes', name: 'callData', type: 'bytes' }, + { + components: [ + { + components: [ + { internalType: 'address', name: 'sender', type: 'address' }, + { internalType: 'uint256', name: 'nonce', type: 'uint256' }, + { + internalType: 'uint256', + name: 'verificationGasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'callGasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'paymasterVerificationGasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'paymasterPostOpGasLimit', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'preVerificationGas', + type: 'uint256', + }, + { internalType: 'address', name: 'paymaster', type: 'address' }, + { + internalType: 'uint256', + name: 'maxFeePerGas', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'maxPriorityFeePerGas', + type: 'uint256', + }, + ], + internalType: 'struct EntryPoint.MemoryUserOp', + name: 'mUserOp', + type: 'tuple', + }, + { internalType: 'bytes32', name: 'userOpHash', type: 'bytes32' }, + { internalType: 'uint256', name: 'prefund', type: 'uint256' }, + { internalType: 'uint256', name: 'contextOffset', type: 'uint256' }, + { internalType: 'uint256', name: 'preOpGas', type: 'uint256' }, + ], + internalType: 'struct EntryPoint.UserOpInfo', + name: 'opInfo', + type: 'tuple', + }, + { internalType: 'bytes', name: 'context', type: 'bytes' }, + ], + name: 'innerHandleOp', + outputs: [ + { internalType: 'uint256', name: 'actualGasCost', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: '', type: 'address' }, + { internalType: 'uint192', name: '', type: 'uint192' }, + ], + name: 'nonceSequenceNumber', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'senderCreator', + outputs: [ + { internalType: 'contract ISenderCreator', name: '', type: 'address' }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'unlockStake', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address payable', + name: 'withdrawAddress', + type: 'address', + }, + ], + name: 'withdrawStake', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address payable', + name: 'withdrawAddress', + type: 'address', + }, + { internalType: 'uint256', name: 'withdrawAmount', type: 'uint256' }, + ], + name: 'withdrawTo', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { stateMutability: 'payable', type: 'receive' }, +] as const diff --git a/src/account-abstraction/constants/address.ts b/src/account-abstraction/constants/address.ts index b62184fc73..ec5cac696e 100644 --- a/src/account-abstraction/constants/address.ts +++ b/src/account-abstraction/constants/address.ts @@ -2,3 +2,5 @@ export const entryPoint06Address = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789' as const export const entryPoint07Address = '0x0000000071727De22E5E9d8BAf0edAc6f37da032' as const +export const entryPoint08Address = + '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108' as const diff --git a/src/account-abstraction/index.ts b/src/account-abstraction/index.ts index ba49ec759c..43dfccb193 100644 --- a/src/account-abstraction/index.ts +++ b/src/account-abstraction/index.ts @@ -17,6 +17,12 @@ export { type ToSoladySmartAccountReturnType, toSoladySmartAccount, } from './accounts/implementations/toSoladySmartAccount.js' +export { + type Simple7702SmartAccountImplementation, + type ToSimple7702SmartAccountParameters, + type ToSimple7702SmartAccountReturnType, + toSimple7702SmartAccount, +} from './accounts/implementations/toSimple7702SmartAccount.js' export { type ToSmartAccountParameters, type ToSmartAccountReturnType, @@ -112,10 +118,15 @@ export { createPaymasterClient, } from './clients/createPaymasterClient.js' -export { entryPoint06Abi, entryPoint07Abi } from './constants/abis.js' +export { + entryPoint06Abi, + entryPoint07Abi, + entryPoint08Abi, +} from './constants/abis.js' export { entryPoint06Address, entryPoint07Address, + entryPoint08Address, } from './constants/address.js' export { diff --git a/src/account-abstraction/types/entryPointVersion.ts b/src/account-abstraction/types/entryPointVersion.ts index 663bb40abc..ce968db10d 100644 --- a/src/account-abstraction/types/entryPointVersion.ts +++ b/src/account-abstraction/types/entryPointVersion.ts @@ -1,7 +1,7 @@ import type { SmartAccount } from '../accounts/types.js' /** @link https://github.com/eth-infinitism/account-abstraction/releases */ -export type EntryPointVersion = '0.6' | '0.7' +export type EntryPointVersion = '0.6' | '0.7' | '0.8' export type DeriveEntryPointVersion = account extends SmartAccount diff --git a/src/account-abstraction/types/rpc.ts b/src/account-abstraction/types/rpc.ts index e45487a171..81ddb7f7c6 100644 --- a/src/account-abstraction/types/rpc.ts +++ b/src/account-abstraction/types/rpc.ts @@ -1,4 +1,5 @@ import type { Hex } from '../../types/misc.js' +import type { RpcAuthorization } from '../../types/rpc.js' import type { EntryPointVersion } from './entryPointVersion.js' import type { EstimateUserOperationGasReturnType, @@ -14,11 +15,13 @@ export type RpcEstimateUserOperationGasReturnType< export type RpcGetUserOperationByHashReturnType< entryPointVersion extends EntryPointVersion = EntryPointVersion, -> = GetUserOperationByHashReturnType +> = GetUserOperationByHashReturnType export type RpcUserOperation< entryPointVersion extends EntryPointVersion = EntryPointVersion, -> = UserOperation +> = UserOperation & { + eip7702Auth?: RpcAuthorization +} export type RpcUserOperationReceipt< entryPointVersion extends EntryPointVersion = EntryPointVersion, @@ -26,4 +29,4 @@ export type RpcUserOperationReceipt< export type RpcUserOperationRequest< entryPointVersion extends EntryPointVersion = EntryPointVersion, -> = UserOperationRequest +> = UserOperationRequest diff --git a/src/account-abstraction/types/userOperation.ts b/src/account-abstraction/types/userOperation.ts index 7d73dd62bb..c3b11f36de 100644 --- a/src/account-abstraction/types/userOperation.ts +++ b/src/account-abstraction/types/userOperation.ts @@ -1,4 +1,5 @@ import type { Address } from 'abitype' +import type { SignedAuthorization } from '../../types/authorization.js' import type { Log } from '../../types/log.js' import type { Hash, Hex } from '../../types/misc.js' import type { TransactionReceipt } from '../../types/transaction.js' @@ -10,6 +11,15 @@ export type EstimateUserOperationGasReturnType< entryPointVersion extends EntryPointVersion = EntryPointVersion, uint256 = bigint, > = OneOf< + | (entryPointVersion extends '0.8' + ? { + preVerificationGas: uint256 + verificationGasLimit: uint256 + callGasLimit: uint256 + paymasterVerificationGasLimit?: uint256 | undefined + paymasterPostOpGasLimit?: uint256 | undefined + } + : never) | (entryPointVersion extends '0.7' ? { preVerificationGas: uint256 @@ -32,12 +42,13 @@ export type EstimateUserOperationGasReturnType< export type GetUserOperationByHashReturnType< entryPointVersion extends EntryPointVersion = EntryPointVersion, uint256 = bigint, + uint32 = number, > = { blockHash: Hash blockNumber: uint256 entryPoint: Address transactionHash: Hash - userOperation: UserOperation + userOperation: UserOperation } /** @link https://eips.ethereum.org/EIPS/eip-4337#entrypoint-definition */ @@ -66,9 +77,48 @@ export type PackedUserOperation = { export type UserOperation< entryPointVersion extends EntryPointVersion = EntryPointVersion, uint256 = bigint, + uint32 = number, > = OneOf< + | (entryPointVersion extends '0.8' + ? { + /** Authorization data. */ + authorization?: SignedAuthorization | undefined + /** The data to pass to the `sender` during the main execution call. */ + callData: Hex + /** The amount of gas to allocate the main execution call */ + callGasLimit: uint256 + /** Account factory. Only for new accounts. */ + factory?: Address | undefined + /** Data for account factory. */ + factoryData?: Hex | undefined + /** Maximum fee per gas. */ + maxFeePerGas: uint256 + /** Maximum priority fee per gas. */ + maxPriorityFeePerGas: uint256 + /** Anti-replay parameter. */ + nonce: uint256 + /** Address of paymaster contract. */ + paymaster?: Address | undefined + /** Data for paymaster. */ + paymasterData?: Hex | undefined + /** The amount of gas to allocate for the paymaster post-operation code. */ + paymasterPostOpGasLimit?: uint256 | undefined + /** The amount of gas to allocate for the paymaster validation code. */ + paymasterVerificationGasLimit?: uint256 | undefined + /** Extra gas to pay the Bundler. */ + preVerificationGas: uint256 + /** The account making the operation. */ + sender: Address + /** Data passed into the account to verify authorization. */ + signature: Hex + /** The amount of gas to allocate for the verification step. */ + verificationGasLimit: uint256 + } + : never) | (entryPointVersion extends '0.7' ? { + /** Authorization data. */ + authorization?: SignedAuthorization | undefined /** The data to pass to the `sender` during the main execution call. */ callData: Hex /** The amount of gas to allocate the main execution call */ @@ -103,6 +153,8 @@ export type UserOperation< : never) | (entryPointVersion extends '0.6' ? { + /** Authorization data. */ + authorization?: SignedAuthorization | undefined /** The data to pass to the `sender` during the main execution call. */ callData: Hex /** The amount of gas to allocate the main execution call */ @@ -132,10 +184,24 @@ export type UserOperation< export type UserOperationRequest< entryPointVersion extends EntryPointVersion = EntryPointVersion, uint256 = bigint, + uint32 = number, > = OneOf< + | (entryPointVersion extends '0.8' + ? UnionPartialBy< + UserOperation<'0.8', uint256, uint32>, + // We are able to calculate these via `prepareUserOperation`. + | keyof EstimateUserOperationGasReturnType<'0.8'> + | 'callData' + | 'maxFeePerGas' + | 'maxPriorityFeePerGas' + | 'nonce' + | 'sender' + | 'signature' + > + : never) | (entryPointVersion extends '0.7' ? UnionPartialBy< - UserOperation<'0.7', uint256>, + UserOperation<'0.7', uint256, uint32>, // We are able to calculate these via `prepareUserOperation`. | keyof EstimateUserOperationGasReturnType<'0.7'> | 'callData' @@ -148,7 +214,7 @@ export type UserOperationRequest< : never) | (entryPointVersion extends '0.6' ? UnionPartialBy< - UserOperation<'0.6', uint256>, + UserOperation<'0.6', uint256, uint32>, // We are able to calculate these via `prepareUserOperation`. | keyof EstimateUserOperationGasReturnType<'0.6'> | 'callData' diff --git a/src/account-abstraction/utils/formatters/userOperationRequest.ts b/src/account-abstraction/utils/formatters/userOperationRequest.ts index 803af44cf4..a817e2114d 100644 --- a/src/account-abstraction/utils/formatters/userOperationRequest.ts +++ b/src/account-abstraction/utils/formatters/userOperationRequest.ts @@ -1,6 +1,8 @@ import type { ErrorType } from '../../../errors/utils.js' +import type { SignedAuthorization } from '../../../types/authorization.js' import type { ExactPartial } from '../../../types/utils.js' import { numberToHex } from '../../../utils/encoding/toHex.js' +import { pad } from '../../../utils/index.js' import type { RpcUserOperation } from '../../types/rpc.js' import type { UserOperation } from '../../types/userOperation.js' @@ -48,6 +50,25 @@ export function formatUserOperationRequest( rpcRequest.signature = request.signature if (typeof request.verificationGasLimit !== 'undefined') rpcRequest.verificationGasLimit = numberToHex(request.verificationGasLimit) + if (typeof request.authorization !== 'undefined') + rpcRequest.eip7702Auth = formatAuthorization(request.authorization) return rpcRequest } + +function formatAuthorization(authorization: SignedAuthorization) { + return { + address: authorization.address, + chainId: numberToHex(authorization.chainId), + nonce: numberToHex(authorization.nonce), + r: authorization.r + ? numberToHex(BigInt(authorization.r), { size: 32 }) + : pad('0x', { size: 32 }), + s: authorization.s + ? numberToHex(BigInt(authorization.s), { size: 32 }) + : pad('0x', { size: 32 }), + yParity: authorization.yParity + ? numberToHex(authorization.yParity, { size: 1 }) + : pad('0x', { size: 32 }), + } +} diff --git a/src/account-abstraction/utils/userOperation/getUserOperationHash.test.ts b/src/account-abstraction/utils/userOperation/getUserOperationHash.test.ts index f448c80bb9..dc5891fba1 100644 --- a/src/account-abstraction/utils/userOperation/getUserOperationHash.test.ts +++ b/src/account-abstraction/utils/userOperation/getUserOperationHash.test.ts @@ -1,6 +1,195 @@ import { describe, expect, test } from 'vitest' import { getUserOperationHash } from './getUserOperationHash.js' +describe('entryPoint: 0.8', () => { + test('default', () => { + expect( + getUserOperationHash({ + chainId: 1, + entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', + entryPointVersion: '0.8', + userOperation: { + callData: '0x', + callGasLimit: 6942069n, + maxFeePerGas: 69420n, + maxPriorityFeePerGas: 69n, + nonce: 0n, + preVerificationGas: 6942069n, + sender: '0x1234567890123456789012345678901234567890', + signature: '0x', + verificationGasLimit: 6942069n, + }, + }), + ).toMatchInlineSnapshot( + `"0xa2224e732a1d4e2f923c7c05d586a0aa6cbc42172ec02f31d35fa9a2b8ba9208"`, + ) + }) + + test('args: factory + factoryData', () => { + expect( + getUserOperationHash({ + chainId: 1, + entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', + entryPointVersion: '0.8', + userOperation: { + callData: '0x', + callGasLimit: 6942069n, + maxFeePerGas: 69420n, + maxPriorityFeePerGas: 69n, + nonce: 0n, + preVerificationGas: 6942069n, + sender: '0x1234567890123456789012345678901234567890', + signature: '0x', + verificationGasLimit: 6942069n, + factory: '0x1234567890123456789012345678901234567890', + factoryData: '0xdeadbeef', + }, + }), + ).toMatchInlineSnapshot( + `"0x3146c70a9ef7538e9b9aca8b00ad4b127ca7eef7817a557f1801acbf8d68c206"`, + ) + }) + + test('args: paymaster', () => { + expect( + getUserOperationHash({ + chainId: 1, + entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', + entryPointVersion: '0.8', + userOperation: { + callData: '0x', + callGasLimit: 6942069n, + maxFeePerGas: 69420n, + maxPriorityFeePerGas: 69n, + nonce: 0n, + preVerificationGas: 6942069n, + sender: '0x1234567890123456789012345678901234567890', + signature: '0x', + verificationGasLimit: 6942069n, + paymaster: '0x1234567890123456789012345678901234567890', + }, + }), + ).toMatchInlineSnapshot( + `"0x364bff8f9104a3854dce4f61f8479ce3019a3bd23e1c8db4da0d7c22850835b9"`, + ) + }) + + test('args: paymasterVerificationGasLimit', () => { + expect( + getUserOperationHash({ + chainId: 1, + entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', + entryPointVersion: '0.8', + userOperation: { + callData: '0x', + callGasLimit: 6942069n, + maxFeePerGas: 69420n, + maxPriorityFeePerGas: 69n, + nonce: 0n, + preVerificationGas: 6942069n, + sender: '0x1234567890123456789012345678901234567890', + signature: '0x', + verificationGasLimit: 6942069n, + paymaster: '0x1234567890123456789012345678901234567890', + paymasterVerificationGasLimit: 6942069n, + }, + }), + ).toMatchInlineSnapshot( + `"0x9cb735eb4278caf0a9f53ad81e5f592e965c9d034f6e2780befa4cd09a990b04"`, + ) + }) + + test('args: paymasterPostOpGasLimit', () => { + expect( + getUserOperationHash({ + chainId: 1, + entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', + entryPointVersion: '0.8', + userOperation: { + callData: '0x', + callGasLimit: 6942069n, + maxFeePerGas: 69420n, + maxPriorityFeePerGas: 69n, + nonce: 0n, + preVerificationGas: 6942069n, + sender: '0x1234567890123456789012345678901234567890', + signature: '0x', + verificationGasLimit: 6942069n, + paymaster: '0x1234567890123456789012345678901234567890', + paymasterVerificationGasLimit: 6942069n, + paymasterPostOpGasLimit: 6942069n, + }, + }), + ).toMatchInlineSnapshot( + `"0x745602113988c3a6f18215d96eecc85775998dcb190e374a0955e637d40fe018"`, + ) + }) + + test('args: paymasterData', () => { + expect( + getUserOperationHash({ + chainId: 1, + entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', + entryPointVersion: '0.8', + userOperation: { + 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', + }, + }), + ).toMatchInlineSnapshot( + `"0xf10ef9afcf27a4fd17e477cd19f37e588e3ff7d48eade07ab5b7eb8caf75667f"`, + ) + }) + + test('args: eip7702', () => { + expect( + getUserOperationHash({ + chainId: 1, + entryPointAddress: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', + entryPointVersion: '0.8', + userOperation: { + 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: 0, + yParity: 0, + r: '0x0000000000000000000000000000000000000000000000000000000000000000', + s: '0x0000000000000000000000000000000000000000000000000000000000000000', + }, + }, + }), + ).toMatchInlineSnapshot( + `"0xd96232eb5d02f483166b9b23dca3ec2b963d70f09b961fce348c51d306278462"`, + ) + }) +}) + describe('entryPoint: 0.7', () => { test('default', () => { expect( diff --git a/src/account-abstraction/utils/userOperation/getUserOperationHash.ts b/src/account-abstraction/utils/userOperation/getUserOperationHash.ts index 7f4bae965c..148fb4862a 100644 --- a/src/account-abstraction/utils/userOperation/getUserOperationHash.ts +++ b/src/account-abstraction/utils/userOperation/getUserOperationHash.ts @@ -1,10 +1,12 @@ import type { Address } from 'abitype' import type { Hash } from '../../../types/misc.js' import { encodeAbiParameters } from '../../../utils/abi/encodeAbiParameters.js' +import { encodePacked } from '../../../utils/abi/encodePacked.js' import { concat } from '../../../utils/data/concat.js' import { pad } from '../../../utils/data/pad.js' import { numberToHex } from '../../../utils/encoding/toHex.js' import { keccak256 } from '../../../utils/hash/keccak256.js' +import { hashTypedData } from '../../../utils/signature/hashTypedData.js' import type { EntryPointVersion } from '../../types/entryPointVersion.js' import type { UserOperation } from '../../types/userOperation.js' @@ -126,9 +128,86 @@ export function getUserOperationHash< ) } + if (entryPointVersion === '0.8') { + const isEip7702 = + userOperation.factory && + userOperation.factory === '0x7702' && + userOperation.authorization + + const delegation = isEip7702 + ? userOperation.authorization?.address + : undefined + + const initCode = delegation + ? userOperation.factoryData + ? encodePacked( + ['address', 'bytes'], + [delegation, userOperation.factoryData], + ) + : encodePacked(['address'], [delegation]) + : userOperation.factory && userOperation.factoryData + ? concat([userOperation.factory, userOperation.factoryData]) + : '0x' + + const accountGasLimits = concat([ + pad(numberToHex(verificationGasLimit), { size: 16 }), + pad(numberToHex(callGasLimit), { size: 16 }), + ]) + + const gasFees = concat([ + pad(numberToHex(maxPriorityFeePerGas), { size: 16 }), + pad(numberToHex(maxFeePerGas), { size: 16 }), + ]) + + const paymasterAndData = userOperation.paymaster + ? concat([ + userOperation.paymaster, + pad(numberToHex(userOperation.paymasterVerificationGasLimit || 0), { + size: 16, + }), + pad(numberToHex(userOperation.paymasterPostOpGasLimit || 0), { + size: 16, + }), + userOperation.paymasterData || '0x', + ]) + : '0x' + + return hashTypedData({ + types: { + PackedUserOperation: [ + { type: 'address', name: 'sender' }, + { type: 'uint256', name: 'nonce' }, + { type: 'bytes', name: 'initCode' }, + { type: 'bytes', name: 'callData' }, + { type: 'bytes32', name: 'accountGasLimits' }, + { type: 'uint256', name: 'preVerificationGas' }, + { type: 'bytes32', name: 'gasFees' }, + { type: 'bytes', name: 'paymasterAndData' }, + ], + }, + primaryType: 'PackedUserOperation', + domain: { + name: 'ERC4337', + version: '1', + chainId, + verifyingContract: entryPointAddress, + }, + message: { + sender, + nonce, + initCode, + callData, + accountGasLimits, + preVerificationGas, + gasFees, + paymasterAndData, + }, + }) + } throw new Error(`entryPointVersion "${entryPointVersion}" not supported.`) })() + if (entryPointVersion === '0.8') return packedUserOp return keccak256( encodeAbiParameters( [{ type: 'bytes32' }, { type: 'address' }, { type: 'uint256' }], diff --git a/src/actions/public/call.test.ts b/src/actions/public/call.test.ts index 3255d6ba38..2825a0b32f 100644 --- a/src/actions/public/call.test.ts +++ b/src/actions/public/call.test.ts @@ -423,14 +423,14 @@ describe('errors', () => { }), ).rejects.toThrowErrorMatchingInlineSnapshot( ` - [CallExecutionError: Execution reverted with reason: Token ID is taken. + [CallExecutionError: Execution reverted with reason: revert: Token ID is taken.. Raw Call Arguments: from: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 to: 0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2 data: 0xa0712d6800000000000000000000000000000000000000000000000000000000000001a4 - Details: execution reverted: Token ID is taken + Details: execution reverted: revert: Token ID is taken Version: viem@x.y.z] `, ) diff --git a/src/constants/address.ts b/src/constants/address.ts index c417c0182e..423736d609 100644 --- a/src/constants/address.ts +++ b/src/constants/address.ts @@ -2,6 +2,8 @@ export const entryPoint06Address = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789' as const export const entryPoint07Address = '0x0000000071727De22E5E9d8BAf0edAc6f37da032' as const +export const entryPoint08Address = + '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108' as const export const ethAddress = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' as const diff --git a/src/types/authorization.ts b/src/types/authorization.ts index 6fa1a4c0a4..8b3568ca30 100644 --- a/src/types/authorization.ts +++ b/src/types/authorization.ts @@ -9,7 +9,7 @@ export type Authorization = { chainId: uint32 /** Nonce of the EOA to delegate to. */ nonce: uint32 -} & (signed extends true ? Signature : ExactPartial) +} & (signed extends true ? Signature : ExactPartial>) export type AuthorizationList< uint32 = number, diff --git a/src/types/misc.ts b/src/types/misc.ts index fd26e17912..625ba085cc 100644 --- a/src/types/misc.ts +++ b/src/types/misc.ts @@ -10,26 +10,26 @@ export type SignableMessage = /** Raw data representation of the message. */ raw: Hex | ByteArray } -export type SignatureLegacy = { +export type SignatureLegacy = { r: Hex s: Hex - v: bigint + v: bigintType } -export type Signature = OneOf< +export type Signature = OneOf< | SignatureLegacy | { r: Hex s: Hex /** @deprecated use `yParity`. */ - v: bigint - yParity?: number | undefined + v: bigintType + yParity?: numberType | undefined } | { r: Hex s: Hex /** @deprecated use `yParity`. */ - v?: bigint | undefined - yParity: number + v?: bigintType | undefined + yParity: numberType } > export type CompactSignature = { diff --git a/test/src/account-abstraction.ts b/test/src/account-abstraction.ts index 2ad34469cf..2729740f59 100644 --- a/test/src/account-abstraction.ts +++ b/test/src/account-abstraction.ts @@ -1,9 +1,11 @@ +import { privateKeyToAccount } from '~viem/accounts/privateKeyToAccount.js' import { VerifyingPaymaster } from '../../contracts/generated.js' import { entryPoint06Abi, entryPoint07Address, formatUserOperation, toPackedUserOperation, + toSimple7702SmartAccount, toSoladySmartAccount, } from '../../src/account-abstraction/index.js' import { @@ -31,12 +33,40 @@ import { accounts } from './constants.js' import { createHttpServer, deploy, + deploySimple7702Account_08, deploySoladyAccount_06, deploySoladyAccount_07, } from './utils.js' const client = anvilMainnet.getClient({ account: true }) +export async function getSmartAccounts_08() { + const { implementationAddress } = await deploySimple7702Account_08() + + const accounts_ = [] + + for (const account of accounts) { + const owner = privateKeyToAccount(account.privateKey) + const account_ = await toSimple7702SmartAccount({ + client, + implementation: implementationAddress, + owner, + }) + await sendTransaction(client, { + account: accounts[9].address, + to: account_.address, + value: parseEther('100'), + }) + accounts_.push(account_) + } + + await mine(client, { + blocks: 1, + }) + + return accounts_ +} + export async function getSmartAccounts_07() { const { factoryAddress } = await deploySoladyAccount_07() diff --git a/test/src/bundler.ts b/test/src/bundler.ts index 38e47b9be0..277c263050 100644 --- a/test/src/bundler.ts +++ b/test/src/bundler.ts @@ -108,6 +108,7 @@ function defineBundler({ entrypoints: [ '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', '0x0000000071727De22E5E9d8BAf0edAc6f37da032', + '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108', ], executorPrivateKeys: [accounts[3].privateKey], utilityPrivateKey: accounts[3].privateKey, diff --git a/test/src/utils.ts b/test/src/utils.ts index 15b2902cc9..b873dcbfec 100644 --- a/test/src/utils.ts +++ b/test/src/utils.ts @@ -30,6 +30,7 @@ import { ErrorsExample, OffchainLookupExample, Payable, + Simple7702Account, SoladyAccount06, SoladyAccount07, SoladyAccountFactory06, @@ -140,6 +141,16 @@ export async function deployPayable() { }) } +export async function deploySimple7702Account_08() { + const { contractAddress: implementationAddress } = await deploy(client, { + abi: Simple7702Account.abi, + bytecode: Simple7702Account.bytecode.object, + }) + return { + implementationAddress: implementationAddress!, + } +} + export async function deploySoladyAccount_07() { const { contractAddress: implementationAddress } = await deploy(client, { abi: SoladyAccount07.abi,