FRE-600: Fix code review blockers

- Consolidated duplicate UndoManagers to single instance
- Fixed connection promise to only resolve on 'connected' status
- Fixed WebSocketProvider import (WebsocketProvider)
- Added proper doc.destroy() cleanup
- Renamed isPresenceInitialized property to avoid conflict

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-25 00:08:01 -04:00
parent 65b552bb08
commit 7c684a42cc
48450 changed files with 5679671 additions and 383 deletions

View File

@@ -0,0 +1,310 @@
import type { BaseError } from '../../../errors/base.js'
import type { ExactPartial } from '../../../types/utils.js'
import {
AccountNotDeployedError,
type AccountNotDeployedErrorType,
ExecutionRevertedError,
type ExecutionRevertedErrorType,
FailedToSendToBeneficiaryError,
type FailedToSendToBeneficiaryErrorType,
GasValuesOverflowError,
type GasValuesOverflowErrorType,
HandleOpsOutOfGasError,
type HandleOpsOutOfGasErrorType,
InitCodeFailedError,
type InitCodeFailedErrorType,
InitCodeMustCreateSenderError,
type InitCodeMustCreateSenderErrorType,
InitCodeMustReturnSenderError,
type InitCodeMustReturnSenderErrorType,
InsufficientPrefundError,
type InsufficientPrefundErrorType,
InternalCallOnlyError,
type InternalCallOnlyErrorType,
InvalidAccountNonceError,
type InvalidAccountNonceErrorType,
InvalidAggregatorError,
type InvalidAggregatorErrorType,
InvalidBeneficiaryError,
type InvalidBeneficiaryErrorType,
InvalidFieldsError,
type InvalidFieldsErrorType,
InvalidPaymasterAndDataError,
type InvalidPaymasterAndDataErrorType,
PaymasterDepositTooLowError,
type PaymasterDepositTooLowErrorType,
PaymasterFunctionRevertedError,
type PaymasterFunctionRevertedErrorType,
PaymasterNotDeployedError,
type PaymasterNotDeployedErrorType,
PaymasterPostOpFunctionRevertedError,
type PaymasterPostOpFunctionRevertedErrorType,
PaymasterRateLimitError,
type PaymasterRateLimitErrorType,
PaymasterStakeTooLowError,
type PaymasterStakeTooLowErrorType,
SenderAlreadyConstructedError,
type SenderAlreadyConstructedErrorType,
SignatureCheckFailedError,
type SignatureCheckFailedErrorType,
SmartAccountFunctionRevertedError,
type SmartAccountFunctionRevertedErrorType,
UnknownBundlerError,
type UnknownBundlerErrorType,
UnsupportedSignatureAggregatorError,
type UnsupportedSignatureAggregatorErrorType,
UserOperationExpiredError,
type UserOperationExpiredErrorType,
UserOperationOutOfTimeRangeError,
type UserOperationOutOfTimeRangeErrorType,
UserOperationPaymasterExpiredError,
type UserOperationPaymasterExpiredErrorType,
UserOperationPaymasterSignatureError,
type UserOperationPaymasterSignatureErrorType,
UserOperationRejectedByEntryPointError,
type UserOperationRejectedByEntryPointErrorType,
UserOperationRejectedByOpCodeError,
type UserOperationRejectedByOpCodeErrorType,
UserOperationRejectedByPaymasterError,
type UserOperationRejectedByPaymasterErrorType,
UserOperationSignatureError,
type UserOperationSignatureErrorType,
VerificationGasLimitExceededError,
type VerificationGasLimitExceededErrorType,
VerificationGasLimitTooLowError,
type VerificationGasLimitTooLowErrorType,
} from '../../errors/bundler.js'
import type { UserOperation } from '../../types/userOperation.js'
const bundlerErrors = [
ExecutionRevertedError,
InvalidFieldsError,
PaymasterDepositTooLowError,
PaymasterRateLimitError,
PaymasterStakeTooLowError,
SignatureCheckFailedError,
UnsupportedSignatureAggregatorError,
UserOperationOutOfTimeRangeError,
UserOperationRejectedByEntryPointError,
UserOperationRejectedByPaymasterError,
UserOperationRejectedByOpCodeError,
]
export type GetBundlerErrorParameters = ExactPartial<UserOperation>
export type GetBundlerErrorReturnType =
| AccountNotDeployedErrorType
| ExecutionRevertedErrorType
| FailedToSendToBeneficiaryErrorType
| GasValuesOverflowErrorType
| HandleOpsOutOfGasErrorType
| InitCodeFailedErrorType
| InitCodeMustCreateSenderErrorType
| InitCodeMustReturnSenderErrorType
| InsufficientPrefundErrorType
| InternalCallOnlyErrorType
| InvalidAccountNonceErrorType
| InvalidAggregatorErrorType
| InvalidBeneficiaryErrorType
| InvalidFieldsErrorType
| InvalidPaymasterAndDataErrorType
| PaymasterDepositTooLowErrorType
| PaymasterFunctionRevertedErrorType
| PaymasterNotDeployedErrorType
| PaymasterPostOpFunctionRevertedErrorType
| PaymasterRateLimitErrorType
| PaymasterStakeTooLowErrorType
| SignatureCheckFailedErrorType
| SenderAlreadyConstructedErrorType
| SmartAccountFunctionRevertedErrorType
| UnsupportedSignatureAggregatorErrorType
| UserOperationOutOfTimeRangeErrorType
| UserOperationRejectedByEntryPointErrorType
| UserOperationRejectedByOpCodeErrorType
| UserOperationRejectedByPaymasterErrorType
| UnknownBundlerErrorType
| UserOperationExpiredErrorType
| UserOperationPaymasterExpiredErrorType
| UserOperationPaymasterSignatureErrorType
| UserOperationSignatureErrorType
| VerificationGasLimitExceededErrorType
| VerificationGasLimitTooLowErrorType
export function getBundlerError(
err: BaseError,
args: GetBundlerErrorParameters,
): GetBundlerErrorReturnType {
const message = (err.details || '').toLowerCase()
if (AccountNotDeployedError.message.test(message))
return new AccountNotDeployedError({
cause: err,
}) as any
if (FailedToSendToBeneficiaryError.message.test(message))
return new FailedToSendToBeneficiaryError({
cause: err,
}) as any
if (GasValuesOverflowError.message.test(message))
return new GasValuesOverflowError({
cause: err,
}) as any
if (HandleOpsOutOfGasError.message.test(message))
return new HandleOpsOutOfGasError({
cause: err,
}) as any
if (InitCodeFailedError.message.test(message))
return new InitCodeFailedError({
cause: err,
factory: args.factory,
factoryData: args.factoryData,
initCode: args.initCode,
}) as any
if (InitCodeMustCreateSenderError.message.test(message))
return new InitCodeMustCreateSenderError({
cause: err,
factory: args.factory,
factoryData: args.factoryData,
initCode: args.initCode,
}) as any
if (InitCodeMustReturnSenderError.message.test(message))
return new InitCodeMustReturnSenderError({
cause: err,
factory: args.factory,
factoryData: args.factoryData,
initCode: args.initCode,
sender: args.sender,
}) as any
if (InsufficientPrefundError.message.test(message))
return new InsufficientPrefundError({
cause: err,
}) as any
if (InternalCallOnlyError.message.test(message))
return new InternalCallOnlyError({
cause: err,
}) as any
if (InvalidAccountNonceError.message.test(message))
return new InvalidAccountNonceError({
cause: err,
nonce: args.nonce,
}) as any
if (InvalidAggregatorError.message.test(message))
return new InvalidAggregatorError({
cause: err,
}) as any
if (InvalidBeneficiaryError.message.test(message))
return new InvalidBeneficiaryError({
cause: err,
}) as any
if (InvalidPaymasterAndDataError.message.test(message))
return new InvalidPaymasterAndDataError({
cause: err,
}) as any
if (PaymasterDepositTooLowError.message.test(message))
return new PaymasterDepositTooLowError({
cause: err,
}) as any
if (PaymasterFunctionRevertedError.message.test(message))
return new PaymasterFunctionRevertedError({
cause: err,
}) as any
if (PaymasterNotDeployedError.message.test(message))
return new PaymasterNotDeployedError({
cause: err,
}) as any
if (PaymasterPostOpFunctionRevertedError.message.test(message))
return new PaymasterPostOpFunctionRevertedError({
cause: err,
}) as any
if (SmartAccountFunctionRevertedError.message.test(message))
return new SmartAccountFunctionRevertedError({
cause: err,
}) as any
if (SenderAlreadyConstructedError.message.test(message))
return new SenderAlreadyConstructedError({
cause: err,
factory: args.factory,
factoryData: args.factoryData,
initCode: args.initCode,
}) as any
if (UserOperationExpiredError.message.test(message))
return new UserOperationExpiredError({
cause: err,
}) as any
if (UserOperationPaymasterExpiredError.message.test(message))
return new UserOperationPaymasterExpiredError({
cause: err,
}) as any
if (UserOperationPaymasterSignatureError.message.test(message))
return new UserOperationPaymasterSignatureError({
cause: err,
}) as any
if (UserOperationSignatureError.message.test(message))
return new UserOperationSignatureError({
cause: err,
}) as any
if (VerificationGasLimitExceededError.message.test(message))
return new VerificationGasLimitExceededError({
cause: err,
}) as any
if (VerificationGasLimitTooLowError.message.test(message))
return new VerificationGasLimitTooLowError({
cause: err,
}) as any
const error = err.walk((e) =>
bundlerErrors.some((error) => error.code === (e as { code: number }).code),
) as BaseError & { code: number; data: any }
if (error) {
if (error.code === ExecutionRevertedError.code)
return new ExecutionRevertedError({
cause: err,
data: error.data,
message: error.details,
}) as any
if (error.code === InvalidFieldsError.code)
return new InvalidFieldsError({
cause: err,
}) as any
if (error.code === PaymasterDepositTooLowError.code)
return new PaymasterDepositTooLowError({
cause: err,
}) as any
if (error.code === PaymasterRateLimitError.code)
return new PaymasterRateLimitError({
cause: err,
}) as any
if (error.code === PaymasterStakeTooLowError.code)
return new PaymasterStakeTooLowError({
cause: err,
}) as any
if (error.code === SignatureCheckFailedError.code)
return new SignatureCheckFailedError({
cause: err,
}) as any
if (error.code === UnsupportedSignatureAggregatorError.code)
return new UnsupportedSignatureAggregatorError({
cause: err,
}) as any
if (error.code === UserOperationOutOfTimeRangeError.code)
return new UserOperationOutOfTimeRangeError({
cause: err,
}) as any
if (error.code === UserOperationRejectedByEntryPointError.code)
return new UserOperationRejectedByEntryPointError({
cause: err,
}) as any
if (error.code === UserOperationRejectedByPaymasterError.code)
return new UserOperationRejectedByPaymasterError({
cause: err,
}) as any
if (error.code === UserOperationRejectedByOpCodeError.code)
return new UserOperationRejectedByOpCodeError({
cause: err,
}) as any
}
return new UnknownBundlerError({
cause: err,
}) as any
}

