diff --git a/package.json b/package.json index fa1e684..17ced56 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sendop", - "version": "0.5.6", + "version": "0.5.7", "description": "ERC-4337 utilities for sending user operations with ethers.js", "author": "Johnson Chen ", "repository": "https://github.com/ethaccount/sendop", diff --git a/src/core/UserOpBuilder.ts b/src/core/UserOpBuilder.ts index c653e00..3a67d10 100644 --- a/src/core/UserOpBuilder.ts +++ b/src/core/UserOpBuilder.ts @@ -106,14 +106,22 @@ export class UserOpBuilder { paymaster, paymasterData = '0x', paymasterPostOpGasLimit = 0, + paymasterVerificationGasLimit = 0, }: { paymaster: string paymasterData?: string paymasterPostOpGasLimit?: BigNumberish + paymasterVerificationGasLimit?: BigNumberish }): UserOpBuilder { this.userOp.paymaster = paymaster this.userOp.paymasterData = paymasterData this.userOp.paymasterPostOpGasLimit = paymasterPostOpGasLimit + this.userOp.paymasterVerificationGasLimit = paymasterVerificationGasLimit + return this + } + + setPaymasterData(paymasterData: string): UserOpBuilder { + this.userOp.paymasterData = paymasterData return this } @@ -233,6 +241,11 @@ export class UserOpBuilder { return [domain, types, this.pack()] } + /** + * Estimates gas values for the user operation using the bundler. + * Always sets: verificationGasLimit, preVerificationGas, callGasLimit + * Conditionally sets: paymasterVerificationGasLimit (only if not already set), maxFeePerGas, maxPriorityFeePerGas + */ async estimateGas(): Promise { this.checkSender() this.checkEntryPointAddress() @@ -242,7 +255,11 @@ export class UserOpBuilder { this.userOp.verificationGasLimit = estimations.verificationGasLimit this.userOp.preVerificationGas = estimations.preVerificationGas this.userOp.callGasLimit = estimations.callGasLimit - this.userOp.paymasterVerificationGasLimit = estimations.paymasterVerificationGasLimit + + // Only set paymasterVerificationGasLimit if not already set + if (!this.userOp.paymasterVerificationGasLimit) { + this.userOp.paymasterVerificationGasLimit = estimations.paymasterVerificationGasLimit + } // Only etherspot returns these if (estimations.maxFeePerGas) { diff --git a/src/paymasters/PaymasterService.ts b/src/paymasters/PaymasterService.ts new file mode 100644 index 0000000..2a3c5c7 --- /dev/null +++ b/src/paymasters/PaymasterService.ts @@ -0,0 +1,85 @@ +import { toUserOpHex, type UserOperation } from '@/core' +import type { AddressLike, BigNumberish, FetchRequest, JsonRpcApiProviderOptions } from 'ethers' +import { JsonRpcProvider, resolveAddress, toBeHex } from 'ethers' +import type { + GetPaymasterDataParams, + GetPaymasterDataResult, + GetPaymasterStubDataParams, + GetPaymasterStubDataResult, +} from './erc7677-types' + +export class PaymasterServiceError extends Error { + constructor(message: string, cause?: unknown) { + super(message, { cause }) + this.name = 'PaymasterServiceError' + } +} + +export class PaymasterService extends JsonRpcProvider { + chainId: number + + constructor(url: string | FetchRequest, chainId: BigNumberish, options?: JsonRpcApiProviderOptions) { + const serviceOptions = { + ...options, + batchMaxCount: options?.batchMaxCount ? 1 : options?.batchMaxCount, + staticNetwork: options?.staticNetwork ?? true, + } + const chainIdNumber = Number(chainId) + super(url, chainIdNumber, serviceOptions) + this.chainId = chainIdNumber + } + + /** + * Get supported entry points for this paymaster + * @returns Array of supported entry point addresses + */ + async supportedEntryPoints(): Promise { + return await this.send('pm_supportedEntryPoints', []) + } + + /** + * Get paymaster stub data for a user operation + */ + async getPaymasterStubData({ + userOp, + entryPointAddress, + context, + }: { + userOp: UserOperation + entryPointAddress: AddressLike + context: Record + }): Promise { + let params: GetPaymasterStubDataParams + try { + params = [toUserOpHex(userOp), await resolveAddress(entryPointAddress), toBeHex(this.chainId), context] + } catch (err) { + throw new PaymasterServiceError('Error building params for pm_getPaymasterStubData', { + cause: err, + }) + } + return await this.send('pm_getPaymasterStubData', params) + } + + /** + * Get final paymaster data for a user operation + */ + async getPaymasterData({ + userOp, + entryPointAddress, + context, + }: { + userOp: UserOperation + entryPointAddress: AddressLike + context: Record + }): Promise { + let params: GetPaymasterDataParams + try { + params = [toUserOpHex(userOp), await resolveAddress(entryPointAddress), toBeHex(this.chainId), context] + } catch (err) { + throw new PaymasterServiceError('Error building params for pm_getPaymasterData', { + cause: err, + }) + } + return await this.send('pm_getPaymasterData', params) + } +} diff --git a/src/paymasters/erc7677-types.ts b/src/paymasters/erc7677-types.ts new file mode 100644 index 0000000..e99f534 --- /dev/null +++ b/src/paymasters/erc7677-types.ts @@ -0,0 +1,33 @@ +// https://eips.ethereum.org/EIPS/eip-7677 + +import type { UserOperationHex } from '../core' + +// [userOp, entryPoint, chainId, context] +export type GetPaymasterStubDataParams = [ + UserOperationHex, // userOp + string, // EntryPoint + string, // Chain ID + Record, // Context +] + +export type GetPaymasterStubDataResult = { + sponsor?: { name: string; icon?: string } // Sponsor info + paymaster: string // Paymaster address + paymasterData: string // Paymaster data + paymasterVerificationGasLimit: string // Paymaster validation gas + paymasterPostOpGasLimit: string // Paymaster post-op gas + isFinal?: boolean // Indicates that the caller does not need to call pm_getPaymasterData +} + +// [userOp, entryPoint, chainId, context] +export type GetPaymasterDataParams = [ + UserOperationHex, // userOp + string, // Entrypoint + string, // Chain ID + Record, // Context +] + +export type GetPaymasterDataResult = { + paymaster: string // Paymaster address + paymasterData: string // Paymaster data +} diff --git a/src/paymasters/index.ts b/src/paymasters/index.ts index 6b0b773..a7b02f9 100644 --- a/src/paymasters/index.ts +++ b/src/paymasters/index.ts @@ -1 +1,3 @@ export * from './public-paymaster' +export * from './PaymasterService' +export * from './erc7677-types' diff --git a/stats.html b/stats.html index 81215bd..116c1f4 100644 --- a/stats.html +++ b/stats.html @@ -4929,7 +4929,7 @@