From 5cca792f6efeda51e2a48e26f994f774e94e6ea9 Mon Sep 17 00:00:00 2001 From: Mike Liu Date: Wed, 3 Dec 2025 10:44:20 -0500 Subject: [PATCH 1/2] feat: entrypoint 0.9 + paymasterSignature --- .changeset/proud-doors-cheat.md | 13 + .../actions/bundler/prepareUserOperation.ts | 23 + src/account-abstraction/constants/abis.ts | 870 ++++++++++++++++++ src/account-abstraction/constants/address.ts | 2 + src/account-abstraction/index.ts | 2 + .../types/entryPointVersion.ts | 2 +- .../types/userOperation.ts | 60 ++ .../utils/formatters/userOperationRequest.ts | 2 + .../getUserOperationHash.test.ts | 217 +++++ .../userOperation/getUserOperationHash.ts | 2 +- .../getUserOperationTypedData.ts | 2 +- .../userOperation/toPackedUserOperation.ts | 6 +- src/constants/address.ts | 2 + 13 files changed, 1199 insertions(+), 4 deletions(-) create mode 100644 .changeset/proud-doors-cheat.md diff --git a/.changeset/proud-doors-cheat.md b/.changeset/proud-doors-cheat.md new file mode 100644 index 0000000000..72c0674fd3 --- /dev/null +++ b/.changeset/proud-doors-cheat.md @@ -0,0 +1,13 @@ +--- +"viem": minor +--- + +Added EntryPoint v0.9 support for Account Abstraction (ERC-4337). + +**Features:** +- Added `entryPoint09Abi` and `entryPoint09Address` constants +- Added `'0.9'` to `EntryPointVersion` type +- Added `UserOperation<'0.9'>` with new `paymasterSignature` field for parallelizable paymaster signing +- Updated `getUserOperationHash` to support v0.9 (uses EIP-712 typed data like v0.8) +- Updated `toPackedUserOperation` to handle `paymasterSignature` +- Updated `prepareUserOperation` type definitions for v0.9 diff --git a/src/account-abstraction/actions/bundler/prepareUserOperation.ts b/src/account-abstraction/actions/bundler/prepareUserOperation.ts index d302ebfd78..64e2e25079 100644 --- a/src/account-abstraction/actions/bundler/prepareUserOperation.ts +++ b/src/account-abstraction/actions/bundler/prepareUserOperation.ts @@ -80,6 +80,12 @@ export type PrepareUserOperationParameterType = type FactoryProperties< entryPointVersion extends EntryPointVersion = EntryPointVersion, > = + | (entryPointVersion extends '0.9' + ? { + factory: UserOperation['factory'] + factoryData: UserOperation['factoryData'] + } + : never) | (entryPointVersion extends '0.8' ? { factory: UserOperation['factory'] @@ -101,6 +107,15 @@ type FactoryProperties< type GasProperties< entryPointVersion extends EntryPointVersion = EntryPointVersion, > = + | (entryPointVersion extends '0.9' + ? { + callGasLimit: UserOperation['callGasLimit'] + preVerificationGas: UserOperation['preVerificationGas'] + verificationGasLimit: UserOperation['verificationGasLimit'] + paymasterPostOpGasLimit: UserOperation['paymasterPostOpGasLimit'] + paymasterVerificationGasLimit: UserOperation['paymasterVerificationGasLimit'] + } + : never) | (entryPointVersion extends '0.8' ? { callGasLimit: UserOperation['callGasLimit'] @@ -139,6 +154,14 @@ type NonceProperties = { type PaymasterProperties< entryPointVersion extends EntryPointVersion = EntryPointVersion, > = + | (entryPointVersion extends '0.9' + ? { + paymaster: UserOperation['paymaster'] + paymasterData: UserOperation['paymasterData'] + paymasterPostOpGasLimit: UserOperation['paymasterPostOpGasLimit'] + paymasterVerificationGasLimit: UserOperation['paymasterVerificationGasLimit'] + } + : never) | (entryPointVersion extends '0.8' ? { paymaster: UserOperation['paymaster'] diff --git a/src/account-abstraction/constants/abis.ts b/src/account-abstraction/constants/abis.ts index 5ea816997b..4a69b42b34 100644 --- a/src/account-abstraction/constants/abis.ts +++ b/src/account-abstraction/constants/abis.ts @@ -2093,3 +2093,873 @@ export const entryPoint08Abi = [ }, { stateMutability: 'payable', type: 'receive' }, ] as const + +export const entryPoint09Abi = [ + { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, + { + inputs: [ + { internalType: 'bool', name: 'success', type: 'bool' }, + { internalType: 'bytes', name: 'ret', type: 'bytes' }, + ], + name: 'DelegateAndRevert', + type: 'error', + }, + { + inputs: [ + { internalType: 'address', name: 'account', type: 'address' }, + { internalType: 'address', name: 'withdrawAddress', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { internalType: 'bytes', name: 'revertReason', type: 'bytes' }, + ], + name: 'DepositWithdrawalFailed', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'sender', type: 'address' }], + name: 'Eip7702SenderNotDelegate', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'sender', type: 'address' }], + name: 'Eip7702SenderWithoutCode', + 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: [ + { internalType: 'address', name: 'beneficiary', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { internalType: 'bytes', name: 'revertData', type: 'bytes' }, + ], + name: 'FailedSendToBeneficiary', + type: 'error', + }, + { + inputs: [ + { internalType: 'uint256', name: 'currentDeposit', type: 'uint256' }, + { internalType: 'uint256', name: 'withdrawAmount', type: 'uint256' }, + ], + name: 'InsufficientDeposit', + type: 'error', + }, + { inputs: [], name: 'InternalFunction', type: 'error' }, + { + inputs: [{ internalType: 'address', name: 'beneficiary', type: 'address' }], + name: 'InvalidBeneficiary', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'paymaster', type: 'address' }], + name: 'InvalidPaymaster', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'paymasterAndDataLength', + type: 'uint256', + }, + ], + name: 'InvalidPaymasterData', + type: 'error', + }, + { + inputs: [ + { internalType: 'uint256', name: 'dataLength', type: 'uint256' }, + { internalType: 'uint256', name: 'pmSignatureLength', type: 'uint256' }, + ], + name: 'InvalidPaymasterSignatureLength', + type: 'error', + }, + { inputs: [], name: 'InvalidShortString', type: 'error' }, + { + inputs: [ + { internalType: 'uint256', name: 'msgValue', type: 'uint256' }, + { internalType: 'uint256', name: 'currentStake', type: 'uint256' }, + ], + name: 'InvalidStake', + type: 'error', + }, + { + inputs: [ + { internalType: 'uint256', name: 'newUnstakeDelaySec', type: 'uint256' }, + { + internalType: 'uint256', + name: 'currentUnstakeDelaySec', + type: 'uint256', + }, + ], + name: 'InvalidUnstakeDelay', + type: 'error', + }, + { + inputs: [ + { internalType: 'uint256', name: 'currentStake', type: 'uint256' }, + { internalType: 'uint256', name: 'unstakeDelaySec', type: 'uint256' }, + { internalType: 'bool', name: 'staked', type: 'bool' }, + ], + name: 'NotStaked', + type: 'error', + }, + { + inputs: [{ internalType: 'bytes', name: 'returnData', type: 'bytes' }], + name: 'PostOpReverted', + type: 'error', + }, + { inputs: [], name: 'Reentrancy', 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: 'uint256', name: 'withdrawTime', type: 'uint256' }, + { internalType: 'uint256', name: 'blockTimestamp', type: 'uint256' }, + ], + name: 'StakeNotUnlocked', + type: 'error', + }, + { + inputs: [ + { internalType: 'address', name: 'account', type: 'address' }, + { internalType: 'address', name: 'withdrawAddress', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { internalType: 'bytes', name: 'revertReason', type: 'bytes' }, + ], + name: 'StakeWithdrawalFailed', + type: 'error', + }, + { + inputs: [{ internalType: 'string', name: 'str', type: 'string' }], + name: 'StringTooLong', + type: 'error', + }, + { + inputs: [ + { internalType: 'uint256', name: 'withdrawTime', type: 'uint256' }, + { internalType: 'uint256', name: 'blockTimestamp', type: 'uint256' }, + ], + name: 'WithdrawalNotDue', + 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: true, + internalType: 'address', + name: 'delegate', + type: 'address', + }, + ], + name: 'EIP7702AccountInitialized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'userOpHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'unusedFactory', + type: 'address', + }, + ], + name: 'IgnoredInitCode', + 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: [], + name: 'getCurrentUserOpHash', + outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }], + 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 ec5cac696e..7a5957029c 100644 --- a/src/account-abstraction/constants/address.ts +++ b/src/account-abstraction/constants/address.ts @@ -4,3 +4,5 @@ export const entryPoint07Address = '0x0000000071727De22E5E9d8BAf0edAc6f37da032' as const export const entryPoint08Address = '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108' as const +export const entryPoint09Address = + '0x433709009B8330FDa32311DF1C2AFA402eD8D009' as const diff --git a/src/account-abstraction/index.ts b/src/account-abstraction/index.ts index b77f296348..b3be9ab550 100644 --- a/src/account-abstraction/index.ts +++ b/src/account-abstraction/index.ts @@ -122,11 +122,13 @@ export { entryPoint06Abi, entryPoint07Abi, entryPoint08Abi, + entryPoint09Abi, } from './constants/abis.js' export { entryPoint06Address, entryPoint07Address, entryPoint08Address, + entryPoint09Address, } from './constants/address.js' export { diff --git a/src/account-abstraction/types/entryPointVersion.ts b/src/account-abstraction/types/entryPointVersion.ts index ce968db10d..ae47e94249 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' | '0.8' +export type EntryPointVersion = '0.6' | '0.7' | '0.8' | '0.9' export type DeriveEntryPointVersion = account extends SmartAccount diff --git a/src/account-abstraction/types/userOperation.ts b/src/account-abstraction/types/userOperation.ts index c3b11f36de..fb325e437e 100644 --- a/src/account-abstraction/types/userOperation.ts +++ b/src/account-abstraction/types/userOperation.ts @@ -11,6 +11,15 @@ export type EstimateUserOperationGasReturnType< entryPointVersion extends EntryPointVersion = EntryPointVersion, uint256 = bigint, > = OneOf< + | (entryPointVersion extends '0.9' + ? { + preVerificationGas: uint256 + verificationGasLimit: uint256 + callGasLimit: uint256 + paymasterVerificationGasLimit?: uint256 | undefined + paymasterPostOpGasLimit?: uint256 | undefined + } + : never) | (entryPointVersion extends '0.8' ? { preVerificationGas: uint256 @@ -79,6 +88,44 @@ export type UserOperation< uint256 = bigint, uint32 = number, > = OneOf< + | (entryPointVersion extends '0.9' + ? { + /** 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 + /** Paymaster signature. Can be provided separately for parallelizable signing. */ + paymasterSignature?: Hex | 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.8' ? { /** Authorization data. */ @@ -186,6 +233,19 @@ export type UserOperationRequest< uint256 = bigint, uint32 = number, > = OneOf< + | (entryPointVersion extends '0.9' + ? UnionPartialBy< + UserOperation<'0.9', uint256, uint32>, + // We are able to calculate these via `prepareUserOperation`. + | keyof EstimateUserOperationGasReturnType<'0.9'> + | 'callData' + | 'maxFeePerGas' + | 'maxPriorityFeePerGas' + | 'nonce' + | 'sender' + | 'signature' + > + : never) | (entryPointVersion extends '0.8' ? UnionPartialBy< UserOperation<'0.8', uint256, uint32>, diff --git a/src/account-abstraction/utils/formatters/userOperationRequest.ts b/src/account-abstraction/utils/formatters/userOperationRequest.ts index a817e2114d..67e9a89c4d 100644 --- a/src/account-abstraction/utils/formatters/userOperationRequest.ts +++ b/src/account-abstraction/utils/formatters/userOperationRequest.ts @@ -39,6 +39,8 @@ export function formatUserOperationRequest( rpcRequest.paymasterPostOpGasLimit = numberToHex( request.paymasterPostOpGasLimit, ) + if (typeof request.paymasterSignature !== 'undefined') + rpcRequest.paymasterSignature = request.paymasterSignature if (typeof request.paymasterVerificationGasLimit !== 'undefined') rpcRequest.paymasterVerificationGasLimit = numberToHex( request.paymasterVerificationGasLimit, diff --git a/src/account-abstraction/utils/userOperation/getUserOperationHash.test.ts b/src/account-abstraction/utils/userOperation/getUserOperationHash.test.ts index a43cb2c133..647166b6ec 100644 --- a/src/account-abstraction/utils/userOperation/getUserOperationHash.test.ts +++ b/src/account-abstraction/utils/userOperation/getUserOperationHash.test.ts @@ -224,6 +224,223 @@ describe('entryPoint: 0.8', () => { }) }) +describe('entryPoint: 0.9', () => { + test('default', () => { + expect( + getUserOperationHash({ + chainId: 1, + entryPointAddress: '0x433709009B8330FDa32311DF1C2AFA402eD8D009', + entryPointVersion: '0.9', + userOperation: { + callData: '0x', + callGasLimit: 6942069n, + maxFeePerGas: 69420n, + maxPriorityFeePerGas: 69n, + nonce: 0n, + preVerificationGas: 6942069n, + sender: '0x1234567890123456789012345678901234567890', + signature: '0x', + verificationGasLimit: 6942069n, + }, + }), + ).toMatchInlineSnapshot( + `"0x5fea5504ddf594de3c1164430244ca36b8f8272d9ae391660be481a2c066fdf3"`, + ) + }) + + test('args: factory + factoryData', () => { + expect( + getUserOperationHash({ + chainId: 1, + entryPointAddress: '0x433709009B8330FDa32311DF1C2AFA402eD8D009', + entryPointVersion: '0.9', + userOperation: { + callData: '0x', + callGasLimit: 6942069n, + maxFeePerGas: 69420n, + maxPriorityFeePerGas: 69n, + nonce: 0n, + preVerificationGas: 6942069n, + sender: '0x1234567890123456789012345678901234567890', + signature: '0x', + verificationGasLimit: 6942069n, + factory: '0x1234567890123456789012345678901234567890', + factoryData: '0xdeadbeef', + }, + }), + ).toMatchInlineSnapshot( + `"0x73f57975f825911aa8a5c883b5df235c6b6d50a196f7f52de40d5bca3c8558ae"`, + ) + }) + + test('args: paymaster', () => { + expect( + getUserOperationHash({ + chainId: 1, + entryPointAddress: '0x433709009B8330FDa32311DF1C2AFA402eD8D009', + entryPointVersion: '0.9', + userOperation: { + callData: '0x', + callGasLimit: 6942069n, + maxFeePerGas: 69420n, + maxPriorityFeePerGas: 69n, + nonce: 0n, + preVerificationGas: 6942069n, + sender: '0x1234567890123456789012345678901234567890', + signature: '0x', + verificationGasLimit: 6942069n, + paymaster: '0x1234567890123456789012345678901234567890', + }, + }), + ).toMatchInlineSnapshot( + `"0x61123074468024bd8c8f864f1999f972353816fe66d3041ea832cf4d04420f81"`, + ) + }) + + test('args: paymasterVerificationGasLimit', () => { + expect( + getUserOperationHash({ + chainId: 1, + entryPointAddress: '0x433709009B8330FDa32311DF1C2AFA402eD8D009', + entryPointVersion: '0.9', + userOperation: { + callData: '0x', + callGasLimit: 6942069n, + maxFeePerGas: 69420n, + maxPriorityFeePerGas: 69n, + nonce: 0n, + preVerificationGas: 6942069n, + sender: '0x1234567890123456789012345678901234567890', + signature: '0x', + verificationGasLimit: 6942069n, + paymaster: '0x1234567890123456789012345678901234567890', + paymasterVerificationGasLimit: 6942069n, + }, + }), + ).toMatchInlineSnapshot( + `"0xc2af022040ee744ac33783a7985ee34fe3cf56a412ed65deea5b5a244866ebd5"`, + ) + }) + + test('args: paymasterPostOpGasLimit', () => { + expect( + getUserOperationHash({ + chainId: 1, + entryPointAddress: '0x433709009B8330FDa32311DF1C2AFA402eD8D009', + entryPointVersion: '0.9', + 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( + `"0x6839a4373751bb9d4b1acf2c77ff756067d5fa345b9943e8230fc60d0544f5d5"`, + ) + }) + + test('args: paymasterData', () => { + expect( + getUserOperationHash({ + chainId: 1, + entryPointAddress: '0x433709009B8330FDa32311DF1C2AFA402eD8D009', + entryPointVersion: '0.9', + 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( + `"0xde381ae0368b77df677808c3a2da72abd68a07eab11ef31c35923f56a75bbe6d"`, + ) + }) + + test('args: paymasterSignature (0.9 feature)', () => { + expect( + getUserOperationHash({ + chainId: 1, + entryPointAddress: '0x433709009B8330FDa32311DF1C2AFA402eD8D009', + entryPointVersion: '0.9', + 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', + paymasterSignature: '0xcafebabe', + }, + }), + ).toMatchInlineSnapshot( + `"0x9a538c5deb10298bcc09baa188099e5a3935ec20d92f211a1d838ab214b260ba"`, + ) + }) + + test('args: authorization', () => { + expect( + getUserOperationHash({ + chainId: 1, + entryPointAddress: '0x433709009B8330FDa32311DF1C2AFA402eD8D009', + entryPointVersion: '0.9', + 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( + `"0x20993875bc385b402072201a31152ff8bfd6a53cf37607fbc68f1bdabff80a1f"`, + ) + }) +}) + 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 8b407d23d0..5770a51f53 100644 --- a/src/account-abstraction/utils/userOperation/getUserOperationHash.ts +++ b/src/account-abstraction/utils/userOperation/getUserOperationHash.ts @@ -41,7 +41,7 @@ export function getUserOperationHash< verificationGasLimit, } = userOperation - if (entryPointVersion === '0.8') + if (entryPointVersion === '0.8' || entryPointVersion === '0.9') return hashTypedData( getUserOperationTypedData({ chainId, diff --git a/src/account-abstraction/utils/userOperation/getUserOperationTypedData.ts b/src/account-abstraction/utils/userOperation/getUserOperationTypedData.ts index 8c8366e751..c8aafadb28 100644 --- a/src/account-abstraction/utils/userOperation/getUserOperationTypedData.ts +++ b/src/account-abstraction/utils/userOperation/getUserOperationTypedData.ts @@ -7,7 +7,7 @@ import { toPackedUserOperation } from './toPackedUserOperation.js' export type GetUserOperationTypedDataParameters = { chainId: number entryPointAddress: Address - userOperation: UserOperation<'0.8'> + userOperation: UserOperation<'0.8'> | UserOperation<'0.9'> } export type GetUserOperationTypedDataReturnType = TypedDataDefinition< diff --git a/src/account-abstraction/utils/userOperation/toPackedUserOperation.ts b/src/account-abstraction/utils/userOperation/toPackedUserOperation.ts index f9bccafd84..d4e3611cd0 100644 --- a/src/account-abstraction/utils/userOperation/toPackedUserOperation.ts +++ b/src/account-abstraction/utils/userOperation/toPackedUserOperation.ts @@ -18,11 +18,12 @@ export function toPackedUserOperation( paymaster, paymasterData, paymasterPostOpGasLimit, + paymasterSignature, paymasterVerificationGasLimit, sender, signature = '0x', verificationGasLimit, - } = userOperation + } = userOperation as UserOperation & { paymasterSignature?: string } const accountGasLimits = concat([ pad(numberToHex(verificationGasLimit || 0n), { size: 16 }), @@ -34,6 +35,8 @@ export function toPackedUserOperation( pad(numberToHex(maxFeePerGas || 0n), { size: 16 }), ]) const nonce = userOperation.nonce ?? 0n + + // For v0.9, paymasterSignature can be provided separately and appended after paymasterData const paymasterAndData = paymaster ? concat([ paymaster, @@ -44,6 +47,7 @@ export function toPackedUserOperation( size: 16, }), paymasterData || '0x', + ...(paymasterSignature ? [paymasterSignature as `0x${string}`] : []), ]) : '0x' const preVerificationGas = userOperation.preVerificationGas ?? 0n diff --git a/src/constants/address.ts b/src/constants/address.ts index 423736d609..70ea34369f 100644 --- a/src/constants/address.ts +++ b/src/constants/address.ts @@ -4,6 +4,8 @@ export const entryPoint07Address = '0x0000000071727De22E5E9d8BAf0edAc6f37da032' as const export const entryPoint08Address = '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108' as const +export const entryPoint09Address = + '0x433709009B8330FDa32311DF1C2AFA402eD8D009' as const export const ethAddress = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' as const From 04c49b5b8699b1d9b8781fa19c6a583414fa0543 Mon Sep 17 00:00:00 2001 From: jxom <7336481+jxom@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:42:08 +1100 Subject: [PATCH 2/2] Update proud-doors-cheat.md --- .changeset/proud-doors-cheat.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/proud-doors-cheat.md b/.changeset/proud-doors-cheat.md index 72c0674fd3..5876f6a9e2 100644 --- a/.changeset/proud-doors-cheat.md +++ b/.changeset/proud-doors-cheat.md @@ -4,7 +4,6 @@ Added EntryPoint v0.9 support for Account Abstraction (ERC-4337). -**Features:** - Added `entryPoint09Abi` and `entryPoint09Address` constants - Added `'0.9'` to `EntryPointVersion` type - Added `UserOperation<'0.9'>` with new `paymasterSignature` field for parallelizable paymaster signing