View File

@@ -0,0 +1,147 @@
import type { Abi, Address } from 'abitype'
import { BaseError } from '../../../errors/base.js'
import {
ContractFunctionExecutionError,
ContractFunctionRevertedError,
ContractFunctionZeroDataError,
} from '../../../errors/contract.js'
import type { ErrorType } from '../../../errors/utils.js'
import type { Call } from '../../../types/calls.js'
import type { Hex } from '../../../types/misc.js'
import { decodeErrorResult } from '../../../utils/abi/decodeErrorResult.js'
import type { GetContractErrorReturnType } from '../../../utils/errors/getContractError.js'
import { ExecutionRevertedError } from '../../errors/bundler.js'
import {
UserOperationExecutionError,
type UserOperationExecutionErrorType,
} from '../../errors/userOperation.js'
import type { UserOperation } from '../../types/userOperation.js'
import {
type GetBundlerErrorParameters,
getBundlerError,
} from './getBundlerError.js'
type GetNodeErrorReturnType = ErrorType
export type GetUserOperationErrorParameters = UserOperation & {
calls?: readonly unknown[] | undefined
docsPath?: string | undefined
}
export type GetUserOperationErrorReturnType<cause = ErrorType> = Omit<
UserOperationExecutionErrorType,
'cause'
> & { cause: cause | GetNodeErrorReturnType }
export type GetUserOperationErrorErrorType = ErrorType
export function getUserOperationError<err extends ErrorType<string>>(
err: err,
{ calls, docsPath, ...args }: GetUserOperationErrorParameters,
): GetUserOperationErrorReturnType<err> {
const cause = (() => {
const cause = getBundlerError(
err as {} as BaseError,
args as GetBundlerErrorParameters,
)
if (calls && cause instanceof ExecutionRevertedError) {
const revertData = getRevertData(cause)
const contractCalls = calls?.filter(
(call: any) => call.abi,
) as readonly Call[]
if (revertData && contractCalls.length > 0)
return getContractError({ calls: contractCalls, revertData })
}
return cause
})()
return new UserOperationExecutionError(cause, {
docsPath,
...args,
}) as GetUserOperationErrorReturnType<err>
}
/////////////////////////////////////////////////////////////////////////////////
function getRevertData(error: BaseError) {
let revertData: Hex | undefined
error.walk((e) => {
const error = e as any
if (
typeof error.data === 'string' ||
typeof error.data?.revertData === 'string' ||
(!(error instanceof BaseError) && typeof error.message === 'string')
) {
const match = (
error.data?.revertData ||
error.data ||
error.message
).match?.(/(0x[A-Za-z0-9]*)/)
if (match) {
revertData = match[1]
return true
}
}
return false
})
return revertData
}
function getContractError(parameters: {
calls: readonly Call[]
revertData: Hex
}) {
const { calls, revertData } = parameters
const { abi, functionName, args, to } = (() => {
const contractCalls = calls?.filter((call) =>
Boolean(call.abi),
) as readonly Call[]
if (contractCalls.length === 1) return contractCalls[0]
const compatContractCalls = contractCalls.filter((call) => {
try {
return Boolean(
decodeErrorResult({
abi: call.abi,
data: revertData,
}),
)
} catch {
return false
}
})
if (compatContractCalls.length === 1) return compatContractCalls[0]
return {
abi: [],
functionName: contractCalls.reduce(
(acc, call) => `${acc ? `${acc} | ` : ''}${call.functionName}`,
'',
),
args: undefined,
to: undefined,
}
})() as {
abi: Abi
functionName: string
args: unknown[]
to: Address
}
const cause = (() => {
if (revertData === '0x')
return new ContractFunctionZeroDataError({ functionName })
return new ContractFunctionRevertedError({
abi,
data: revertData,
functionName,
})
})()
return new ContractFunctionExecutionError(cause as BaseError, {
abi,
args,
contractAddress: to,
functionName,
}) as GetContractErrorReturnType
}

