UNPKG

ox

Version:

Ethereum Standard Library

944 lines (885 loc) 28.8 kB
import * as AbiParameters from '../core/AbiParameters.js' import type * as Address from '../core/Address.js' import type * as Authorization from '../core/Authorization.js' import type * as Errors from '../core/Errors.js' import * as Hash from '../core/Hash.js' import * as Hex from '../core/Hex.js' import type { Assign, Compute, OneOf } from '../core/internal/types.js' import * as Signature from '../core/Signature.js' import * as TypedData from '../core/TypedData.js' import type * as EntryPoint from './EntryPoint.js' /** User Operation. */ export type UserOperation< entryPointVersion extends EntryPoint.Version = EntryPoint.Version, signed extends boolean = boolean, bigintType = bigint, numberType = number, > = OneOf< | (entryPointVersion extends '0.6' ? V06<signed, bigintType> : never) | (entryPointVersion extends '0.7' ? V07<signed, bigintType> : never) | (entryPointVersion extends '0.8' ? V08<signed, bigintType, numberType> : never) > /** * Packed User Operation. * * @see https://eips.ethereum.org/EIPS/eip-4337#entrypoint-definition */ export type Packed = { /** Concatenation of `verificationGasLimit` (16 bytes) and `callGasLimit` (16 bytes) */ accountGasLimits: Hex.Hex /** The data to pass to the `sender` during the main execution call. */ callData: Hex.Hex /** Concatenation of `factory` and `factoryData`. */ initCode: Hex.Hex /** Concatenation of `maxPriorityFee` (16 bytes) and `maxFeePerGas` (16 bytes) */ gasFees: Hex.Hex /** Anti-replay parameter. */ nonce: bigint /** Concatenation of paymaster fields (or empty). */ paymasterAndData: Hex.Hex /** Extra gas to pay the Bundler. */ preVerificationGas: bigint /** The account making the operation. */ sender: Address.Address /** Data passed into the account to verify authorization. */ signature: Hex.Hex } /** RPC User Operation type. */ export type Rpc< entryPointVersion extends EntryPoint.Version = EntryPoint.Version, signed extends boolean = true, > = OneOf< | (entryPointVersion extends '0.6' ? V06<signed, Hex.Hex> : never) | (entryPointVersion extends '0.7' ? V07<signed, Hex.Hex> : never) | (entryPointVersion extends '0.8' ? V08<signed, Hex.Hex, Hex.Hex> : never) > /** Transaction Info. */ export type TransactionInfo< entryPointVersion extends EntryPoint.Version = EntryPoint.Version, bigintType = bigint, > = { blockHash: Hex.Hex blockNumber: bigintType entryPoint: Address.Address transactionHash: Hex.Hex userOperation: UserOperation<entryPointVersion, true, bigintType> } /** RPC Transaction Info. */ export type RpcTransactionInfo< entryPointVersion extends EntryPoint.Version = EntryPoint.Version, > = TransactionInfo<entryPointVersion, Hex.Hex> /** Type for User Operation on EntryPoint 0.6 */ export type V06<signed extends boolean = boolean, bigintType = bigint> = { /** The data to pass to the `sender` during the main execution call. */ callData: Hex.Hex /** The amount of gas to allocate the main execution call */ callGasLimit: bigintType /** Account init code. Only for new accounts. */ initCode?: Hex.Hex | undefined /** Maximum fee per gas. */ maxFeePerGas: bigintType /** Maximum priority fee per gas. */ maxPriorityFeePerGas: bigintType /** Anti-replay parameter. */ nonce: bigintType /** Paymaster address with calldata. */ paymasterAndData?: Hex.Hex | undefined /** Extra gas to pay the Bundler. */ preVerificationGas: bigintType /** The account making the operation. */ sender: Address.Address /** Data passed into the account to verify authorization. */ signature?: Hex.Hex | undefined /** The amount of gas to allocate for the verification step. */ verificationGasLimit: bigintType } & (signed extends true ? { signature: Hex.Hex } : {}) /** RPC User Operation on EntryPoint 0.6 */ export type RpcV06<signed extends boolean = true> = V06<signed, Hex.Hex> /** Type for User Operation on EntryPoint 0.7 */ export type V07<signed extends boolean = boolean, bigintType = bigint> = { /** The data to pass to the `sender` during the main execution call. */ callData: Hex.Hex /** The amount of gas to allocate the main execution call */ callGasLimit: bigintType /** Account factory. Only for new accounts. */ factory?: Address.Address | undefined /** Data for account factory. */ factoryData?: Hex.Hex | undefined /** Maximum fee per gas. */ maxFeePerGas: bigintType /** Maximum priority fee per gas. */ maxPriorityFeePerGas: bigintType /** Anti-replay parameter. */ nonce: bigintType /** Address of paymaster contract. */ paymaster?: Address.Address | undefined /** Data for paymaster. */ paymasterData?: Hex.Hex | undefined /** The amount of gas to allocate for the paymaster post-operation code. */ paymasterPostOpGasLimit?: bigintType | undefined /** The amount of gas to allocate for the paymaster validation code. */ paymasterVerificationGasLimit?: bigintType | undefined /** Extra gas to pay the Bundler. */ preVerificationGas: bigintType /** The account making the operation. */ sender: Address.Address /** Data passed into the account to verify authorization. */ signature?: Hex.Hex | undefined /** The amount of gas to allocate for the verification step. */ verificationGasLimit: bigintType } & (signed extends true ? { signature: Hex.Hex } : {}) /** RPC User Operation on EntryPoint 0.7 */ export type RpcV07<signed extends boolean = true> = V07<signed, Hex.Hex> /** Type for User Operation on EntryPoint 0.8 */ export type V08< signed extends boolean = boolean, bigintType = bigint, numberType = number, > = { /** Authorization data. */ authorization?: Authorization.Signed<bigintType, numberType> | undefined /** The data to pass to the `sender` during the main execution call. */ callData: Hex.Hex /** The amount of gas to allocate the main execution call */ callGasLimit: bigintType /** Account factory. Only for new accounts. */ factory?: Address.Address | undefined /** Data for account factory. */ factoryData?: Hex.Hex | undefined /** Maximum fee per gas. */ maxFeePerGas: bigintType /** Maximum priority fee per gas. */ maxPriorityFeePerGas: bigintType /** Anti-replay parameter. */ nonce: bigintType /** Address of paymaster contract. */ paymaster?: Address.Address | undefined /** Data for paymaster. */ paymasterData?: Hex.Hex | undefined /** The amount of gas to allocate for the paymaster post-operation code. */ paymasterPostOpGasLimit?: bigintType | undefined /** The amount of gas to allocate for the paymaster validation code. */ paymasterVerificationGasLimit?: bigintType | undefined /** Extra gas to pay the Bundler. */ preVerificationGas: bigintType /** The account making the operation. */ sender: Address.Address /** Data passed into the account to verify authorization. */ signature?: Hex.Hex | undefined /** The amount of gas to allocate for the verification step. */ verificationGasLimit: bigintType } & (signed extends true ? { signature: Hex.Hex } : {}) /** RPC User Operation on EntryPoint 0.8 */ export type RpcV08<signed extends boolean = true> = V08< signed, Hex.Hex, Hex.Hex > /** * Instantiates a {@link ox#UserOperation.UserOperation} from a provided input. * * @example * ```ts twoslash * import { Value } from 'ox' * import { UserOperation } from 'ox/erc4337' * * const userOperation = UserOperation.from({ * callData: '0xdeadbeef', * callGasLimit: 300_000n, * maxFeePerGas: Value.fromGwei('20'), * maxPriorityFeePerGas: Value.fromGwei('2'), * nonce: 69n, * preVerificationGas: 100_000n, * sender: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357', * verificationGasLimit: 100_000n, * }) * ``` * * @example * ### From Packed User Operation * * ```ts twoslash * import { UserOperation } from 'ox/erc4337' * * const packed: UserOperation.Packed = { * accountGasLimits: '0x...', * callData: '0xdeadbeef', * initCode: '0x', * gasFees: '0x...', * nonce: 69n, * paymasterAndData: '0x', * preVerificationGas: 100_000n, * sender: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357', * signature: '0x', * } * * const userOperation = UserOperation.from(packed) * ``` * * @example * ### Attaching Signatures * * ```ts twoslash * import { Secp256k1, Value } from 'ox' * import { UserOperation } from 'ox/erc4337' * * const userOperation = UserOperation.from({ * callData: '0xdeadbeef', * callGasLimit: 300_000n, * maxFeePerGas: Value.fromGwei('20'), * maxPriorityFeePerGas: Value.fromGwei('2'), * nonce: 69n, * preVerificationGas: 100_000n, * sender: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357', * verificationGasLimit: 100_000n, * }) * * const payload = UserOperation.getSignPayload(userOperation, { * chainId: 1, * entryPointAddress: '0x1234567890123456789012345678901234567890', * entryPointVersion: '0.7', * }) * * const signature = Secp256k1.sign({ payload, privateKey: '0x...' }) * * const userOperation_signed = UserOperation.from(userOperation, { signature }) // [!code focus] * ``` * * @param userOperation - The user operation to instantiate (structured or packed format). * @returns User Operation. */ export function from< const userOperation extends UserOperation | Packed, const signature extends Hex.Hex | undefined = undefined, >( userOperation: userOperation | UserOperation | Packed, options: from.Options<signature> = {}, ): from.ReturnType<userOperation, signature> { const signature = (() => { if (typeof options.signature === 'string') return options.signature if (typeof options.signature === 'object') return Signature.toHex(options.signature) if (userOperation.signature) return userOperation.signature return undefined })() const packed = 'accountGasLimits' in userOperation && 'gasFees' in userOperation const userOp = packed ? fromPacked(userOperation) : userOperation return { ...userOp, signature } as never } export declare namespace from { export type Options< signature extends Signature.Signature | Hex.Hex | undefined = undefined, > = { signature?: signature | Signature.Signature | Hex.Hex | undefined } export type ReturnType< userOperation extends UserOperation | Packed = UserOperation | Packed, signature extends Signature.Signature | Hex.Hex | undefined = undefined, > = Compute< Assign< userOperation, signature extends Signature.Signature | Hex.Hex ? Readonly<{ signature: Hex.Hex }> : {} > > export type ErrorType = Errors.GlobalErrorType } /** * Converts an {@link ox#UserOperation.Rpc} to an {@link ox#UserOperation.UserOperation}. * * @example * ```ts twoslash * import { UserOperation } from 'ox/erc4337' * * const userOperation = UserOperation.fromRpc({ * callData: '0xdeadbeef', * callGasLimit: '0x69420', * maxFeePerGas: '0x2ca6ae494', * maxPriorityFeePerGas: '0x41cc3c0', * nonce: '0x357', * preVerificationGas: '0x69420', * signature: '0x', * sender: '0x1234567890123456789012345678901234567890', * verificationGasLimit: '0x69420', * }) * ``` * * @param rpc - The RPC user operation to convert. * @returns An instantiated {@link ox#UserOperation.UserOperation}. */ export function fromRpc(rpc: Rpc): UserOperation { return { ...rpc, callGasLimit: BigInt(rpc.callGasLimit), maxFeePerGas: BigInt(rpc.maxFeePerGas), maxPriorityFeePerGas: BigInt(rpc.maxPriorityFeePerGas), nonce: BigInt(rpc.nonce), preVerificationGas: BigInt(rpc.preVerificationGas), verificationGasLimit: BigInt(rpc.verificationGasLimit), ...(rpc.paymasterPostOpGasLimit && { paymasterPostOpGasLimit: BigInt(rpc.paymasterPostOpGasLimit), }), ...(rpc.paymasterVerificationGasLimit && { paymasterVerificationGasLimit: BigInt(rpc.paymasterVerificationGasLimit), }), } as UserOperation } export declare namespace fromRpc { type ErrorType = Errors.GlobalErrorType } /** * Obtains the signing payload for a {@link ox#UserOperation.UserOperation}. * * @example * ```ts twoslash * import { Secp256k1, Value } from 'ox' * import { UserOperation } from 'ox/erc4337' * * const userOperation = UserOperation.from({ * callData: '0xdeadbeef', * callGasLimit: 300_000n, * maxFeePerGas: Value.fromGwei('20'), * maxPriorityFeePerGas: Value.fromGwei('2'), * nonce: 69n, * preVerificationGas: 100_000n, * sender: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357', * verificationGasLimit: 100_000n, * }) * * const payload = UserOperation.getSignPayload(userOperation, { // [!code focus] * chainId: 1, // [!code focus] * entryPointAddress: '0x1234567890123456789012345678901234567890', // [!code focus] * entryPointVersion: '0.6', // [!code focus] * }) // [!code focus] * * const signature = Secp256k1.sign({ payload, privateKey: '0x...' }) * ``` * * @param userOperation - The user operation to get the sign payload for. * @returns The signing payload for the user operation. */ export function getSignPayload< entrypointVersion extends EntryPoint.Version = EntryPoint.Version, >( userOperation: UserOperation<entrypointVersion>, options: getSignPayload.Options<entrypointVersion>, ): Hex.Hex { return hash(userOperation, options) } export declare namespace getSignPayload { type Options< entrypointVersion extends EntryPoint.Version = EntryPoint.Version, > = hash.Options<entrypointVersion> type ErrorType = hash.ErrorType | Errors.GlobalErrorType } /** * Hashes a {@link ox#UserOperation.UserOperation}. This is the "user operation hash". * * @example * ```ts twoslash * import { Value } from 'ox' * import { UserOperation } from 'ox/erc4337' * * const userOperation = UserOperation.hash({ * callData: '0xdeadbeef', * callGasLimit: 300_000n, * maxFeePerGas: Value.fromGwei('20'), * maxPriorityFeePerGas: Value.fromGwei('2'), * nonce: 69n, * preVerificationGas: 100_000n, * sender: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357', * verificationGasLimit: 100_000n, * }, { * chainId: 1, * entryPointAddress: '0x1234567890123456789012345678901234567890', * entryPointVersion: '0.6', * }) * ``` * * @param userOperation - The user operation to hash. * @returns The hash of the user operation. */ export function hash< entrypointVersion extends EntryPoint.Version = EntryPoint.Version, >( userOperation: UserOperation<entrypointVersion>, options: hash.Options<entrypointVersion>, ): Hex.Hex { const { chainId, entryPointAddress, entryPointVersion } = options const { callData, callGasLimit, initCode, factory, factoryData, maxFeePerGas, maxPriorityFeePerGas, nonce, paymaster, paymasterAndData, paymasterData, paymasterPostOpGasLimit, paymasterVerificationGasLimit, preVerificationGas, sender, verificationGasLimit, } = userOperation as UserOperation if (entryPointVersion === '0.8') { const typedData = toTypedData(userOperation as UserOperation<'0.8', true>, { chainId, entryPointAddress, }) return TypedData.getSignPayload(typedData) } const packedUserOp = (() => { if (entryPointVersion === '0.6') { return AbiParameters.encode( [ { type: 'address' }, { type: 'uint256' }, { type: 'bytes32' }, { type: 'bytes32' }, { type: 'uint256' }, { type: 'uint256' }, { type: 'uint256' }, { type: 'uint256' }, { type: 'uint256' }, { type: 'bytes32' }, ], [ sender, nonce, Hash.keccak256(initCode ?? '0x'), Hash.keccak256(callData), callGasLimit, verificationGasLimit, preVerificationGas, maxFeePerGas, maxPriorityFeePerGas, Hash.keccak256(paymasterAndData ?? '0x'), ], ) } if (entryPointVersion === '0.7') { const accountGasLimits = Hex.concat( Hex.padLeft(Hex.fromNumber(verificationGasLimit), 16), Hex.padLeft(Hex.fromNumber(callGasLimit), 16), ) const gasFees = Hex.concat( Hex.padLeft(Hex.fromNumber(maxPriorityFeePerGas), 16), Hex.padLeft(Hex.fromNumber(maxFeePerGas), 16), ) const initCode_hashed = Hash.keccak256( factory && factoryData ? Hex.concat(factory, factoryData) : '0x', ) const paymasterAndData_hashed = Hash.keccak256( paymaster ? Hex.concat( paymaster, Hex.padLeft( Hex.fromNumber(paymasterVerificationGasLimit || 0), 16, ), Hex.padLeft(Hex.fromNumber(paymasterPostOpGasLimit || 0), 16), paymasterData || '0x', ) : '0x', ) return AbiParameters.encode( [ { type: 'address' }, { type: 'uint256' }, { type: 'bytes32' }, { type: 'bytes32' }, { type: 'bytes32' }, { type: 'uint256' }, { type: 'bytes32' }, { type: 'bytes32' }, ], [ sender, nonce, initCode_hashed, Hash.keccak256(callData), accountGasLimits, preVerificationGas, gasFees, paymasterAndData_hashed, ], ) } throw new Error(`entryPointVersion "${entryPointVersion}" not supported.`) })() return Hash.keccak256( AbiParameters.encode( [{ type: 'bytes32' }, { type: 'address' }, { type: 'uint256' }], [Hash.keccak256(packedUserOp), entryPointAddress, BigInt(chainId)], ), ) } export declare namespace hash { type Options< entrypointVersion extends EntryPoint.Version = EntryPoint.Version, > = { chainId: number entryPointAddress: Address.Address entryPointVersion: entrypointVersion | EntryPoint.Version } type ErrorType = | AbiParameters.encode.ErrorType | Hash.keccak256.ErrorType | Hex.concat.ErrorType | Hex.fromNumber.ErrorType | Hex.padLeft.ErrorType | Errors.GlobalErrorType } /** * Converts a {@link ox#UserOperation.UserOperation} to `initCode`. * * @example * ```ts twoslash * import { Value } from 'ox' * import { UserOperation } from 'ox/erc4337' * * const initCode = UserOperation.toInitCode({ * authorization: { * address: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357', * chainId: 1, * nonce: 69n, * yParity: 0, * r: 1n, * s: 2n, * }, * callData: '0xdeadbeef', * callGasLimit: 300_000n, * factory: '0x7702', * factoryData: '0xdeadbeef', * maxFeePerGas: Value.fromGwei('20'), * maxPriorityFeePerGas: Value.fromGwei('2'), * nonce: 69n, * preVerificationGas: 100_000n, * sender: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357', * }) * ``` * * @param userOperation - The user operation to convert. * @returns The init code. */ export function toInitCode(userOperation: Partial<UserOperation>): Hex.Hex { const { authorization, factory, factoryData } = userOperation if ( factory === '0x7702' || factory === '0x7702000000000000000000000000000000000000' ) { if (!authorization) return '0x7702000000000000000000000000000000000000' const delegation = authorization.address return Hex.concat(delegation, factoryData ?? '0x') } if (!factory) return '0x' return Hex.concat(factory, factoryData ?? '0x') } /** * Transforms a User Operation into "packed" format. * * @example * ```ts twoslash * import { Value } from 'ox' * import { UserOperation } from 'ox/erc4337' * * const packed = UserOperation.toPacked({ * callData: '0xdeadbeef', * callGasLimit: 300_000n, * maxFeePerGas: Value.fromGwei('20'), * maxPriorityFeePerGas: Value.fromGwei('2'), * nonce: 69n, * preVerificationGas: 100_000n, * sender: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357', * signature: '0x...', * verificationGasLimit: 100_000n, * }) * ``` * * @param userOperation - The user operation to transform. * @returns The packed user operation. */ export function toPacked( userOperation: UserOperation<'0.7' | '0.8', true>, ): Packed { const { callGasLimit, callData, maxPriorityFeePerGas, maxFeePerGas, nonce, paymaster, paymasterData, paymasterPostOpGasLimit, paymasterVerificationGasLimit, sender, signature, verificationGasLimit, } = userOperation const accountGasLimits = Hex.concat( Hex.padLeft(Hex.fromNumber(verificationGasLimit || 0n), 16), Hex.padLeft(Hex.fromNumber(callGasLimit || 0n), 16), ) const initCode = toInitCode(userOperation) const gasFees = Hex.concat( Hex.padLeft(Hex.fromNumber(maxPriorityFeePerGas || 0n), 16), Hex.padLeft(Hex.fromNumber(maxFeePerGas || 0n), 16), ) const paymasterAndData = paymaster ? Hex.concat( paymaster, Hex.padLeft(Hex.fromNumber(paymasterVerificationGasLimit || 0n), 16), Hex.padLeft(Hex.fromNumber(paymasterPostOpGasLimit || 0n), 16), paymasterData || '0x', ) : '0x' const preVerificationGas = userOperation.preVerificationGas ?? 0n return { accountGasLimits, callData, initCode, gasFees, nonce, paymasterAndData, preVerificationGas, sender, signature, } } export declare namespace toPacked { export type ErrorType = Errors.GlobalErrorType } /** * Transforms a "packed" User Operation into a structured {@link ox#UserOperation.UserOperation}. * * @example * ```ts twoslash * import { UserOperation } from 'ox/erc4337' * * const packed: UserOperation.Packed = { * accountGasLimits: '0x...', * callData: '0xdeadbeef', * initCode: '0x...', * gasFees: '0x...', * nonce: 69n, * paymasterAndData: '0x', * preVerificationGas: 100_000n, * sender: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357', * signature: '0x...', * } * * const userOperation = UserOperation.fromPacked(packed) * ``` * * @param packed - The packed user operation to transform. * @returns The structured user operation. */ export function fromPacked(packed: Packed): UserOperation<'0.7' | '0.8', true> { const { accountGasLimits, callData, initCode, gasFees, nonce, paymasterAndData, preVerificationGas, sender, signature, } = packed const verificationGasLimit = BigInt(Hex.slice(accountGasLimits, 0, 16)) const callGasLimit = BigInt(Hex.slice(accountGasLimits, 16, 32)) const { factory, factoryData } = (() => { if (initCode === '0x') return { factory: undefined, factoryData: undefined } const factory = Hex.slice(initCode, 0, 20) const factoryData = Hex.size(initCode) > 20 ? Hex.slice(initCode, 20) : undefined return { factory, factoryData } })() const maxPriorityFeePerGas = BigInt(Hex.slice(gasFees, 0, 16)) const maxFeePerGas = BigInt(Hex.slice(gasFees, 16, 32)) const { paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData, } = (() => { if (paymasterAndData === '0x') return { paymaster: undefined, paymasterVerificationGasLimit: undefined, paymasterPostOpGasLimit: undefined, paymasterData: undefined, } const paymaster = Hex.slice(paymasterAndData, 0, 20) const paymasterVerificationGasLimit = BigInt( Hex.slice(paymasterAndData, 20, 36), ) const paymasterPostOpGasLimit = BigInt(Hex.slice(paymasterAndData, 36, 52)) const paymasterData = Hex.size(paymasterAndData) > 52 ? Hex.slice(paymasterAndData, 52) : undefined return { paymaster, paymasterVerificationGasLimit, paymasterPostOpGasLimit, paymasterData, } })() return { callData, callGasLimit, ...(factory && { factory }), ...(factoryData && { factoryData }), maxFeePerGas, maxPriorityFeePerGas, nonce, ...(paymaster && { paymaster }), ...(paymasterData && { paymasterData }), ...(typeof paymasterPostOpGasLimit === 'bigint' && { paymasterPostOpGasLimit, }), ...(typeof paymasterVerificationGasLimit === 'bigint' && { paymasterVerificationGasLimit, }), preVerificationGas, sender, signature, verificationGasLimit, } } export declare namespace fromPacked { export type ErrorType = Hex.slice.ErrorType | Errors.GlobalErrorType } /** * Converts a {@link ox#UserOperation.UserOperation} to a {@link ox#UserOperation.Rpc}. * * @example * ```ts twoslash * import { Value } from 'ox' * import { UserOperation } from 'ox/erc4337' * * const userOperation = UserOperation.toRpc({ * callData: '0xdeadbeef', * callGasLimit: 300_000n, * maxFeePerGas: Value.fromGwei('20'), * maxPriorityFeePerGas: Value.fromGwei('2'), * nonce: 69n, * preVerificationGas: 100_000n, * sender: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357', * verificationGasLimit: 100_000n, * }) * ``` * * @param userOperation - The user operation to convert. * @returns An RPC-formatted user operation. */ export function toRpc(userOperation: UserOperation): Rpc { const rpc = {} as Rpc rpc.callData = userOperation.callData rpc.callGasLimit = Hex.fromNumber(userOperation.callGasLimit) rpc.maxFeePerGas = Hex.fromNumber(userOperation.maxFeePerGas) rpc.maxPriorityFeePerGas = Hex.fromNumber(userOperation.maxPriorityFeePerGas) rpc.nonce = Hex.fromNumber(userOperation.nonce) rpc.preVerificationGas = Hex.fromNumber(userOperation.preVerificationGas) rpc.sender = userOperation.sender rpc.verificationGasLimit = Hex.fromNumber(userOperation.verificationGasLimit) if (userOperation.factory) rpc.factory = userOperation.factory if (userOperation.factoryData) rpc.factoryData = userOperation.factoryData if (userOperation.initCode) rpc.initCode = userOperation.initCode if (userOperation.paymaster) rpc.paymaster = userOperation.paymaster if (userOperation.paymasterData) rpc.paymasterData = userOperation.paymasterData if (typeof userOperation.paymasterPostOpGasLimit === 'bigint') rpc.paymasterPostOpGasLimit = Hex.fromNumber( userOperation.paymasterPostOpGasLimit, ) if (typeof userOperation.paymasterVerificationGasLimit === 'bigint') rpc.paymasterVerificationGasLimit = Hex.fromNumber( userOperation.paymasterVerificationGasLimit, ) if (userOperation.signature) rpc.signature = userOperation.signature return rpc } export declare namespace toRpc { export type ErrorType = Hex.fromNumber.ErrorType | Errors.GlobalErrorType } /** * Converts a signed {@link ox#UserOperation.UserOperation} to a {@link ox#TypedData.Definition}. * * @example * ```ts twoslash * import { Value } from 'ox' * import { UserOperation } from 'ox/erc4337' * * const typedData = UserOperation.toTypedData({ * authorization: { * chainId: 1, * address: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357', * nonce: 69n, * yParity: 0, * r: 1n, * s: 2n, * }, * callData: '0xdeadbeef', * callGasLimit: 300_000n, * maxFeePerGas: Value.fromGwei('20'), * maxPriorityFeePerGas: Value.fromGwei('2'), * nonce: 69n, * preVerificationGas: 100_000n, * sender: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357', * signature: '0x...', * verificationGasLimit: 100_000n, * }, { * chainId: 1, * entryPointAddress: '0x1234567890123456789012345678901234567890', * }) * ``` * * @param userOperation - The user operation to convert. * @returns A Typed Data definition. */ export function toTypedData( userOperation: UserOperation<'0.8', true>, options: toTypedData.Options, ): TypedData.Definition<typeof toTypedData.types, 'PackedUserOperation'> { const { chainId, entryPointAddress } = options const packedUserOp = toPacked(userOperation) return { domain: { name: 'ERC4337', version: '1', chainId, verifyingContract: entryPointAddress, }, message: packedUserOp, primaryType: 'PackedUserOperation', types: toTypedData.types, } } export namespace toTypedData { export type Options = { chainId: number entryPointAddress: Address.Address } export type ErrorType = Errors.GlobalErrorType export const 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' }, ], } as const }