View File

@@ -0,0 +1,31 @@
import type { ErrorType } from '../../../errors/utils.js'
import type { RpcUserOperation } from '../../types/rpc.js'
import type { UserOperation } from '../../types/userOperation.js'
export type FormatUserOperationErrorType = ErrorType
export function formatUserOperation(parameters: RpcUserOperation) {
const userOperation = { ...parameters } as unknown as UserOperation
if (parameters.callGasLimit)
userOperation.callGasLimit = BigInt(parameters.callGasLimit)
if (parameters.maxFeePerGas)
userOperation.maxFeePerGas = BigInt(parameters.maxFeePerGas)
if (parameters.maxPriorityFeePerGas)
userOperation.maxPriorityFeePerGas = BigInt(parameters.maxPriorityFeePerGas)
if (parameters.nonce) userOperation.nonce = BigInt(parameters.nonce)
if (parameters.paymasterPostOpGasLimit)
userOperation.paymasterPostOpGasLimit = BigInt(
parameters.paymasterPostOpGasLimit,
)
if (parameters.paymasterVerificationGasLimit)
userOperation.paymasterVerificationGasLimit = BigInt(
parameters.paymasterVerificationGasLimit,
)
if (parameters.preVerificationGas)
userOperation.preVerificationGas = BigInt(parameters.preVerificationGas)
if (parameters.verificationGasLimit)
userOperation.verificationGasLimit = BigInt(parameters.verificationGasLimit)
return userOperation
}

View File

@@ -0,0 +1,26 @@
import type { ErrorType } from '../../../errors/utils.js'
import type { RpcEstimateUserOperationGasReturnType } from '../../types/rpc.js'
import type { EstimateUserOperationGasReturnType } from '../../types/userOperation.js'
export type FormatUserOperationGasErrorType = ErrorType
export function formatUserOperationGas(
parameters: RpcEstimateUserOperationGasReturnType,
): EstimateUserOperationGasReturnType {
const gas = {} as EstimateUserOperationGasReturnType
if (parameters.callGasLimit)
gas.callGasLimit = BigInt(parameters.callGasLimit)
if (parameters.preVerificationGas)
gas.preVerificationGas = BigInt(parameters.preVerificationGas)
if (parameters.verificationGasLimit)
gas.verificationGasLimit = BigInt(parameters.verificationGasLimit)
if (parameters.paymasterPostOpGasLimit)
gas.paymasterPostOpGasLimit = BigInt(parameters.paymasterPostOpGasLimit)
if (parameters.paymasterVerificationGasLimit)
gas.paymasterVerificationGasLimit = BigInt(
parameters.paymasterVerificationGasLimit,
)
return gas
}

View File

@@ -0,0 +1,24 @@
import type { ErrorType } from '../../../errors/utils.js'
import { formatLog } from '../../../utils/formatters/log.js'
import { formatTransactionReceipt } from '../../../utils/formatters/transactionReceipt.js'
import type { RpcUserOperationReceipt } from '../../types/rpc.js'
import type { UserOperationReceipt } from '../../types/userOperation.js'
export type FormatUserOperationReceiptErrorType = ErrorType
export function formatUserOperationReceipt(
parameters: RpcUserOperationReceipt,
) {
const receipt = { ...parameters } as unknown as UserOperationReceipt
if (parameters.actualGasCost)
receipt.actualGasCost = BigInt(parameters.actualGasCost)
if (parameters.actualGasUsed)
receipt.actualGasUsed = BigInt(parameters.actualGasUsed)
if (parameters.logs)
receipt.logs = parameters.logs.map((log) => formatLog(log)) as any
if (parameters.receipt)
receipt.receipt = formatTransactionReceipt(receipt.receipt as any)
return receipt
}

View File

@@ -0,0 +1,76 @@
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'
export type FormatUserOperationRequestErrorType = ErrorType
export function formatUserOperationRequest(
request: ExactPartial<UserOperation>,
) {
const rpcRequest = {} as RpcUserOperation
if (typeof request.callData !== 'undefined')
rpcRequest.callData = request.callData
if (typeof request.callGasLimit !== 'undefined')
rpcRequest.callGasLimit = numberToHex(request.callGasLimit)
if (typeof request.factory !== 'undefined')
rpcRequest.factory = request.factory
if (typeof request.factoryData !== 'undefined')
rpcRequest.factoryData = request.factoryData
if (typeof request.initCode !== 'undefined')
rpcRequest.initCode = request.initCode
if (typeof request.maxFeePerGas !== 'undefined')
rpcRequest.maxFeePerGas = numberToHex(request.maxFeePerGas)
if (typeof request.maxPriorityFeePerGas !== 'undefined')
rpcRequest.maxPriorityFeePerGas = numberToHex(request.maxPriorityFeePerGas)
if (typeof request.nonce !== 'undefined')
rpcRequest.nonce = numberToHex(request.nonce)
if (typeof request.paymaster !== 'undefined')
rpcRequest.paymaster = request.paymaster
if (typeof request.paymasterAndData !== 'undefined')
rpcRequest.paymasterAndData = request.paymasterAndData || '0x'
if (typeof request.paymasterData !== 'undefined')
rpcRequest.paymasterData = request.paymasterData
if (typeof request.paymasterPostOpGasLimit !== 'undefined')
rpcRequest.paymasterPostOpGasLimit = numberToHex(
request.paymasterPostOpGasLimit,
)
if (typeof request.paymasterSignature !== 'undefined')
rpcRequest.paymasterSignature = request.paymasterSignature
if (typeof request.paymasterVerificationGasLimit !== 'undefined')
rpcRequest.paymasterVerificationGasLimit = numberToHex(
request.paymasterVerificationGasLimit,
)
if (typeof request.preVerificationGas !== 'undefined')
rpcRequest.preVerificationGas = numberToHex(request.preVerificationGas)
if (typeof request.sender !== 'undefined') rpcRequest.sender = request.sender
if (typeof request.signature !== 'undefined')
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 }),
}
}

View File

@@ -0,0 +1,28 @@
import { concat } from '../../../utils/data/concat.js'
import type { UserOperation } from '../../types/userOperation.js'
export type GetInitCodeOptions = {
/** Prepare the init code for hashing. */
forHash?: boolean | undefined
}
export function getInitCode(
userOperation: Pick<
UserOperation,
'authorization' | 'factory' | 'factoryData'
>,
options: GetInitCodeOptions = {},
) {
const { forHash } = options
const { authorization, factory, factoryData } = userOperation
if (
forHash &&
(factory === '0x7702' ||
factory === '0x7702000000000000000000000000000000000000')
) {
if (!authorization) return '0x7702000000000000000000000000000000000000'
return concat([authorization.address, factoryData ?? '0x'])
}
if (!factory) return '0x'
return concat([factory, factoryData ?? '0x'])
}

View File

@@ -0,0 +1,130 @@
import type { Address } from 'abitype'
import type { Hash, Hex } from '../../../types/misc.js'
import { encodeAbiParameters } from '../../../utils/abi/encodeAbiParameters.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'
import { getInitCode } from './getInitCode.js'
import { getUserOperationTypedData } from './getUserOperationTypedData.js'
import { toPackedUserOperation } from './toPackedUserOperation.js'
export type GetUserOperationHashParameters<
entryPointVersion extends EntryPointVersion = EntryPointVersion,
> = {
chainId: number
entryPointAddress: Address
entryPointVersion: entryPointVersion | EntryPointVersion
userOperation: UserOperation<entryPointVersion>
}
export type GetUserOperationHashReturnType = Hash
export function getUserOperationHash<
entryPointVersion extends EntryPointVersion,
>(
parameters: GetUserOperationHashParameters<entryPointVersion>,
): GetUserOperationHashReturnType {
const { chainId, entryPointAddress, entryPointVersion } = parameters
const userOperation = parameters.userOperation as UserOperation
const {
authorization,
callData = '0x',
callGasLimit,
maxFeePerGas,
maxPriorityFeePerGas,
nonce,
paymasterAndData = '0x',
preVerificationGas,
sender,
verificationGasLimit,
} = userOperation
if (entryPointVersion === '0.8' || entryPointVersion === '0.9')
return hashTypedData(
getUserOperationTypedData({
chainId,
entryPointAddress,
userOperation,
}),
)
const packedUserOp = (() => {
if (entryPointVersion === '0.6') {
const factory = userOperation.initCode?.slice(0, 42) as Hex
const factoryData = userOperation.initCode?.slice(42) as Hex | undefined
const initCode = getInitCode(
{
authorization,
factory,
factoryData,
},
{ forHash: true },
)
return encodeAbiParameters(
[
{ type: 'address' },
{ type: 'uint256' },
{ type: 'bytes32' },
{ type: 'bytes32' },
{ type: 'uint256' },
{ type: 'uint256' },
{ type: 'uint256' },
{ type: 'uint256' },
{ type: 'uint256' },
{ type: 'bytes32' },
],
[
sender,
nonce,
keccak256(initCode),
keccak256(callData),
callGasLimit,
verificationGasLimit,
preVerificationGas,
maxFeePerGas,
maxPriorityFeePerGas,
keccak256(paymasterAndData),
],
)
}
if (entryPointVersion === '0.7') {
const packedUserOp = toPackedUserOperation(userOperation, {
forHash: true,
})
return encodeAbiParameters(
[
{ type: 'address' },
{ type: 'uint256' },
{ type: 'bytes32' },
{ type: 'bytes32' },
{ type: 'bytes32' },
{ type: 'uint256' },
{ type: 'bytes32' },
{ type: 'bytes32' },
],
[
packedUserOp.sender,
packedUserOp.nonce,
keccak256(packedUserOp.initCode),
keccak256(packedUserOp.callData),
packedUserOp.accountGasLimits,
packedUserOp.preVerificationGas,
packedUserOp.gasFees,
keccak256(packedUserOp.paymasterAndData),
],
)
}
throw new Error(`entryPointVersion "${entryPointVersion}" not supported.`)
})()
return keccak256(
encodeAbiParameters(
[{ type: 'bytes32' }, { type: 'address' }, { type: 'uint256' }],
[keccak256(packedUserOp), entryPointAddress, BigInt(chainId)],
),
)
}

View File

@@ -0,0 +1,49 @@
import type { Address } from 'abitype'
import type { TypedDataDefinition } from '../../../types/typedData.js'
import type { UserOperation } from '../../types/userOperation.js'
import { toPackedUserOperation } from './toPackedUserOperation.js'
export type GetUserOperationTypedDataParameters = {
chainId: number
entryPointAddress: Address
userOperation: UserOperation<'0.8'> | UserOperation<'0.9'>
}
export type GetUserOperationTypedDataReturnType = TypedDataDefinition<
typeof types,
'PackedUserOperation'
>
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
export function getUserOperationTypedData(
parameters: GetUserOperationTypedDataParameters,
): GetUserOperationTypedDataReturnType {
const { chainId, entryPointAddress, userOperation } = parameters
const packedUserOp = toPackedUserOperation(userOperation, { forHash: true })
return {
types,
primaryType: 'PackedUserOperation',
domain: {
name: 'ERC4337',
version: '1',
chainId,
verifyingContract: entryPointAddress,
},
message: packedUserOp,
}
}

View File

@@ -0,0 +1,88 @@
import { concat } from '../../../utils/data/concat.js'
import { pad } from '../../../utils/data/pad.js'
import { size } from '../../../utils/data/size.js'
import { numberToHex } from '../../../utils/index.js'
import type {
PackedUserOperation,
UserOperation,
} from '../../types/userOperation.js'
import { getInitCode } from './getInitCode.js'
/** Magic suffix for paymaster signature encoding (keccak256("PaymasterSignature")[:8]) */
const paymasterSignatureMagic = '0x22e325a297439656' as const
export type ToPackedUserOperationOptions = {
/** Prepare the packed user operation for hashing. */
forHash?: boolean | undefined
}
export function toPackedUserOperation(
userOperation: UserOperation,
options: ToPackedUserOperationOptions = {},
): PackedUserOperation {
const {
callGasLimit,
callData,
maxPriorityFeePerGas,
maxFeePerGas,
paymaster,
paymasterData,
paymasterPostOpGasLimit,
paymasterSignature,
paymasterVerificationGasLimit,
sender,
signature = '0x',
verificationGasLimit,
} = userOperation as UserOperation & { paymasterSignature?: string }
const accountGasLimits = concat([
pad(numberToHex(verificationGasLimit || 0n), { size: 16 }),
pad(numberToHex(callGasLimit || 0n), { size: 16 }),
])
const initCode = getInitCode(userOperation, options)
const gasFees = concat([
pad(numberToHex(maxPriorityFeePerGas || 0n), { size: 16 }),
pad(numberToHex(maxFeePerGas || 0n), { size: 16 }),
])
const nonce = userOperation.nonce ?? 0n
// For v0.9, paymasterSignature can be provided separately and appended after paymasterData.
// The encoding uses a magic suffix and length prefix as per ERC-4337 spec:
// - forHash: just append the magic (signature is not part of hash)
// - !forHash: append signature + length (2 bytes) + magic
const paymasterAndData = paymaster
? concat([
paymaster,
pad(numberToHex(paymasterVerificationGasLimit || 0n), {
size: 16,
}),
pad(numberToHex(paymasterPostOpGasLimit || 0n), {
size: 16,
}),
paymasterData || '0x',
...(paymasterSignature
? options.forHash
? [paymasterSignatureMagic]
: [
paymasterSignature as `0x${string}`,
pad(numberToHex(size(paymasterSignature)), { size: 2 }),
paymasterSignatureMagic,
]
: []),
])
: '0x'
const preVerificationGas = userOperation.preVerificationGas ?? 0n
return {
accountGasLimits,
callData,
initCode,
gasFees,
nonce,
paymasterAndData,
preVerificationGas,
sender,
signature,
}
}

View File

@@ -0,0 +1,3 @@
import { UserOperation } from 'ox/erc4337'
export const toUserOperation = UserOperation.from