From 22ceffc80c2c21ed469da02df1c776d2a79df04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Fri, 10 Jan 2025 16:01:29 +0100 Subject: [PATCH 01/55] Add a specific userOperations module --- .../src/packs/safe-4337/Safe4337Pack.test.ts | 12 +- .../src/packs/safe-4337/Safe4337Pack.ts | 138 +++--------------- .../src/packs/safe-4337/SafeOperation.ts | 7 +- .../src/packs/safe-4337/constants.ts | 2 +- .../relay-kit/src/packs/safe-4337/types.ts | 2 +- .../src/packs/safe-4337/utils/entrypoint.ts | 30 +++- .../packs/safe-4337/utils/userOperations.ts | 129 ++++++++++++++++ packages/types-kit/src/types.ts | 22 ++- 8 files changed, 209 insertions(+), 133 deletions(-) create mode 100644 packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts index 117d32fe8..3661f64d5 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts @@ -5,7 +5,7 @@ import Safe, * as protocolKit from '@safe-global/protocol-kit' import { WebAuthnCredentials } from '@safe-global/protocol-kit/tests/e2e/utils/webauthnShim' import { createMockPasskey } from '@safe-global/protocol-kit/tests/e2e/utils/passkeys' import { - getAddModulesLibDeployment, + getSafeModuleSetupDeployment, getSafe4337ModuleDeployment } from '@safe-global/safe-modules-deployments' import { MetaTransactionData, OperationType } from '@safe-global/types-kit' @@ -38,7 +38,7 @@ jest.mock('./utils', () => ({ })) let safe4337ModuleAddress: viem.Hash -let addModulesLibAddress: string +let safeModulesSetupAddress: string describe('Safe4337Pack', () => { beforeAll(async () => { @@ -48,7 +48,7 @@ describe('Safe4337Pack', () => { version: '0.2.0', network })?.networkAddresses[network] as viem.Hash - addModulesLibAddress = getAddModulesLibDeployment({ + safeModulesSetupAddress = getSafeModuleSetupDeployment({ released: true, version: '0.2.0', network @@ -250,7 +250,7 @@ describe('Safe4337Pack', () => { safeAccountConfig: { owners: [fixtures.OWNER_1, fixtures.OWNER_2], threshold: 1, - to: addModulesLibAddress, + to: safeModulesSetupAddress, data: viem.encodeFunctionData({ abi: constants.ABI, functionName: 'enableModules', @@ -295,7 +295,7 @@ describe('Safe4337Pack', () => { }) const enable4337ModuleTransaction = { - to: addModulesLibAddress, + to: safeModulesSetupAddress, value: '0', data: enableModulesData, operation: OperationType.DelegateCall @@ -641,7 +641,7 @@ describe('Safe4337Pack', () => { }) const enable4337ModuleTransaction = { - to: addModulesLibAddress, + to: safeModulesSetupAddress, value: '0', data: enableModulesData, operation: OperationType.DelegateCall diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index e45507f4a..e1eabf592 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -11,15 +11,13 @@ import Safe, { import { RelayKitBasePack } from '@safe-global/relay-kit/RelayKitBasePack' import { isSafeOperationResponse, - MetaTransactionData, OperationType, SafeOperationConfirmation, SafeOperationResponse, - SafeSignature, - UserOperation + SafeSignature } from '@safe-global/types-kit' import { - getAddModulesLibDeployment, + getSafeModuleSetupDeployment, getSafe4337ModuleDeployment, getSafeWebAuthnShareSignerDeployment } from '@safe-global/safe-modules-deployments' @@ -34,26 +32,24 @@ import { UserOperationReceipt, UserOperationWithPayload, PaymasterOptions, - ERC20PaymasterOption, BundlerClient } from './types' import { ABI, DEFAULT_SAFE_VERSION, DEFAULT_SAFE_MODULES_VERSION, - RPC_4337_CALLS, - ENTRYPOINT_ABI + RPC_4337_CALLS } from './constants' import { addDummySignature, - encodeMultiSendCallData, getEip4337BundlerProvider, signSafeOp, userOperationToHexValues } from './utils' -import { entryPointToSafeModules, EQ_OR_GT_0_3_0 } from './utils/entrypoint' +import { entryPointToSafeModules } from './utils/entrypoint' import { PimlicoFeeEstimator } from './estimators/PimlicoFeeEstimator' import getRelayKitVersion from './utils/getRelayKitVersion' +import { createUserOperation } from './utils/userOperations' const MAX_ERC20_AMOUNT_TO_APPROVE = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn @@ -153,24 +149,18 @@ export class Safe4337Pack extends RelayKitBasePack<{ const bundlerClient = getEip4337BundlerProvider(bundlerUrl) const chainId = await bundlerClient.request({ method: RPC_4337_CALLS.CHAIN_ID }) - let addModulesLibAddress = customContracts?.addModulesLibAddress + let safeModulesSetupAddress = customContracts?.safeModulesSetupAddress const network = parseInt(chainId, 16).toString() const safeModulesVersion = initOptions.safeModulesVersion || DEFAULT_SAFE_MODULES_VERSION - if (semverSatisfies(safeModulesVersion, EQ_OR_GT_0_3_0)) { - throw new Error( - `Incompatibility detected: Safe modules version ${safeModulesVersion} is not supported. The SDK can use 0.2.0 only.` - ) - } - - if (!addModulesLibAddress) { - const addModulesDeployment = getAddModulesLibDeployment({ + if (!safeModulesSetupAddress) { + const safeModuleSetupDeployment = getSafeModuleSetupDeployment({ released: true, version: safeModulesVersion, network }) - addModulesLibAddress = addModulesDeployment?.networkAddresses[network] + safeModulesSetupAddress = safeModuleSetupDeployment?.networkAddresses[network] } let safe4337ModuleAddress = customContracts?.safe4337ModuleAddress @@ -183,9 +173,9 @@ export class Safe4337Pack extends RelayKitBasePack<{ safe4337ModuleAddress = safe4337ModuleDeployment?.networkAddresses[network] } - if (!addModulesLibAddress || !safe4337ModuleAddress) { + if (!safeModulesSetupAddress || !safe4337ModuleAddress) { throw new Error( - `Safe4337Module and/or AddModulesLib not available for chain ${network} and modules version ${safeModulesVersion}` + `Safe4337Module and/or SafeModuleSetup not available for chain ${network} and modules version ${safeModulesVersion}` ) } @@ -237,7 +227,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ // first setup transaction: Enable 4337 module const enable4337ModuleTransaction = { - to: addModulesLibAddress, + to: safeModulesSetupAddress, value: '0', data: encodeFunctionData({ abi: ABI, @@ -496,72 +486,18 @@ export class Safe4337Pack extends RelayKitBasePack<{ transactions, options = {} }: Safe4337CreateTransactionProps): Promise { - const safeAddress = await this.protocolKit.getAddress() - const nonce = await this.#getSafeNonceFromEntrypoint(safeAddress) const { amountToApprove, validUntil, validAfter, feeEstimator } = options - if (amountToApprove) { - const paymasterOptions = this.#paymasterOptions as ERC20PaymasterOption - - if (!paymasterOptions.paymasterTokenAddress) { - throw new Error('Paymaster must be initialized') - } - - const approveToPaymasterTransaction = { - to: paymasterOptions.paymasterTokenAddress, - data: encodeFunctionData({ - abi: ABI, - functionName: 'approve', - args: [paymasterOptions.paymasterAddress, amountToApprove] - }), - value: '0', - operation: OperationType.Call // Call for approve - } - - transactions.push(approveToPaymasterTransaction) - } - - const isBatch = transactions.length > 1 - const multiSendAddress = this.protocolKit.getMultiSendAddress() - - const callData = isBatch - ? this.#encodeExecuteUserOpCallData({ - to: multiSendAddress, - value: '0', - data: encodeMultiSendCallData(transactions), - operation: OperationType.DelegateCall - }) - : this.#encodeExecuteUserOpCallData(transactions[0]) - - const paymasterAndData = - this.#paymasterOptions && 'paymasterAddress' in this.#paymasterOptions - ? this.#paymasterOptions.paymasterAddress - : '0x' - - const userOperation: UserOperation = { - sender: safeAddress, - nonce: nonce, - initCode: '0x', - callData, - callGasLimit: 1n, - verificationGasLimit: 1n, - preVerificationGas: 1n, - maxFeePerGas: 1n, - maxPriorityFeePerGas: 1n, - paymasterAndData, - signature: '0x' - } - + const userOperation = await createUserOperation(this.protocolKit, transactions, { + entryPoint: this.#ENTRYPOINT_ADDRESS, + paymasterOptions: this.#paymasterOptions, + amountToApprove + }) + console.log('userOperation', userOperation) if (this.#onchainIdentifier) { userOperation.callData += this.#onchainIdentifier } - const isSafeDeployed = await this.protocolKit.isSafeDeployed() - - if (!isSafeDeployed) { - userOperation.initCode = await this.protocolKit.getInitCode() - } - const safeOperation = new EthSafeOperation(userOperation, { chainId: this.#chainId, moduleAddress: this.#SAFE_4337_MODULE_ADDRESS, @@ -792,44 +728,6 @@ export class Safe4337Pack extends RelayKitBasePack<{ return this.#bundlerClient.request({ method: RPC_4337_CALLS.CHAIN_ID }) } - /** - * Gets account nonce from the bundler. - * - * @param {string} safeAddress - Account address for which the nonce is to be fetched. - * @returns {Promise} The Promise object will resolve to the account nonce. - */ - async #getSafeNonceFromEntrypoint(safeAddress: string): Promise { - const safeProvider = this.protocolKit.getSafeProvider() - - const newNonce = await safeProvider.readContract({ - address: this.#ENTRYPOINT_ADDRESS || '0x', - abi: ENTRYPOINT_ABI, - functionName: 'getNonce', - args: [safeAddress, 0n] - }) - - return newNonce.toString() - } - - /** - * Encode the UserOperation execution from a transaction. - * - * @param {MetaTransactionData} transaction - The transaction data to encode. - * @return {string} The encoded call data string. - */ - #encodeExecuteUserOpCallData(transaction: MetaTransactionData): string { - return encodeFunctionData({ - abi: ABI, - functionName: 'executeUserOp', - args: [ - transaction.to, - BigInt(transaction.value), - transaction.data as Hex, - transaction.operation || OperationType.Call - ] - }) - } - getOnchainIdentifier(): string { return this.#onchainIdentifier } diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts index 3bf833787..ef149ffa7 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts @@ -4,7 +4,8 @@ import { SafeOperation, SafeSignature, SafeUserOperation, - UserOperation + UserOperation, + UserOperationV06 } from '@safe-global/types-kit' import { buildSignatureBytes } from '@safe-global/protocol-kit' import { calculateSafeUserOperationHash } from './utils' @@ -32,14 +33,14 @@ class EthSafeOperation implements SafeOperation { this.data = { safe: userOperation.sender, nonce: BigInt(userOperation.nonce), - initCode: userOperation.initCode, + initCode: (userOperation as UserOperationV06).initCode, callData: userOperation.callData, callGasLimit: userOperation.callGasLimit, verificationGasLimit: userOperation.verificationGasLimit, preVerificationGas: userOperation.preVerificationGas, maxFeePerGas: userOperation.maxFeePerGas, maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas, - paymasterAndData: userOperation.paymasterAndData, + paymasterAndData: (userOperation as UserOperationV06).paymasterAndData, validAfter: validAfter || 0, validUntil: validUntil || 0, entryPoint diff --git a/packages/relay-kit/src/packs/safe-4337/constants.ts b/packages/relay-kit/src/packs/safe-4337/constants.ts index 4703bac47..4d6a00ab5 100644 --- a/packages/relay-kit/src/packs/safe-4337/constants.ts +++ b/packages/relay-kit/src/packs/safe-4337/constants.ts @@ -1,7 +1,7 @@ import { parseAbi } from 'viem' export const DEFAULT_SAFE_VERSION = '1.4.1' -export const DEFAULT_SAFE_MODULES_VERSION = '0.2.0' +export const DEFAULT_SAFE_MODULES_VERSION = '0.3.0' export const EIP712_SAFE_OPERATION_TYPE = { SafeOp: [ diff --git a/packages/relay-kit/src/packs/safe-4337/types.ts b/packages/relay-kit/src/packs/safe-4337/types.ts index 77ba49ae0..d5e0d11dd 100644 --- a/packages/relay-kit/src/packs/safe-4337/types.ts +++ b/packages/relay-kit/src/packs/safe-4337/types.ts @@ -49,7 +49,7 @@ export type Safe4337InitOptions = { customContracts?: { entryPointAddress?: string safe4337ModuleAddress?: string - addModulesLibAddress?: string + safeModulesSetupAddress?: string safeWebAuthnSharedSignerAddress?: string } options: ExistingSafeOptions | PredictedSafeOptions diff --git a/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts b/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts index 3626f3859..b941458c2 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts @@ -1,4 +1,5 @@ -import { ENTRYPOINT_ADDRESS_V06, ENTRYPOINT_ADDRESS_V07 } from '../constants' +import Safe from '@safe-global/protocol-kit' +import { ENTRYPOINT_ABI, ENTRYPOINT_ADDRESS_V06, ENTRYPOINT_ADDRESS_V07 } from '../constants' const EQ_0_2_0 = '0.2.0' @@ -20,3 +21,30 @@ export function entryPointToSafeModules(entryPoint: string) { export function isEntryPointV6(address: string): boolean { return sameString(address, ENTRYPOINT_ADDRESS_V06) } + +export function isEntryPointV7(address: string): boolean { + return sameString(address, ENTRYPOINT_ADDRESS_V07) +} + +/** + * Gets account nonce from the bundler. + * + * @param {string} safeAddress - Account address for which the nonce is to be fetched. + * @returns {Promise} The Promise object will resolve to the account nonce. + */ +export async function getSafeNonceFromEntrypoint( + protocolKit: Safe, + safeAddress: string, + entryPointAddress: string +): Promise { + const safeProvider = protocolKit.getSafeProvider() + + const newNonce = await safeProvider.readContract({ + address: entryPointAddress || '0x', + abi: ENTRYPOINT_ABI, + functionName: 'getNonce', + args: [safeAddress, 0n] + }) + + return newNonce.toString() +} diff --git a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts new file mode 100644 index 000000000..149f3d6c7 --- /dev/null +++ b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts @@ -0,0 +1,129 @@ +import Safe, { getSafeProxyFactoryContract } from '@safe-global/protocol-kit' +import { MetaTransactionData, OperationType, UserOperation } from '@safe-global/types-kit' +import { getSafeNonceFromEntrypoint, isEntryPointV6 } from './entrypoint' +import { encodeFunctionData, Hex } from 'viem' +import { ABI } from '../constants' +import { ERC20PaymasterOption, PaymasterOptions } from '../types' +import { encodeMultiSendCallData } from '../utils' + +/** + * Encode the UserOperation execution from a transaction. + * + * @param {MetaTransactionData} transaction - The transaction data to encode. + * @return {string} The encoded call data string. + */ +export function encodeExecuteUserOpCallData(transaction: MetaTransactionData): string { + return encodeFunctionData({ + abi: ABI, + functionName: 'executeUserOp', + args: [ + transaction.to, + BigInt(transaction.value), + transaction.data as Hex, + transaction.operation || OperationType.Call + ] + }) +} + +export async function getCallData( + protocolKit: Safe, + transactions: MetaTransactionData[], + paymasterOptions: ERC20PaymasterOption, + amountToApprove?: bigint +): Promise { + if (amountToApprove) { + const approveToPaymasterTransaction = { + to: paymasterOptions.paymasterTokenAddress, + data: encodeFunctionData({ + abi: ABI, + functionName: 'approve', + args: [paymasterOptions.paymasterAddress, amountToApprove] + }), + value: '0', + operation: OperationType.Call // Call for approve + } + + transactions.push(approveToPaymasterTransaction) + } + + const isBatch = transactions.length > 1 + const multiSendAddress = protocolKit.getMultiSendAddress() + + const callData = isBatch + ? encodeExecuteUserOpCallData({ + to: multiSendAddress, + value: '0', + data: encodeMultiSendCallData(transactions), + operation: OperationType.DelegateCall + }) + : encodeExecuteUserOpCallData(transactions[0]) + + return callData +} + +export async function createUserOperation( + protocolKit: Safe, + transactions: MetaTransactionData[], + { + amountToApprove, + entryPoint, + paymasterOptions + }: { entryPoint: string; amountToApprove?: bigint; paymasterOptions: PaymasterOptions } +): Promise { + const safeAddress = await protocolKit.getAddress() + const nonce = await getSafeNonceFromEntrypoint(protocolKit, safeAddress, entryPoint) + const isSafeDeployed = await protocolKit.isSafeDeployed() + const paymasterAndData = + paymasterOptions && 'paymasterAddress' in paymasterOptions + ? paymasterOptions.paymasterAddress + : '0x' + const callData = await getCallData( + protocolKit, + transactions, + paymasterOptions as ERC20PaymasterOption, + amountToApprove + ) + const initCode = isSafeDeployed ? '0x' : await protocolKit.getInitCode() + + if (isEntryPointV6(entryPoint)) { + return { + sender: safeAddress, + nonce, + initCode, + callData, + callGasLimit: 1n, + verificationGasLimit: 1n, + preVerificationGas: 1n, + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, + paymasterAndData, + signature: '0x' + } + } + + const factoryContract = isSafeDeployed + ? undefined + : await getSafeProxyFactoryContract({ + safeProvider: protocolKit.getSafeProvider(), + safeVersion: protocolKit.getContractVersion() + }) + const factory = factoryContract?.contractAddress + + return { + sender: safeAddress, + nonce, + factory, + factoryData: initCode, + callData, + callGasLimit: 1n, + verificationGasLimit: 1n, + preVerificationGas: 1n, + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, + paymaster: paymasterAndData, + paymasterData: '0x', + paymasterVerificationGasLimit: undefined, + paymasterPostOpGasLimit: undefined, + signature: '0x' + } +} diff --git a/packages/types-kit/src/types.ts b/packages/types-kit/src/types.ts index cec7c4ca5..0628c8e2b 100644 --- a/packages/types-kit/src/types.ts +++ b/packages/types-kit/src/types.ts @@ -260,7 +260,7 @@ export interface MetaTransactionOptions { isSponsored?: boolean } -export type UserOperation = { +export type UserOperationV06 = { sender: string nonce: string initCode: string @@ -274,6 +274,26 @@ export type UserOperation = { signature: string } +export type UserOperationV07 = { + sender: string + nonce: string + factory?: string + factoryData?: string + callData: string + callGasLimit: bigint + verificationGasLimit: bigint + preVerificationGas: bigint + maxFeePerGas: bigint + maxPriorityFeePerGas: bigint + paymaster?: string + paymasterData?: string + paymasterVerificationGasLimit?: bigint + paymasterPostOpGasLimit?: bigint + signature: string +} + +export type UserOperation = UserOperationV06 | UserOperationV07 + export type SafeUserOperation = { safe: string nonce: bigint From 5ab7193eeea574533362e6cc114f1ab6bc0e1e38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Mon, 13 Jan 2025 15:38:23 +0100 Subject: [PATCH 02/55] eth_estimateUserOperationGas v0.7 support --- .../src/packs/safe-4337/Safe4337Pack.test.ts | 4 +- .../src/packs/safe-4337/Safe4337Pack.ts | 12 +- .../src/packs/safe-4337/SafeOperation.ts | 104 +++++++++++++++++- .../estimators/PimlicoFeeEstimator.ts | 15 ++- .../relay-kit/src/packs/safe-4337/utils.ts | 13 ++- 5 files changed, 130 insertions(+), 18 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts index 3661f64d5..38488b079 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts @@ -764,7 +764,7 @@ describe('Safe4337Pack', () => { expect(requestMock).toHaveBeenCalledWith({ method: constants.RPC_4337_CALLS.SEND_USER_OPERATION, params: [ - utils.userOperationToHexValues(safeOperation.toUserOperation()), + utils.userOperationToHexValues(safeOperation.toUserOperation(), fixtures.ENTRYPOINTS[0]), fixtures.ENTRYPOINTS[0] ] }) @@ -861,7 +861,7 @@ describe('Safe4337Pack', () => { expect(requestMock).toHaveBeenCalledWith({ method: constants.RPC_4337_CALLS.SEND_USER_OPERATION, params: [ - utils.userOperationToHexValues(safeOperation.toUserOperation()), + utils.userOperationToHexValues(safeOperation.toUserOperation(), fixtures.ENTRYPOINTS[0]), fixtures.ENTRYPOINTS[0] ] }) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index e1eabf592..6052c4ad7 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -424,7 +424,8 @@ export class Safe4337Pack extends RelayKitBasePack<{ safeOperation.toUserOperation(), this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, threshold - ) + ), + this.#ENTRYPOINT_ADDRESS ), this.#ENTRYPOINT_ADDRESS ] @@ -493,7 +494,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ paymasterOptions: this.#paymasterOptions, amountToApprove }) - console.log('userOperation', userOperation) + if (this.#onchainIdentifier) { userOperation.callData += this.#onchainIdentifier } @@ -506,6 +507,8 @@ export class Safe4337Pack extends RelayKitBasePack<{ validAfter }) + console.log('safeOperation', safeOperation) + return await this.getEstimateFee({ safeOperation, feeEstimator @@ -677,7 +680,10 @@ export class Safe4337Pack extends RelayKitBasePack<{ return this.#bundlerClient.request({ method: RPC_4337_CALLS.SEND_USER_OPERATION, - params: [userOperationToHexValues(userOperation), this.#ENTRYPOINT_ADDRESS] + params: [ + userOperationToHexValues(userOperation, this.#ENTRYPOINT_ADDRESS), + this.#ENTRYPOINT_ADDRESS + ] }) } diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts index ef149ffa7..5d778a24f 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts @@ -1,14 +1,16 @@ -import { Hex, encodePacked, toHex } from 'viem' +import { Hex, concat, encodePacked, pad, toHex, hexToBytes, sliceHex, getAddress } from 'viem' import { EstimateGasData, SafeOperation, SafeSignature, SafeUserOperation, UserOperation, - UserOperationV06 + UserOperationV06, + UserOperationV07 } from '@safe-global/types-kit' import { buildSignatureBytes } from '@safe-global/protocol-kit' import { calculateSafeUserOperationHash } from './utils' +import { isEntryPointV6, isEntryPointV7 } from './utils/entrypoint' type SafeOperationOptions = { moduleAddress: string @@ -23,6 +25,7 @@ class EthSafeOperation implements SafeOperation { signatures: Map = new Map() moduleAddress: string chainId: bigint + entryPoint: string constructor( userOperation: UserOperation, @@ -30,17 +33,49 @@ class EthSafeOperation implements SafeOperation { ) { this.chainId = chainId this.moduleAddress = moduleAddress + this.entryPoint = entryPoint + + let initCode + let paymasterAndData + + console.log('userOperation', userOperation) + + if (isEntryPointV7(entryPoint)) { + const userOpV07 = userOperation as UserOperationV07 + + initCode = userOpV07.factory + ? concat([userOpV07.factory as Hex, (userOpV07.factoryData as Hex) || ('0x' as Hex)]) + : '0x' + paymasterAndData = userOpV07.paymaster + ? concat([ + userOpV07.paymaster as Hex, + pad(toHex(userOpV07.paymasterVerificationGasLimit || 0n), { + size: 16 + }), + pad(toHex(userOpV07.paymasterPostOpGasLimit || 0n), { + size: 16 + }), + (userOpV07.paymasterData as Hex) || ('0x' as Hex) + ]) + : '0x' + } else { + const userOpV06 = userOperation as UserOperationV06 + + initCode = userOpV06.initCode + paymasterAndData = userOpV06.paymasterAndData + } + this.data = { safe: userOperation.sender, nonce: BigInt(userOperation.nonce), - initCode: (userOperation as UserOperationV06).initCode, + initCode, callData: userOperation.callData, callGasLimit: userOperation.callGasLimit, verificationGasLimit: userOperation.verificationGasLimit, preVerificationGas: userOperation.preVerificationGas, maxFeePerGas: userOperation.maxFeePerGas, maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas, - paymasterAndData: (userOperation as UserOperationV06).paymasterAndData, + paymasterAndData, validAfter: validAfter || 0, validUntil: validUntil || 0, entryPoint @@ -74,17 +109,39 @@ class EthSafeOperation implements SafeOperation { } toUserOperation(): UserOperation { + if (isEntryPointV6(this.entryPoint)) { + return { + sender: this.data.safe, + nonce: toHex(this.data.nonce), + initCode: this.data.initCode, + callData: this.data.callData, + callGasLimit: this.data.callGasLimit, + verificationGasLimit: this.data.verificationGasLimit, + preVerificationGas: this.data.preVerificationGas, + maxFeePerGas: this.data.maxFeePerGas, + maxPriorityFeePerGas: this.data.maxPriorityFeePerGas, + paymasterAndData: this.data.paymasterAndData, + signature: encodePacked( + ['uint48', 'uint48', 'bytes'], + [this.data.validAfter, this.data.validUntil, this.encodedSignatures() as Hex] + ) + } + } + + const factoryData = this.unpackInitCode(this.data.initCode) + const paymasterData = this.unpackPaymasterAndData(this.data.paymasterAndData) + return { sender: this.data.safe, nonce: toHex(this.data.nonce), - initCode: this.data.initCode, + ...factoryData, callData: this.data.callData, callGasLimit: this.data.callGasLimit, verificationGasLimit: this.data.verificationGasLimit, preVerificationGas: this.data.preVerificationGas, maxFeePerGas: this.data.maxFeePerGas, maxPriorityFeePerGas: this.data.maxPriorityFeePerGas, - paymasterAndData: this.data.paymasterAndData, + ...paymasterData, signature: encodePacked( ['uint48', 'uint48', 'bytes'], [this.data.validAfter, this.data.validUntil, this.encodedSignatures() as Hex] @@ -95,6 +152,41 @@ class EthSafeOperation implements SafeOperation { getHash(): string { return calculateSafeUserOperationHash(this.data, this.chainId, this.moduleAddress) } + + unpackPaymasterAndData( + paymasterAndData: string + ): Pick< + UserOperationV07, + 'paymaster' | 'paymasterVerificationGasLimit' | 'paymasterPostOpGasLimit' | 'paymasterData' + > { + const paymasterAndDataBytes = hexToBytes(paymasterAndData as Hex) + const isZero = paymasterAndDataBytes.every((byte) => byte === 0) + + return paymasterAndDataBytes.length > 0 && !isZero + ? { + paymaster: getAddress(sliceHex(paymasterAndData as Hex, 0, 20)), + paymasterVerificationGasLimit: BigInt(sliceHex(paymasterAndData as Hex, 20, 36)), + paymasterPostOpGasLimit: BigInt(sliceHex(paymasterAndData as Hex, 36, 52)), + paymasterData: sliceHex(paymasterAndData as Hex, 52) + } + : { + paymaster: '0x', + paymasterData: '0x', + paymasterVerificationGasLimit: undefined, + paymasterPostOpGasLimit: undefined + } + } + + unpackInitCode(initCode: string): Pick { + const initCodeBytes = hexToBytes(initCode as Hex) + + return initCodeBytes.length > 0 + ? { + factory: getAddress(sliceHex(initCode as Hex, 0, 20)), + factoryData: sliceHex(initCode as Hex, 20) + } + : {} + } } export default EthSafeOperation diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.ts b/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.ts index 697fc552f..16bde6f46 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.ts @@ -37,22 +37,27 @@ export class PimlicoFeeEstimator implements IFeeEstimator { const gasEstimate = await paymasterClient.request({ method: RPC_4337_CALLS.SPONSOR_USER_OPERATION, params: sponsorshipPolicyId - ? [userOperationToHexValues(userOperation), entryPoint, { sponsorshipPolicyId }] - : [userOperationToHexValues(userOperation), entryPoint] + ? [userOperationToHexValues(userOperation, entryPoint), entryPoint, { sponsorshipPolicyId }] + : [userOperationToHexValues(userOperation, entryPoint), entryPoint] }) + console.log('getPaymasterEstimation', gasEstimate) return gasEstimate } async #getFeeData( bundlerClient: BundlerClient ): Promise> { - const { - fast: { maxFeePerGas, maxPriorityFeePerGas } - } = await bundlerClient.request({ + const feeData = await bundlerClient.request({ method: 'pimlico_getUserOperationGasPrice' }) + console.log('getFeeData', feeData) + + const { + fast: { maxFeePerGas, maxPriorityFeePerGas } + } = feeData + return { maxFeePerGas: BigInt(maxFeePerGas), maxPriorityFeePerGas: BigInt(maxPriorityFeePerGas) diff --git a/packages/relay-kit/src/packs/safe-4337/utils.ts b/packages/relay-kit/src/packs/safe-4337/utils.ts index 7fcf3d076..2494f56f7 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils.ts @@ -23,6 +23,7 @@ import { } from '@safe-global/protocol-kit' import { ABI, EIP712_SAFE_OPERATION_TYPE } from './constants' import { BundlerClient, PimlicoCustomRpcSchema } from './types' +import { isEntryPointV7 } from './utils/entrypoint' /** * Gets the EIP-4337 bundler provider. @@ -128,7 +129,7 @@ export function calculateSafeUserOperationHash( * @param {UserOperation} userOperation - The UserOperation object whose values are to be converted. * @returns {UserOperation} A new UserOperation object with the values converted to hexadecimal. */ -export function userOperationToHexValues(userOperation: UserOperation) { +export function userOperationToHexValues(userOperation: UserOperation, entryPointAddress: string) { const userOperationWithHexValues = { ...userOperation, nonce: toHex(BigInt(userOperation.nonce)), @@ -136,7 +137,15 @@ export function userOperationToHexValues(userOperation: UserOperation) { verificationGasLimit: toHex(userOperation.verificationGasLimit), preVerificationGas: toHex(userOperation.preVerificationGas), maxFeePerGas: toHex(userOperation.maxFeePerGas), - maxPriorityFeePerGas: toHex(userOperation.maxPriorityFeePerGas) + maxPriorityFeePerGas: toHex(userOperation.maxPriorityFeePerGas), + ...(isEntryPointV7(entryPointAddress) + ? { + paymaster: null, + paymasterData: null, + paymasterVerificationGasLimit: null, + paymasterPostOpGasLimit: null + } + : {}) } return userOperationWithHexValues From ee61a99998fa7dabcb719f87496b149e575ff8db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Mon, 13 Jan 2025 16:35:41 +0100 Subject: [PATCH 03/55] Fix initCode --- .../src/packs/safe-4337/SafeOperation.ts | 42 ++----------- .../packs/safe-4337/utils/userOperations.ts | 59 +++++++++++++++---- 2 files changed, 50 insertions(+), 51 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts index 5d778a24f..0c3a68c66 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts @@ -1,4 +1,4 @@ -import { Hex, concat, encodePacked, pad, toHex, hexToBytes, sliceHex, getAddress } from 'viem' +import { Hex, concat, encodePacked, pad, toHex } from 'viem' import { EstimateGasData, SafeOperation, @@ -11,6 +11,7 @@ import { import { buildSignatureBytes } from '@safe-global/protocol-kit' import { calculateSafeUserOperationHash } from './utils' import { isEntryPointV6, isEntryPointV7 } from './utils/entrypoint' +import { unpackInitCode, unpackPaymasterAndData } from './utils/userOperations' type SafeOperationOptions = { moduleAddress: string @@ -128,8 +129,8 @@ class EthSafeOperation implements SafeOperation { } } - const factoryData = this.unpackInitCode(this.data.initCode) - const paymasterData = this.unpackPaymasterAndData(this.data.paymasterAndData) + const factoryData = unpackInitCode(this.data.initCode) + const paymasterData = unpackPaymasterAndData(this.data.paymasterAndData) return { sender: this.data.safe, @@ -152,41 +153,6 @@ class EthSafeOperation implements SafeOperation { getHash(): string { return calculateSafeUserOperationHash(this.data, this.chainId, this.moduleAddress) } - - unpackPaymasterAndData( - paymasterAndData: string - ): Pick< - UserOperationV07, - 'paymaster' | 'paymasterVerificationGasLimit' | 'paymasterPostOpGasLimit' | 'paymasterData' - > { - const paymasterAndDataBytes = hexToBytes(paymasterAndData as Hex) - const isZero = paymasterAndDataBytes.every((byte) => byte === 0) - - return paymasterAndDataBytes.length > 0 && !isZero - ? { - paymaster: getAddress(sliceHex(paymasterAndData as Hex, 0, 20)), - paymasterVerificationGasLimit: BigInt(sliceHex(paymasterAndData as Hex, 20, 36)), - paymasterPostOpGasLimit: BigInt(sliceHex(paymasterAndData as Hex, 36, 52)), - paymasterData: sliceHex(paymasterAndData as Hex, 52) - } - : { - paymaster: '0x', - paymasterData: '0x', - paymasterVerificationGasLimit: undefined, - paymasterPostOpGasLimit: undefined - } - } - - unpackInitCode(initCode: string): Pick { - const initCodeBytes = hexToBytes(initCode as Hex) - - return initCodeBytes.length > 0 - ? { - factory: getAddress(sliceHex(initCode as Hex, 0, 20)), - factoryData: sliceHex(initCode as Hex, 20) - } - : {} - } } export default EthSafeOperation diff --git a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts index 149f3d6c7..ddb1393bd 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts @@ -1,7 +1,12 @@ -import Safe, { getSafeProxyFactoryContract } from '@safe-global/protocol-kit' -import { MetaTransactionData, OperationType, UserOperation } from '@safe-global/types-kit' +import Safe from '@safe-global/protocol-kit' +import { + MetaTransactionData, + OperationType, + UserOperation, + UserOperationV07 +} from '@safe-global/types-kit' import { getSafeNonceFromEntrypoint, isEntryPointV6 } from './entrypoint' -import { encodeFunctionData, Hex } from 'viem' +import { encodeFunctionData, getAddress, Hex, hexToBytes, sliceHex } from 'viem' import { ABI } from '../constants' import { ERC20PaymasterOption, PaymasterOptions } from '../types' import { encodeMultiSendCallData } from '../utils' @@ -61,6 +66,43 @@ export async function getCallData( return callData } +export function unpackPaymasterAndData( + paymasterAndData: string +): Pick< + UserOperationV07, + 'paymaster' | 'paymasterVerificationGasLimit' | 'paymasterPostOpGasLimit' | 'paymasterData' +> { + const paymasterAndDataBytes = hexToBytes(paymasterAndData as Hex) + const isZero = paymasterAndDataBytes.every((byte) => byte === 0) + + return paymasterAndDataBytes.length > 0 && !isZero + ? { + paymaster: getAddress(sliceHex(paymasterAndData as Hex, 0, 20)), + paymasterVerificationGasLimit: BigInt(sliceHex(paymasterAndData as Hex, 20, 36)), + paymasterPostOpGasLimit: BigInt(sliceHex(paymasterAndData as Hex, 36, 52)), + paymasterData: sliceHex(paymasterAndData as Hex, 52) + } + : { + paymaster: '0x', + paymasterData: '0x', + paymasterVerificationGasLimit: undefined, + paymasterPostOpGasLimit: undefined + } +} + +export function unpackInitCode( + initCode: string +): Pick { + const initCodeBytes = hexToBytes(initCode as Hex) + + return initCodeBytes.length > 0 + ? { + factory: getAddress(sliceHex(initCode as Hex, 0, 20)), + factoryData: sliceHex(initCode as Hex, 20) + } + : {} +} + export async function createUserOperation( protocolKit: Safe, transactions: MetaTransactionData[], @@ -101,19 +143,10 @@ export async function createUserOperation( } } - const factoryContract = isSafeDeployed - ? undefined - : await getSafeProxyFactoryContract({ - safeProvider: protocolKit.getSafeProvider(), - safeVersion: protocolKit.getContractVersion() - }) - const factory = factoryContract?.contractAddress - return { sender: safeAddress, nonce, - factory, - factoryData: initCode, + ...unpackInitCode(initCode), callData, callGasLimit: 1n, verificationGasLimit: 1n, From 7a1a7674060b396188fa9106e3a44497271561eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Wed, 15 Jan 2025 09:20:40 +0100 Subject: [PATCH 04/55] Change SafeOp structure --- .../src/packs/safe-4337/Safe4337Pack.test.ts | 3 +- .../src/packs/safe-4337/Safe4337Pack.ts | 8 +-- .../src/packs/safe-4337/SafeOperation.ts | 56 +++++++++++++++---- .../src/packs/safe-4337/constants.ts | 20 ++++++- .../relay-kit/src/packs/safe-4337/types.ts | 10 +++- .../relay-kit/src/packs/safe-4337/utils.ts | 33 +++++++---- .../packs/safe-4337/utils/userOperations.ts | 29 +++++----- packages/types-kit/src/types.ts | 5 ++ 8 files changed, 121 insertions(+), 43 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts index 38488b079..0eec33ea5 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts @@ -722,7 +722,8 @@ describe('Safe4337Pack', () => { const safeOpHash = utils.calculateSafeUserOperationHash( safeOperation.data, BigInt(fixtures.CHAIN_ID), - fixtures.MODULE_ADDRESS + fixtures.MODULE_ADDRESS, + fixtures.ENTRYPOINTS[0] ) const passkeySignature = await safe4337Pack.protocolKit.signHash(safeOpHash) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index 6052c4ad7..ebb2fe565 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -431,6 +431,8 @@ export class Safe4337Pack extends RelayKitBasePack<{ ] }) + console.log('estimateUserOperationGas', estimateUserOperationGas) + if (estimateUserOperationGas) { safeOperation.addEstimations({ preVerificationGas: BigInt(estimateUserOperationGas.preVerificationGas), @@ -465,9 +467,6 @@ export class Safe4337Pack extends RelayKitBasePack<{ sponsorshipPolicyId: this.#paymasterOptions.sponsorshipPolicyId }) - safeOperation.data.paymasterAndData = - paymasterEstimation?.paymasterAndData || safeOperation.data.paymasterAndData - if (paymasterEstimation) { safeOperation.addEstimations(paymasterEstimation) } @@ -632,7 +631,8 @@ export class Safe4337Pack extends RelayKitBasePack<{ signature = await signSafeOp( safeOp.data, this.protocolKit.getSafeProvider(), - this.#SAFE_4337_MODULE_ADDRESS + this.#SAFE_4337_MODULE_ADDRESS, + this.#ENTRYPOINT_ADDRESS ) } else { const safeOpHash = safeOp.getHash() diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts index 0c3a68c66..9a17f5317 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts @@ -23,6 +23,7 @@ type SafeOperationOptions = { class EthSafeOperation implements SafeOperation { data: SafeUserOperation + userOperation: UserOperation signatures: Map = new Map() moduleAddress: string chainId: bigint @@ -35,6 +36,7 @@ class EthSafeOperation implements SafeOperation { this.chainId = chainId this.moduleAddress = moduleAddress this.entryPoint = entryPoint + this.userOperation = userOperation let initCode let paymasterAndData @@ -96,16 +98,43 @@ class EthSafeOperation implements SafeOperation { } addEstimations(estimations: EstimateGasData): void { - const keys: (keyof EstimateGasData)[] = [ - 'maxFeePerGas', - 'maxPriorityFeePerGas', - 'verificationGasLimit', - 'preVerificationGas', - 'callGasLimit' - ] - - for (const key of keys) { - this.data[key] = BigInt(estimations[key] || this.data[key]) + this.data.maxFeePerGas = BigInt(estimations.maxFeePerGas || this.data.maxFeePerGas) + this.data.maxPriorityFeePerGas = BigInt( + estimations.maxPriorityFeePerGas || this.data.maxPriorityFeePerGas + ) + this.data.verificationGasLimit = BigInt( + estimations.verificationGasLimit || this.data.verificationGasLimit + ) + this.data.preVerificationGas = BigInt( + estimations.preVerificationGas || this.data.preVerificationGas + ) + this.data.callGasLimit = BigInt(estimations.callGasLimit || this.data.callGasLimit) + + if (isEntryPointV7(this.entryPoint)) { + console.log('estimations', estimations) + const paymaster = estimations.paymaster || (this.userOperation as UserOperationV07).paymaster + const paymasterData = + estimations.paymasterData || (this.userOperation as UserOperationV07).paymasterData + + if (estimations.paymasterPostOpGasLimit || estimations.paymasterVerificationGasLimit) { + this.data['paymasterAndData'] = + 'paymaster' in estimations + ? concat([ + paymaster as Hex, + pad(toHex(estimations.paymasterVerificationGasLimit || 0n), { + size: 16 + }), + pad(toHex(estimations.paymasterPostOpGasLimit || 0n), { + size: 16 + }), + (paymasterData as Hex) || ('0x' as Hex) + ]) + : '0x' + console.log("this.data['paymasterAndData']", this.data['paymasterAndData']) + } + } else { + this.data['paymasterAndData'] = + estimations.paymasterAndData || (this.userOperation as UserOperationV06).paymasterAndData } } @@ -151,7 +180,12 @@ class EthSafeOperation implements SafeOperation { } getHash(): string { - return calculateSafeUserOperationHash(this.data, this.chainId, this.moduleAddress) + return calculateSafeUserOperationHash( + this.data, + this.chainId, + this.moduleAddress, + this.entryPoint + ) } } diff --git a/packages/relay-kit/src/packs/safe-4337/constants.ts b/packages/relay-kit/src/packs/safe-4337/constants.ts index 4d6a00ab5..c266897c4 100644 --- a/packages/relay-kit/src/packs/safe-4337/constants.ts +++ b/packages/relay-kit/src/packs/safe-4337/constants.ts @@ -3,7 +3,7 @@ import { parseAbi } from 'viem' export const DEFAULT_SAFE_VERSION = '1.4.1' export const DEFAULT_SAFE_MODULES_VERSION = '0.3.0' -export const EIP712_SAFE_OPERATION_TYPE = { +export const EIP712_SAFE_OPERATION_TYPE_V06 = { SafeOp: [ { type: 'address', name: 'safe' }, { type: 'uint256', name: 'nonce' }, @@ -21,6 +21,24 @@ export const EIP712_SAFE_OPERATION_TYPE = { ] } +export const EIP712_SAFE_OPERATION_TYPE_V07 = { + SafeOp: [ + { type: 'address', name: 'safe' }, + { type: 'uint256', name: 'nonce' }, + { type: 'bytes', name: 'initCode' }, + { type: 'bytes', name: 'callData' }, + { type: 'uint128', name: 'verificationGasLimit' }, + { type: 'uint128', name: 'callGasLimit' }, + { type: 'uint256', name: 'preVerificationGas' }, + { type: 'uint128', name: 'maxPriorityFeePerGas' }, + { type: 'uint128', name: 'maxFeePerGas' }, + { type: 'bytes', name: 'paymasterAndData' }, + { type: 'uint48', name: 'validAfter' }, + { type: 'uint48', name: 'validUntil' }, + { type: 'address', name: 'entryPoint' } + ] +} + export const ABI = parseAbi([ 'function enableModules(address[])', 'function multiSend(bytes memory transactions) public payable', diff --git a/packages/relay-kit/src/packs/safe-4337/types.ts b/packages/relay-kit/src/packs/safe-4337/types.ts index d5e0d11dd..773fcdd65 100644 --- a/packages/relay-kit/src/packs/safe-4337/types.ts +++ b/packages/relay-kit/src/packs/safe-4337/types.ts @@ -83,9 +83,13 @@ export type Safe4337ExecutableProps = { executable: EthSafeOperation | SafeOperationResponse } -export type EstimateSponsoredGasData = { - paymasterAndData: string -} & EstimateGasData +export type EstimateSponsoredGasData = ( + | { + paymasterAndData: string + } + | { paymaster: string; paymasterData: string } +) & + EstimateGasData type Log = { logIndex: string diff --git a/packages/relay-kit/src/packs/safe-4337/utils.ts b/packages/relay-kit/src/packs/safe-4337/utils.ts index 2494f56f7..bc1e53dec 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils.ts @@ -13,7 +13,8 @@ import { OperationType, MetaTransactionData, SafeSignature, - UserOperation + UserOperation, + UserOperationV07 } from '@safe-global/types-kit' import { EthSafeSignature, @@ -21,7 +22,7 @@ import { encodeMultiSendData, buildSignatureBytes } from '@safe-global/protocol-kit' -import { ABI, EIP712_SAFE_OPERATION_TYPE } from './constants' +import { ABI, EIP712_SAFE_OPERATION_TYPE_V06, EIP712_SAFE_OPERATION_TYPE_V07 } from './constants' import { BundlerClient, PimlicoCustomRpcSchema } from './types' import { isEntryPointV7 } from './utils/entrypoint' @@ -51,7 +52,8 @@ export function getEip4337BundlerProvider(bundlerUrl: string): BundlerClient { export async function signSafeOp( safeUserOperation: SafeUserOperation, safeProvider: SafeProvider, - safe4337ModuleAddress: string + safe4337ModuleAddress: string, + entryPoint: string ): Promise { const signer = await safeProvider.getExternalSigner() @@ -66,7 +68,9 @@ export async function signSafeOp( chainId: Number(chainId), verifyingContract: safe4337ModuleAddress }, - types: EIP712_SAFE_OPERATION_TYPE, + types: isEntryPointV7(entryPoint) + ? EIP712_SAFE_OPERATION_TYPE_V07 + : EIP712_SAFE_OPERATION_TYPE_V06, message: { ...safeUserOperation, nonce: toHex(safeUserOperation.nonce), @@ -110,14 +114,17 @@ export function encodeMultiSendCallData(transactions: MetaTransactionData[]): st export function calculateSafeUserOperationHash( safeUserOperation: SafeUserOperation, chainId: bigint, - safe4337ModuleAddress: string + safe4337ModuleAddress: string, + entryPoint: string ): string { return hashTypedData({ domain: { chainId: Number(chainId), verifyingContract: safe4337ModuleAddress }, - types: EIP712_SAFE_OPERATION_TYPE, + types: isEntryPointV7(entryPoint) + ? EIP712_SAFE_OPERATION_TYPE_V07 + : EIP712_SAFE_OPERATION_TYPE_V06, primaryType: 'SafeOp', message: safeUserOperation }) @@ -130,6 +137,8 @@ export function calculateSafeUserOperationHash( * @returns {UserOperation} A new UserOperation object with the values converted to hexadecimal. */ export function userOperationToHexValues(userOperation: UserOperation, entryPointAddress: string) { + const userOpV07 = userOperation as UserOperationV07 + const userOperationWithHexValues = { ...userOperation, nonce: toHex(BigInt(userOperation.nonce)), @@ -140,10 +149,14 @@ export function userOperationToHexValues(userOperation: UserOperation, entryPoin maxPriorityFeePerGas: toHex(userOperation.maxPriorityFeePerGas), ...(isEntryPointV7(entryPointAddress) ? { - paymaster: null, - paymasterData: null, - paymasterVerificationGasLimit: null, - paymasterPostOpGasLimit: null + paymaster: userOpV07.paymaster !== '0x' ? userOpV07.paymaster : null, + paymasterData: userOpV07.paymasterData !== '0x' ? userOpV07.paymasterData : null, + paymasterVerificationGasLimit: userOpV07.paymasterVerificationGasLimit + ? toHex(userOpV07.paymasterVerificationGasLimit) + : null, + paymasterPostOpGasLimit: userOpV07.paymasterPostOpGasLimit + ? toHex(userOpV07.paymasterPostOpGasLimit) + : null } : {}) } diff --git a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts index ddb1393bd..fad723e52 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts @@ -75,19 +75,22 @@ export function unpackPaymasterAndData( const paymasterAndDataBytes = hexToBytes(paymasterAndData as Hex) const isZero = paymasterAndDataBytes.every((byte) => byte === 0) - return paymasterAndDataBytes.length > 0 && !isZero - ? { - paymaster: getAddress(sliceHex(paymasterAndData as Hex, 0, 20)), - paymasterVerificationGasLimit: BigInt(sliceHex(paymasterAndData as Hex, 20, 36)), - paymasterPostOpGasLimit: BigInt(sliceHex(paymasterAndData as Hex, 36, 52)), - paymasterData: sliceHex(paymasterAndData as Hex, 52) - } - : { - paymaster: '0x', - paymasterData: '0x', - paymasterVerificationGasLimit: undefined, - paymasterPostOpGasLimit: undefined - } + const unpackedData = + paymasterAndDataBytes.length > 0 && !isZero + ? { + paymaster: getAddress(sliceHex(paymasterAndData as Hex, 0, 20)), + paymasterVerificationGasLimit: BigInt(sliceHex(paymasterAndData as Hex, 20, 36)), + paymasterPostOpGasLimit: BigInt(sliceHex(paymasterAndData as Hex, 36, 52)), + paymasterData: sliceHex(paymasterAndData as Hex, 52) + } + : { + paymaster: '0x', + paymasterData: '0x', + paymasterVerificationGasLimit: undefined, + paymasterPostOpGasLimit: undefined + } + + return unpackedData } export function unpackInitCode( diff --git a/packages/types-kit/src/types.ts b/packages/types-kit/src/types.ts index 0628c8e2b..cc852d78b 100644 --- a/packages/types-kit/src/types.ts +++ b/packages/types-kit/src/types.ts @@ -311,11 +311,16 @@ export type SafeUserOperation = { } export type EstimateGasData = { + paymasterAndData?: string + paymaster?: string + paymasterData?: string maxFeePerGas?: bigint maxPriorityFeePerGas?: bigint preVerificationGas?: bigint verificationGasLimit?: bigint callGasLimit?: bigint + paymasterVerificationGasLimit?: bigint + paymasterPostOpGasLimit?: bigint } export interface SafeOperation { From 36c2da906937bbcc13e2f6040d973792d015a63a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Fri, 17 Jan 2025 10:56:54 +0100 Subject: [PATCH 05/55] Working version for sponsored transactions --- .../src/packs/safe-4337/Safe4337Pack.ts | 22 +- .../src/packs/safe-4337/SafeOperation.ts | 206 +++++++----------- .../src/packs/safe-4337/utils/entrypoint.ts | 4 +- .../packs/safe-4337/utils/userOperations.ts | 6 +- packages/types-kit/src/types.ts | 17 +- 5 files changed, 103 insertions(+), 152 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index ebb2fe565..e5c592bcf 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -409,7 +409,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ const setupEstimationData = await feeEstimator?.setupEstimation?.({ bundlerUrl: this.#BUNDLER_URL, entryPoint: this.#ENTRYPOINT_ADDRESS, - userOperation: safeOperation.toUserOperation() + userOperation: safeOperation.getUserOperation() }) if (setupEstimationData) { @@ -421,7 +421,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ params: [ userOperationToHexValues( addDummySignature( - safeOperation.toUserOperation(), + safeOperation.getUserOperation(), this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, threshold ), @@ -431,8 +431,6 @@ export class Safe4337Pack extends RelayKitBasePack<{ ] }) - console.log('estimateUserOperationGas', estimateUserOperationGas) - if (estimateUserOperationGas) { safeOperation.addEstimations({ preVerificationGas: BigInt(estimateUserOperationGas.preVerificationGas), @@ -444,7 +442,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ const adjustEstimationData = await feeEstimator?.adjustEstimation?.({ bundlerUrl: this.#BUNDLER_URL, entryPoint: this.#ENTRYPOINT_ADDRESS, - userOperation: safeOperation.toUserOperation() + userOperation: safeOperation.getUserOperation() }) if (adjustEstimationData) { @@ -458,7 +456,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ const paymasterEstimation = await feeEstimator?.getPaymasterEstimation?.({ userOperation: addDummySignature( - safeOperation.toUserOperation(), + safeOperation.getUserOperation(), this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, threshold ), @@ -506,8 +504,6 @@ export class Safe4337Pack extends RelayKitBasePack<{ validAfter }) - console.log('safeOperation', safeOperation) - return await this.getEstimateFee({ safeOperation, feeEstimator @@ -629,7 +625,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ ] ) { signature = await signSafeOp( - safeOp.data, + safeOp.getSafeOperation(), this.protocolKit.getSafeProvider(), this.#SAFE_4337_MODULE_ADDRESS, this.#ENTRYPOINT_ADDRESS @@ -641,12 +637,12 @@ export class Safe4337Pack extends RelayKitBasePack<{ } } - const signedSafeOperation = new EthSafeOperation(safeOp.toUserOperation(), { + const signedSafeOperation = new EthSafeOperation(safeOp.getUserOperation(), { chainId: this.#chainId, moduleAddress: this.#SAFE_4337_MODULE_ADDRESS, entryPoint: this.#ENTRYPOINT_ADDRESS, - validUntil: safeOp.data.validUntil, - validAfter: safeOp.data.validAfter + validUntil: safeOp.options.validUntil, + validAfter: safeOp.options.validAfter }) safeOp.signatures.forEach((signature: SafeSignature) => { @@ -676,7 +672,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ safeOperation = executable } - const userOperation = safeOperation.toUserOperation() + const userOperation = safeOperation.getUserOperation() return this.#bundlerClient.request({ method: RPC_4337_CALLS.SEND_USER_OPERATION, diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts index 9a17f5317..754a516dd 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts @@ -2,6 +2,7 @@ import { Hex, concat, encodePacked, pad, toHex } from 'viem' import { EstimateGasData, SafeOperation, + SafeOperationOptions, SafeSignature, SafeUserOperation, UserOperation, @@ -10,45 +11,70 @@ import { } from '@safe-global/types-kit' import { buildSignatureBytes } from '@safe-global/protocol-kit' import { calculateSafeUserOperationHash } from './utils' -import { isEntryPointV6, isEntryPointV7 } from './utils/entrypoint' -import { unpackInitCode, unpackPaymasterAndData } from './utils/userOperations' - -type SafeOperationOptions = { - moduleAddress: string - entryPoint: string - chainId: bigint - validAfter?: number - validUntil?: number -} +import { isEntryPointV7 } from './utils/entrypoint' class EthSafeOperation implements SafeOperation { - data: SafeUserOperation + options: SafeOperationOptions userOperation: UserOperation signatures: Map = new Map() - moduleAddress: string - chainId: bigint - entryPoint: string - - constructor( - userOperation: UserOperation, - { chainId, entryPoint, validAfter, validUntil, moduleAddress }: SafeOperationOptions - ) { - this.chainId = chainId - this.moduleAddress = moduleAddress - this.entryPoint = entryPoint + + constructor(userOperation: UserOperation, options: SafeOperationOptions) { this.userOperation = userOperation + this.options = options + } + + getSignature(signer: string): SafeSignature | undefined { + return this.signatures.get(signer.toLowerCase()) + } + + addSignature(signature: SafeSignature): void { + this.signatures.set(signature.signer.toLowerCase(), signature) + } + + encodedSignatures(): string { + return buildSignatureBytes(Array.from(this.signatures.values())) + } + + addEstimations(estimations: EstimateGasData): void { + const userOpV06 = this.userOperation as UserOperationV06 + userOpV06.maxFeePerGas = BigInt(estimations.maxFeePerGas || userOpV06.maxFeePerGas) + userOpV06.maxPriorityFeePerGas = BigInt( + estimations.maxPriorityFeePerGas || userOpV06.maxPriorityFeePerGas + ) + userOpV06.verificationGasLimit = BigInt( + estimations.verificationGasLimit || userOpV06.verificationGasLimit + ) + userOpV06.preVerificationGas = BigInt( + estimations.preVerificationGas || userOpV06.preVerificationGas + ) + userOpV06.callGasLimit = BigInt(estimations.callGasLimit || userOpV06.callGasLimit) + + userOpV06.paymasterAndData = estimations.paymasterAndData || userOpV06.paymasterAndData + + if (isEntryPointV7(this.options.entryPoint)) { + const userOp = this.userOperation as UserOperationV07 + userOp.paymasterPostOpGasLimit = estimations.paymasterPostOpGasLimit + ? BigInt(estimations.paymasterPostOpGasLimit) || userOp.paymasterPostOpGasLimit + : undefined + userOp.paymasterVerificationGasLimit = estimations.paymasterVerificationGasLimit + ? BigInt(estimations.paymasterVerificationGasLimit) || userOp.paymasterVerificationGasLimit + : undefined + userOp.paymaster = estimations.paymaster || userOp.paymaster + userOp.paymasterData = estimations.paymasterData || userOp.paymasterData + } + } + getSafeOperation(): SafeUserOperation { let initCode let paymasterAndData - console.log('userOperation', userOperation) - - if (isEntryPointV7(entryPoint)) { - const userOpV07 = userOperation as UserOperationV07 + if (isEntryPointV7(this.options.entryPoint)) { + const userOpV07 = this.userOperation as UserOperationV07 initCode = userOpV07.factory ? concat([userOpV07.factory as Hex, (userOpV07.factoryData as Hex) || ('0x' as Hex)]) : '0x' + paymasterAndData = userOpV07.paymaster ? concat([ userOpV07.paymaster as Hex, @@ -62,129 +88,49 @@ class EthSafeOperation implements SafeOperation { ]) : '0x' } else { - const userOpV06 = userOperation as UserOperationV06 + const userOpV06 = this.userOperation as UserOperationV06 initCode = userOpV06.initCode paymasterAndData = userOpV06.paymasterAndData } - this.data = { - safe: userOperation.sender, - nonce: BigInt(userOperation.nonce), + return { + safe: this.userOperation.sender, + nonce: BigInt(this.userOperation.nonce), initCode, - callData: userOperation.callData, - callGasLimit: userOperation.callGasLimit, - verificationGasLimit: userOperation.verificationGasLimit, - preVerificationGas: userOperation.preVerificationGas, - maxFeePerGas: userOperation.maxFeePerGas, - maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas, + callData: this.userOperation.callData, + callGasLimit: this.userOperation.callGasLimit, + verificationGasLimit: this.userOperation.verificationGasLimit, + preVerificationGas: this.userOperation.preVerificationGas, + maxFeePerGas: this.userOperation.maxFeePerGas, + maxPriorityFeePerGas: this.userOperation.maxPriorityFeePerGas, paymasterAndData, - validAfter: validAfter || 0, - validUntil: validUntil || 0, - entryPoint - } - } - - getSignature(signer: string): SafeSignature | undefined { - return this.signatures.get(signer.toLowerCase()) - } - - addSignature(signature: SafeSignature): void { - this.signatures.set(signature.signer.toLowerCase(), signature) - } - - encodedSignatures(): string { - return buildSignatureBytes(Array.from(this.signatures.values())) - } - - addEstimations(estimations: EstimateGasData): void { - this.data.maxFeePerGas = BigInt(estimations.maxFeePerGas || this.data.maxFeePerGas) - this.data.maxPriorityFeePerGas = BigInt( - estimations.maxPriorityFeePerGas || this.data.maxPriorityFeePerGas - ) - this.data.verificationGasLimit = BigInt( - estimations.verificationGasLimit || this.data.verificationGasLimit - ) - this.data.preVerificationGas = BigInt( - estimations.preVerificationGas || this.data.preVerificationGas - ) - this.data.callGasLimit = BigInt(estimations.callGasLimit || this.data.callGasLimit) - - if (isEntryPointV7(this.entryPoint)) { - console.log('estimations', estimations) - const paymaster = estimations.paymaster || (this.userOperation as UserOperationV07).paymaster - const paymasterData = - estimations.paymasterData || (this.userOperation as UserOperationV07).paymasterData - - if (estimations.paymasterPostOpGasLimit || estimations.paymasterVerificationGasLimit) { - this.data['paymasterAndData'] = - 'paymaster' in estimations - ? concat([ - paymaster as Hex, - pad(toHex(estimations.paymasterVerificationGasLimit || 0n), { - size: 16 - }), - pad(toHex(estimations.paymasterPostOpGasLimit || 0n), { - size: 16 - }), - (paymasterData as Hex) || ('0x' as Hex) - ]) - : '0x' - console.log("this.data['paymasterAndData']", this.data['paymasterAndData']) - } - } else { - this.data['paymasterAndData'] = - estimations.paymasterAndData || (this.userOperation as UserOperationV06).paymasterAndData + validAfter: this.options.validAfter || 0, + validUntil: this.options.validUntil || 0, + entryPoint: this.options.entryPoint } } - toUserOperation(): UserOperation { - if (isEntryPointV6(this.entryPoint)) { - return { - sender: this.data.safe, - nonce: toHex(this.data.nonce), - initCode: this.data.initCode, - callData: this.data.callData, - callGasLimit: this.data.callGasLimit, - verificationGasLimit: this.data.verificationGasLimit, - preVerificationGas: this.data.preVerificationGas, - maxFeePerGas: this.data.maxFeePerGas, - maxPriorityFeePerGas: this.data.maxPriorityFeePerGas, - paymasterAndData: this.data.paymasterAndData, - signature: encodePacked( - ['uint48', 'uint48', 'bytes'], - [this.data.validAfter, this.data.validUntil, this.encodedSignatures() as Hex] - ) - } - } - - const factoryData = unpackInitCode(this.data.initCode) - const paymasterData = unpackPaymasterAndData(this.data.paymasterAndData) - + getUserOperation(): UserOperation { return { - sender: this.data.safe, - nonce: toHex(this.data.nonce), - ...factoryData, - callData: this.data.callData, - callGasLimit: this.data.callGasLimit, - verificationGasLimit: this.data.verificationGasLimit, - preVerificationGas: this.data.preVerificationGas, - maxFeePerGas: this.data.maxFeePerGas, - maxPriorityFeePerGas: this.data.maxPriorityFeePerGas, - ...paymasterData, + ...this.userOperation, signature: encodePacked( ['uint48', 'uint48', 'bytes'], - [this.data.validAfter, this.data.validUntil, this.encodedSignatures() as Hex] + [ + this.options.validAfter || 0, + this.options.validUntil || 0, + this.encodedSignatures() as Hex + ] ) } } getHash(): string { return calculateSafeUserOperationHash( - this.data, - this.chainId, - this.moduleAddress, - this.entryPoint + this.getSafeOperation(), + this.options.chainId, + this.options.moduleAddress, + this.options.entryPoint ) } } diff --git a/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts b/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts index b941458c2..52824992f 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts @@ -36,7 +36,7 @@ export async function getSafeNonceFromEntrypoint( protocolKit: Safe, safeAddress: string, entryPointAddress: string -): Promise { +): Promise { const safeProvider = protocolKit.getSafeProvider() const newNonce = await safeProvider.readContract({ @@ -46,5 +46,5 @@ export async function getSafeNonceFromEntrypoint( args: [safeAddress, 0n] }) - return newNonce.toString() + return newNonce } diff --git a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts index fad723e52..29f031c27 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts @@ -6,7 +6,7 @@ import { UserOperationV07 } from '@safe-global/types-kit' import { getSafeNonceFromEntrypoint, isEntryPointV6 } from './entrypoint' -import { encodeFunctionData, getAddress, Hex, hexToBytes, sliceHex } from 'viem' +import { encodeFunctionData, getAddress, Hex, hexToBytes, sliceHex, toHex } from 'viem' import { ABI } from '../constants' import { ERC20PaymasterOption, PaymasterOptions } from '../types' import { encodeMultiSendCallData } from '../utils' @@ -133,7 +133,7 @@ export async function createUserOperation( if (isEntryPointV6(entryPoint)) { return { sender: safeAddress, - nonce, + nonce: toHex(nonce), initCode, callData, callGasLimit: 1n, @@ -148,7 +148,7 @@ export async function createUserOperation( return { sender: safeAddress, - nonce, + nonce: toHex(nonce), ...unpackInitCode(initCode), callData, callGasLimit: 1n, diff --git a/packages/types-kit/src/types.ts b/packages/types-kit/src/types.ts index cc852d78b..8edbb439d 100644 --- a/packages/types-kit/src/types.ts +++ b/packages/types-kit/src/types.ts @@ -323,16 +323,25 @@ export type EstimateGasData = { paymasterPostOpGasLimit?: bigint } +export type SafeOperationOptions = { + moduleAddress: string + entryPoint: string + chainId: bigint + validAfter?: number + validUntil?: number +} + export interface SafeOperation { - readonly chainId: bigint - readonly moduleAddress: string - readonly data: SafeUserOperation + userOperation: UserOperation + options: SafeOperationOptions + readonly signatures: Map getSignature(signer: string): SafeSignature | undefined addSignature(signature: SafeSignature): void encodedSignatures(): string addEstimations(estimations: EstimateGasData): void - toUserOperation(): UserOperation + getUserOperation(): UserOperation + getSafeOperation(): SafeUserOperation getHash(): string } From feda7e8aefe56b2c68129c0b54185fc6e5c022b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Mon, 20 Jan 2025 10:20:52 +0100 Subject: [PATCH 06/55] Update playgrounds --- .../relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts | 4 ++-- playground/relay-kit/usdc-transfer-4337-sponsored.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts b/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts index abf437791..38d2024aa 100644 --- a/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts +++ b/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts @@ -14,11 +14,11 @@ const OWNER_ADDRESS = '' const POLICY_ID = '' // CHAIN -const CHAIN_NAME = 'sepolia' +const CHAIN_NAME = '11155111' // const CHAIN_NAME = 'gnosis' // RPC URL -const RPC_URL = 'https://rpc.sepolia.org' // SEPOLIA +const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // SEPOLIA // const RPC_URL = 'https://rpc.gnosischain.com/' // GNOSIS // Bundler URL diff --git a/playground/relay-kit/usdc-transfer-4337-sponsored.ts b/playground/relay-kit/usdc-transfer-4337-sponsored.ts index 82c1b6298..32687692e 100644 --- a/playground/relay-kit/usdc-transfer-4337-sponsored.ts +++ b/playground/relay-kit/usdc-transfer-4337-sponsored.ts @@ -14,11 +14,11 @@ const SAFE_ADDRESS = '' const POLICY_ID = '' // CHAIN -const CHAIN_NAME = 'sepolia' +const CHAIN_NAME = '11155111' // const CHAIN_NAME = 'gnosis' // RPC URL -const RPC_URL = 'https://rpc.sepolia.org' // SEPOLIA +const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // SEPOLIA // const RPC_URL = 'https://rpc.gnosischain.com/' // GNOSIS // Bundler URL From 77c99734cbbbbed5cb13c882a7f09ce0cdb66ac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Mon, 20 Jan 2025 12:27:44 +0100 Subject: [PATCH 07/55] Fix SigningMethod evaluated incorrectly --- packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index e5c592bcf..0430d0c27 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -617,12 +617,11 @@ export class Safe4337Pack extends RelayKitBasePack<{ } } else { if ( - signingMethod in [ SigningMethod.ETH_SIGN_TYPED_DATA_V4, SigningMethod.ETH_SIGN_TYPED_DATA_V3, SigningMethod.ETH_SIGN_TYPED_DATA - ] + ].includes(signingMethod) ) { signature = await signSafeOp( safeOp.getSafeOperation(), From 8eff3923e5f22edf9bb659e723057e22087c6c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Mon, 20 Jan 2025 12:28:01 +0100 Subject: [PATCH 08/55] Fix paymaster 0x0000... --- packages/relay-kit/src/packs/safe-4337/SafeOperation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts index 754a516dd..75cddfbfc 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts @@ -1,4 +1,4 @@ -import { Hex, concat, encodePacked, pad, toHex } from 'viem' +import { Hex, concat, encodePacked, isAddress, pad, toHex } from 'viem' import { EstimateGasData, SafeOperation, @@ -75,7 +75,7 @@ class EthSafeOperation implements SafeOperation { ? concat([userOpV07.factory as Hex, (userOpV07.factoryData as Hex) || ('0x' as Hex)]) : '0x' - paymasterAndData = userOpV07.paymaster + paymasterAndData = isAddress(userOpV07.paymaster || '') ? concat([ userOpV07.paymaster as Hex, pad(toHex(userOpV07.paymasterVerificationGasLimit || 0n), { From dbe373dee842b35b4ed3c3d15b0997b7492e23c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Wed, 22 Jan 2025 11:45:36 +0100 Subject: [PATCH 09/55] Add more RPC calls --- packages/relay-kit/src/packs/safe-4337/constants.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/relay-kit/src/packs/safe-4337/constants.ts b/packages/relay-kit/src/packs/safe-4337/constants.ts index c266897c4..cd128d27d 100644 --- a/packages/relay-kit/src/packs/safe-4337/constants.ts +++ b/packages/relay-kit/src/packs/safe-4337/constants.ts @@ -70,5 +70,7 @@ export enum RPC_4337_CALLS { GET_USER_OPERATION_RECEIPT = 'eth_getUserOperationReceipt', SUPPORTED_ENTRY_POINTS = 'eth_supportedEntryPoints', CHAIN_ID = 'eth_chainId', - SPONSOR_USER_OPERATION = 'pm_sponsorUserOperation' + SPONSOR_USER_OPERATION = 'pm_sponsorUserOperation', + GET_PAYMASTER_STUB_DATA = 'pm_getPaymasterStubData', + GET_PAYMASTER_DATA = 'pm_getPaymasterData' } From 9fcea54b109699621ef9874ac8938358de3a0c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Wed, 22 Jan 2025 17:11:38 +0100 Subject: [PATCH 10/55] Update to work with new Pimlico paymasters --- packages/relay-kit/package.json | 2 +- .../src/packs/safe-4337/Safe4337Pack.ts | 27 ++-- .../src/packs/safe-4337/SafeOperation.ts | 8 +- .../estimators/PimlicoFeeEstimator.ts | 77 +++++++--- .../relay-kit/src/packs/safe-4337/types.ts | 85 +++++++---- packages/types-kit/src/types.ts | 14 +- yarn.lock | 135 ++++++++++++++++-- 7 files changed, 268 insertions(+), 80 deletions(-) diff --git a/packages/relay-kit/package.json b/packages/relay-kit/package.json index 51313a3e6..3074369ac 100644 --- a/packages/relay-kit/package.json +++ b/packages/relay-kit/package.json @@ -42,6 +42,6 @@ "@safe-global/protocol-kit": "^5.1.1", "@safe-global/safe-modules-deployments": "^2.2.4", "@safe-global/types-kit": "^1.0.1", - "viem": "^2.21.8" + "viem": "^2.22.11" } } diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index 0430d0c27..fc4e3b563 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -409,9 +409,12 @@ export class Safe4337Pack extends RelayKitBasePack<{ const setupEstimationData = await feeEstimator?.setupEstimation?.({ bundlerUrl: this.#BUNDLER_URL, entryPoint: this.#ENTRYPOINT_ADDRESS, - userOperation: safeOperation.getUserOperation() + userOperation: safeOperation.getUserOperation(), + paymasterOptions: this.#paymasterOptions }) + console.log('1.setupEstimationData', setupEstimationData) + if (setupEstimationData) { safeOperation.addEstimations(setupEstimationData) } @@ -431,12 +434,10 @@ export class Safe4337Pack extends RelayKitBasePack<{ ] }) + console.log('2.estimateUserOperationGas', estimateUserOperationGas) + if (estimateUserOperationGas) { - safeOperation.addEstimations({ - preVerificationGas: BigInt(estimateUserOperationGas.preVerificationGas), - verificationGasLimit: BigInt(estimateUserOperationGas.verificationGasLimit), - callGasLimit: BigInt(estimateUserOperationGas.callGasLimit) - }) + safeOperation.addEstimations(estimateUserOperationGas) } const adjustEstimationData = await feeEstimator?.adjustEstimation?.({ @@ -445,26 +446,26 @@ export class Safe4337Pack extends RelayKitBasePack<{ userOperation: safeOperation.getUserOperation() }) + console.log('3.adjustEstimationData', adjustEstimationData) + if (adjustEstimationData) { safeOperation.addEstimations(adjustEstimationData) } - if (this.#paymasterOptions?.isSponsored) { - if (!this.#paymasterOptions.paymasterUrl) { - throw new Error('No paymaster url provided for a sponsored transaction') - } - + if (this.#paymasterOptions) { const paymasterEstimation = await feeEstimator?.getPaymasterEstimation?.({ userOperation: addDummySignature( safeOperation.getUserOperation(), this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, threshold ), - paymasterUrl: this.#paymasterOptions.paymasterUrl, + bundlerUrl: this.#BUNDLER_URL, entryPoint: this.#ENTRYPOINT_ADDRESS, - sponsorshipPolicyId: this.#paymasterOptions.sponsorshipPolicyId + paymasterOptions: this.#paymasterOptions }) + console.log('4.paymasterEstimation', paymasterEstimation) + if (paymasterEstimation) { safeOperation.addEstimations(paymasterEstimation) } diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts index 75cddfbfc..15cfabd68 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts @@ -54,11 +54,11 @@ class EthSafeOperation implements SafeOperation { if (isEntryPointV7(this.options.entryPoint)) { const userOp = this.userOperation as UserOperationV07 userOp.paymasterPostOpGasLimit = estimations.paymasterPostOpGasLimit - ? BigInt(estimations.paymasterPostOpGasLimit) || userOp.paymasterPostOpGasLimit - : undefined + ? BigInt(estimations.paymasterPostOpGasLimit) + : userOp.paymasterPostOpGasLimit userOp.paymasterVerificationGasLimit = estimations.paymasterVerificationGasLimit - ? BigInt(estimations.paymasterVerificationGasLimit) || userOp.paymasterVerificationGasLimit - : undefined + ? BigInt(estimations.paymasterVerificationGasLimit) + : userOp.paymasterVerificationGasLimit userOp.paymaster = estimations.paymaster || userOp.paymaster userOp.paymasterData = estimations.paymasterData || userOp.paymasterData } diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.ts b/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.ts index 16bde6f46..be86c8c07 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.ts @@ -1,21 +1,45 @@ import { EstimateGasData } from '@safe-global/types-kit' import { BundlerClient, + ERC20PaymasterOption, EstimateFeeFunctionProps, - EstimateSponsoredFeeFunctionProps, - EstimateSponsoredGasData, - IFeeEstimator + IFeeEstimator, + SponsoredPaymasterOption } from '../types' import { getEip4337BundlerProvider, userOperationToHexValues } from '../utils' import { RPC_4337_CALLS } from '../constants' export class PimlicoFeeEstimator implements IFeeEstimator { - async setupEstimation({ bundlerUrl }: EstimateFeeFunctionProps): Promise { + async setupEstimation({ + bundlerUrl, + userOperation, + entryPoint, + paymasterOptions + }: EstimateFeeFunctionProps): Promise { const bundlerClient = getEip4337BundlerProvider(bundlerUrl) - const feeData = await this.#getFeeData(bundlerClient) + const chainId = await bundlerClient.request({ method: 'eth_chainId' }) - return feeData + let paymasterStubData = {} + if (paymasterOptions) { + const paymasterClient = getEip4337BundlerProvider(paymasterOptions.paymasterUrl) + const context = (paymasterOptions as ERC20PaymasterOption).paymasterTokenAddress + ? { + token: (paymasterOptions as ERC20PaymasterOption).paymasterTokenAddress + } + : {} + paymasterStubData = await paymasterClient.request({ + method: RPC_4337_CALLS.GET_PAYMASTER_STUB_DATA, + params: (paymasterOptions as SponsoredPaymasterOption).sponsorshipPolicyId + ? [userOperationToHexValues(userOperation, entryPoint), entryPoint, chainId, context] + : [userOperationToHexValues(userOperation, entryPoint), entryPoint, chainId, context] + }) + } + + return { + ...feeData, + ...paymasterStubData + } } async adjustEstimation({ userOperation }: EstimateFeeFunctionProps): Promise { @@ -28,20 +52,33 @@ export class PimlicoFeeEstimator implements IFeeEstimator { async getPaymasterEstimation({ userOperation, - paymasterUrl, entryPoint, - sponsorshipPolicyId - }: EstimateSponsoredFeeFunctionProps): Promise { - const paymasterClient = getEip4337BundlerProvider(paymasterUrl) + paymasterOptions + }: EstimateFeeFunctionProps): Promise { + if (!paymasterOptions) throw new Error("Paymaster options can't be empty") - const gasEstimate = await paymasterClient.request({ - method: RPC_4337_CALLS.SPONSOR_USER_OPERATION, - params: sponsorshipPolicyId - ? [userOperationToHexValues(userOperation, entryPoint), entryPoint, { sponsorshipPolicyId }] - : [userOperationToHexValues(userOperation, entryPoint), entryPoint] - }) + const paymasterClient = getEip4337BundlerProvider(paymasterOptions.paymasterUrl) + + let gasEstimate: EstimateGasData + + if (paymasterOptions?.isSponsored) { + gasEstimate = await paymasterClient.request({ + method: RPC_4337_CALLS.SPONSOR_USER_OPERATION, + params: [userOperationToHexValues(userOperation, entryPoint), entryPoint] + }) + } else { + const chainId = await paymasterClient.request({ method: 'eth_chainId' }) + gasEstimate = await paymasterClient.request({ + method: RPC_4337_CALLS.GET_PAYMASTER_DATA, + params: [ + userOperationToHexValues(userOperation, entryPoint), + entryPoint, + chainId, + { token: (paymasterOptions as ERC20PaymasterOption).paymasterTokenAddress } + ] + }) + } - console.log('getPaymasterEstimation', gasEstimate) return gasEstimate } @@ -52,15 +89,13 @@ export class PimlicoFeeEstimator implements IFeeEstimator { method: 'pimlico_getUserOperationGasPrice' }) - console.log('getFeeData', feeData) - const { fast: { maxFeePerGas, maxPriorityFeePerGas } } = feeData return { - maxFeePerGas: BigInt(maxFeePerGas), - maxPriorityFeePerGas: BigInt(maxPriorityFeePerGas) + maxFeePerGas: maxFeePerGas, + maxPriorityFeePerGas: maxPriorityFeePerGas } } } diff --git a/packages/relay-kit/src/packs/safe-4337/types.ts b/packages/relay-kit/src/packs/safe-4337/types.ts index 773fcdd65..cff8a8f8c 100644 --- a/packages/relay-kit/src/packs/safe-4337/types.ts +++ b/packages/relay-kit/src/packs/safe-4337/types.ts @@ -33,6 +33,7 @@ export type SponsoredPaymasterOption = { } export type ERC20PaymasterOption = { + paymasterUrl: string isSponsored?: false paymasterAddress: string paymasterTokenAddress: string @@ -141,31 +142,20 @@ export type EstimateFeeFunctionProps = { userOperation: UserOperation bundlerUrl: string entryPoint: string + paymasterOptions?: PaymasterOptions } export type EstimateFeeFunction = ({ userOperation, bundlerUrl, - entryPoint + entryPoint, + paymasterOptions }: EstimateFeeFunctionProps) => Promise -export type EstimateSponsoredFeeFunctionProps = { - userOperation: UserOperation - paymasterUrl: string - entryPoint: string - sponsorshipPolicyId?: string -} - -export type EstimateSponsoredFeeFunction = ({ - userOperation, - paymasterUrl, - entryPoint -}: EstimateSponsoredFeeFunctionProps) => Promise - export interface IFeeEstimator { setupEstimation?: EstimateFeeFunction adjustEstimation?: EstimateFeeFunction - getPaymasterEstimation?: EstimateSponsoredFeeFunction + getPaymasterEstimation?: EstimateFeeFunction } export type EstimateFeeProps = { @@ -199,14 +189,55 @@ export type PimlicoCustomRpcSchema = [ } }, { - Method: 'pm_sponsorUserOperation' - Parameters: [UserOperationStringValues, string, { sponsorshipPolicyId?: string }?] - ReturnType: { - paymasterAndData: string - callGasLimit: string - verificationGasLimit: string - preVerificationGas: string - } + Method: RPC_4337_CALLS.SPONSOR_USER_OPERATION + Parameters: [UserOperationStringValues, string, { token?: string }?] + ReturnType: + | { + paymasterAndData: string + callGasLimit: string + verificationGasLimit: string + verificationGas: string + preVerificationGas: string + } + | { + paymaster: string + paymasterData: string + callGasLimit: string + verificationGasLimit: string + verificationGas: string + preVerificationGas: string + paymasterVerificationGasLimit: string + paymasterPostOpGasLimit: string + } + }, + { + Method: RPC_4337_CALLS.GET_PAYMASTER_STUB_DATA + Parameters: [UserOperationStringValues, string, string, { token?: string }?] + ReturnType: + | { + paymasterAndData: string + } + | { + paymaster: string + paymasterData: string + paymasterVerificationGasLimit?: string + paymasterPostOpGasLimit?: string + } + }, + { + Method: RPC_4337_CALLS.GET_PAYMASTER_DATA + Parameters: [UserOperationStringValues, string, string, { token?: string }?] + ReturnType: + | { + paymasterAndData: string + preVerificationGas: string + verificationGasLimit: string + callGasLimit: string + } + | { + paymaster: string + paymasterData: string + } }, { Method: RPC_4337_CALLS.SUPPORTED_ENTRY_POINTS @@ -216,7 +247,13 @@ export type PimlicoCustomRpcSchema = [ { Method: RPC_4337_CALLS.ESTIMATE_USER_OPERATION_GAS Parameters: [UserOperationStringValues, string] - ReturnType: { callGasLimit: string; verificationGasLimit: string; preVerificationGas: string } + ReturnType: { + callGasLimit: string + verificationGasLimit: string + preVerificationGas: string + paymasterPostOpGasLimit?: string + paymasterVerificationGasLimit?: string + } }, { Method: RPC_4337_CALLS.SEND_USER_OPERATION diff --git a/packages/types-kit/src/types.ts b/packages/types-kit/src/types.ts index 8edbb439d..349d8da34 100644 --- a/packages/types-kit/src/types.ts +++ b/packages/types-kit/src/types.ts @@ -314,13 +314,13 @@ export type EstimateGasData = { paymasterAndData?: string paymaster?: string paymasterData?: string - maxFeePerGas?: bigint - maxPriorityFeePerGas?: bigint - preVerificationGas?: bigint - verificationGasLimit?: bigint - callGasLimit?: bigint - paymasterVerificationGasLimit?: bigint - paymasterPostOpGasLimit?: bigint + maxFeePerGas?: bigint | number | string + maxPriorityFeePerGas?: bigint | number | string + preVerificationGas?: bigint | number | string + verificationGasLimit?: bigint | number | string + callGasLimit?: bigint | number | string + paymasterVerificationGasLimit?: bigint | number | string + paymasterPostOpGasLimit?: bigint | number | string } export type SafeOperationOptions = { diff --git a/yarn.lock b/yarn.lock index fd534b609..bd9e64663 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,6 +22,11 @@ resolved "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.9.2.tgz" integrity sha512-0h+FrQDqe2Wn+IIGFkTCd4aAwTJ+7834Ek1COohCyV26AXhwQ7WQaz+4F/nLOeVl/3BtWHOHLPsq46V8YB46Eg== +"@adraffy/ens-normalize@^1.10.1": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" + integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== + "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz" @@ -1129,6 +1134,13 @@ dependencies: "@noble/hashes" "1.4.0" +"@noble/curves@1.7.0", "@noble/curves@~1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.7.0.tgz#0512360622439256df892f21d25b388f52505e45" + integrity sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw== + dependencies: + "@noble/hashes" "1.6.0" + "@noble/curves@^1.4.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.5.0.tgz#7a9b9b507065d516e6dce275a1e31db8d2a100dd" @@ -1150,6 +1162,13 @@ dependencies: "@noble/hashes" "1.4.0" +"@noble/curves@~1.8.1": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.8.1.tgz#19bc3970e205c99e4bdb1c64a4785706bce497ff" + integrity sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ== + dependencies: + "@noble/hashes" "1.7.1" + "@noble/hashes@1.1.2": version "1.1.2" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz" @@ -1180,6 +1199,21 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== +"@noble/hashes@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.0.tgz#d4bfb516ad6e7b5111c216a5cc7075f4cf19e6c5" + integrity sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ== + +"@noble/hashes@1.6.1", "@noble/hashes@~1.6.0": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.1.tgz#df6e5943edcea504bac61395926d6fd67869a0d5" + integrity sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w== + +"@noble/hashes@1.7.1", "@noble/hashes@^1.5.0", "@noble/hashes@~1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.7.1.tgz#5738f6d765710921e7a751e00c20ae091ed8db0f" + integrity sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ== + "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1": version "1.3.3" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" @@ -1776,6 +1810,11 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.8.tgz#8f23646c352f020c83bca750a82789e246d42b50" integrity sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg== +"@scure/base@~1.2.1", "@scure/base@~1.2.2", "@scure/base@~1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.4.tgz#002eb571a35d69bdb4c214d0995dff76a8dcd2a9" + integrity sha512-5Yy9czTO47mqz+/J8GM6GIId4umdCk1wc1q8rKERQulIoc8VP9pzDcghv10Tl2E7R96ZUx/PhND3ESYUQX8NuQ== + "@scure/bip32@1.1.5": version "1.1.5" resolved "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz" @@ -1803,6 +1842,24 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" +"@scure/bip32@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.6.0.tgz#6dbc6b4af7c9101b351f41231a879d8da47e0891" + integrity sha512-82q1QfklrUUdXJzjuRU7iG7D7XiFx5PHYVS0+oeNKhyDLT7WPqs6pBcM2W5ZdwOwKCwoE1Vy1se+DHjcXwCYnA== + dependencies: + "@noble/curves" "~1.7.0" + "@noble/hashes" "~1.6.0" + "@scure/base" "~1.2.1" + +"@scure/bip32@^1.5.0": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.6.2.tgz#093caa94961619927659ed0e711a6e4bf35bffd0" + integrity sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw== + dependencies: + "@noble/curves" "~1.8.1" + "@noble/hashes" "~1.7.1" + "@scure/base" "~1.2.2" + "@scure/bip39@1.1.1": version "1.1.1" resolved "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz" @@ -1827,6 +1884,22 @@ "@noble/hashes" "~1.5.0" "@scure/base" "~1.1.8" +"@scure/bip39@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.5.0.tgz#c8f9533dbd787641b047984356531d84485f19be" + integrity sha512-Dop+ASYhnrwm9+HA/HwXg7j2ZqM6yk2fyLWb5znexjctFY3+E+eU8cIWI0Pql0Qx4hPZCijlGq4OL71g+Uz30A== + dependencies: + "@noble/hashes" "~1.6.0" + "@scure/base" "~1.2.1" + +"@scure/bip39@^1.4.0": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.5.4.tgz#07fd920423aa671be4540d59bdd344cc1461db51" + integrity sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA== + dependencies: + "@noble/hashes" "~1.7.1" + "@scure/base" "~1.2.4" + "@sentry/core@5.30.0": version "5.30.0" resolved "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz" @@ -2356,11 +2429,21 @@ abitype@1.0.5, abitype@^1.0.2: resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.5.tgz#29d0daa3eea867ca90f7e4123144c1d1270774b6" integrity sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw== +abitype@1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.7.tgz#876a0005d211e1c9132825d45bcee7b46416b284" + integrity sha512-ZfYYSktDQUwc2eduYu8C4wOs+RDPmnRYMh7zNfzeMtGGgb0U+6tLGjixUic6mXf5xKKCcgT5Qp6cv39tOARVFw== + abitype@^0.9.8: version "0.9.10" resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.9.10.tgz#fa6fa30a6465da98736f98b6c601a02ed49f6eec" integrity sha512-FIS7U4n7qwAT58KibwYig5iFG4K61rbhAqaQh/UWj8v1Y8mjX3F8TC9gd8cz9yT1TYel9f8nS5NO5kZp2RW0jQ== +abitype@^1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.8.tgz#3554f28b2e9d6e9f35eb59878193eabd1b9f46ba" + integrity sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg== + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" @@ -3992,16 +4075,16 @@ ethjs-util@0.1.6, ethjs-util@^0.1.6: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" +eventemitter3@5.0.1, eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + eventemitter3@^4.0.4: version "4.0.7" resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz" @@ -5183,6 +5266,11 @@ isows@1.0.4: resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.4.tgz#810cd0d90cc4995c26395d2aa4cfa4037ebdf061" integrity sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ== +isows@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7" + integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" @@ -6848,6 +6936,19 @@ os-tmpdir@~1.0.2: resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== +ox@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/ox/-/ox-0.6.5.tgz#e6506a589bd6af9b5fecfcb2c641b63c9882edb6" + integrity sha512-vmnH8KvMDwFZDbNY1mq2CBRBWIgSliZB/dFV0xKp+DfF/dJkTENt6nmA+DzHSSAgL/GO2ydjkXWvlndJgSY4KQ== + dependencies: + "@adraffy/ens-normalize" "^1.10.1" + "@noble/curves" "^1.6.0" + "@noble/hashes" "^1.5.0" + "@scure/bip32" "^1.5.0" + "@scure/bip39" "^1.4.0" + abitype "^1.0.6" + eventemitter3 "5.0.1" + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" @@ -8489,6 +8590,20 @@ viem@^2.21.8: webauthn-p256 "0.0.5" ws "8.17.1" +viem@^2.22.11: + version "2.22.11" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.22.11.tgz#e8e937980e11688a79559419882347754bb0d009" + integrity sha512-r86JkRcE8GVTRKBZADkT01EbmIAkjqJE3xcgeIk1AznKYE/KQfuaki8vZwaOoqQd5jqVZ7m5kGtFFsRe6LEWrg== + dependencies: + "@noble/curves" "1.7.0" + "@noble/hashes" "1.6.1" + "@scure/bip32" "1.6.0" + "@scure/bip39" "1.5.0" + abitype "1.0.7" + isows "1.0.6" + ox "0.6.5" + ws "8.18.0" + walk-up-path@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-3.0.1.tgz#c8d78d5375b4966c717eb17ada73dbd41490e886" @@ -8927,6 +9042,11 @@ ws@8.17.1: resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== +ws@8.18.0, ws@^8.17.1, ws@^8.5.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + ws@8.5.0: version "8.5.0" resolved "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz" @@ -8937,11 +9057,6 @@ ws@^7.4.6: resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -ws@^8.17.1, ws@^8.5.0: - version "8.18.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" - integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== - xtend@~4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" From 5eff1699b879733dbe5d7e209b8610fd030ee295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Thu, 23 Jan 2025 12:00:04 +0100 Subject: [PATCH 11/55] Improvements --- .../estimators/PimlicoFeeEstimator.ts | 34 ++++++++++++------- .../relay-kit/src/packs/safe-4337/types.ts | 14 ++++---- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.ts b/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.ts index be86c8c07..ede01e8d7 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.ts @@ -4,7 +4,8 @@ import { ERC20PaymasterOption, EstimateFeeFunctionProps, IFeeEstimator, - SponsoredPaymasterOption + SponsoredPaymasterOption, + UserOperationStringValues } from '../types' import { getEip4337BundlerProvider, userOperationToHexValues } from '../utils' import { RPC_4337_CALLS } from '../constants' @@ -27,7 +28,7 @@ export class PimlicoFeeEstimator implements IFeeEstimator { ? { token: (paymasterOptions as ERC20PaymasterOption).paymasterTokenAddress } - : {} + : undefined paymasterStubData = await paymasterClient.request({ method: RPC_4337_CALLS.GET_PAYMASTER_STUB_DATA, params: (paymasterOptions as SponsoredPaymasterOption).sponsorshipPolicyId @@ -42,13 +43,13 @@ export class PimlicoFeeEstimator implements IFeeEstimator { } } - async adjustEstimation({ userOperation }: EstimateFeeFunctionProps): Promise { - return { - callGasLimit: userOperation.callGasLimit + userOperation.callGasLimit / 2n, // +50% - verificationGasLimit: userOperation.verificationGasLimit * 4n, // +300% - preVerificationGas: userOperation.preVerificationGas + userOperation.preVerificationGas / 20n // +5% - } - } + // async adjustEstimation({ userOperation }: EstimateFeeFunctionProps): Promise { + // return { + // callGasLimit: userOperation.callGasLimit + userOperation.callGasLimit / 2n, // +50% + // verificationGasLimit: userOperation.verificationGasLimit * 4n, // +300% + // preVerificationGas: userOperation.preVerificationGas + userOperation.preVerificationGas / 20n // +5% + // } + // } async getPaymasterEstimation({ userOperation, @@ -61,10 +62,19 @@ export class PimlicoFeeEstimator implements IFeeEstimator { let gasEstimate: EstimateGasData - if (paymasterOptions?.isSponsored) { + if (paymasterOptions.isSponsored) { + const params: [UserOperationStringValues, string, { sponsorshipPolicyId: string }?] = [ + userOperationToHexValues(userOperation, entryPoint), + entryPoint + ] + if (paymasterOptions.sponsorshipPolicyId) { + params.push({ + sponsorshipPolicyId: paymasterOptions.sponsorshipPolicyId + }) + } gasEstimate = await paymasterClient.request({ method: RPC_4337_CALLS.SPONSOR_USER_OPERATION, - params: [userOperationToHexValues(userOperation, entryPoint), entryPoint] + params }) } else { const chainId = await paymasterClient.request({ method: 'eth_chainId' }) @@ -74,7 +84,7 @@ export class PimlicoFeeEstimator implements IFeeEstimator { userOperationToHexValues(userOperation, entryPoint), entryPoint, chainId, - { token: (paymasterOptions as ERC20PaymasterOption).paymasterTokenAddress } + { token: paymasterOptions.paymasterTokenAddress } ] }) } diff --git a/packages/relay-kit/src/packs/safe-4337/types.ts b/packages/relay-kit/src/packs/safe-4337/types.ts index cff8a8f8c..96e9ec899 100644 --- a/packages/relay-kit/src/packs/safe-4337/types.ts +++ b/packages/relay-kit/src/packs/safe-4337/types.ts @@ -28,19 +28,19 @@ type PredictedSafeOptions = { export type SponsoredPaymasterOption = { isSponsored: true - paymasterUrl: string sponsorshipPolicyId?: string } export type ERC20PaymasterOption = { - paymasterUrl: string isSponsored?: false paymasterAddress: string paymasterTokenAddress: string amountToApprove?: bigint } -export type PaymasterOptions = SponsoredPaymasterOption | ERC20PaymasterOption | undefined +export type PaymasterOptions = + | ({ paymasterUrl: string } & (SponsoredPaymasterOption | ERC20PaymasterOption)) + | undefined export type Safe4337InitOptions = { provider: SafeProviderConfig['provider'] @@ -163,7 +163,7 @@ export type EstimateFeeProps = { feeEstimator?: IFeeEstimator } -type UserOperationStringValues = Omit< +export type UserOperationStringValues = Omit< UserOperation, | 'callGasLimit' | 'verificationGasLimit' @@ -190,7 +190,7 @@ export type PimlicoCustomRpcSchema = [ }, { Method: RPC_4337_CALLS.SPONSOR_USER_OPERATION - Parameters: [UserOperationStringValues, string, { token?: string }?] + Parameters: [UserOperationStringValues, string, { sponsorshipPolicyId: string }?] ReturnType: | { paymasterAndData: string @@ -212,7 +212,7 @@ export type PimlicoCustomRpcSchema = [ }, { Method: RPC_4337_CALLS.GET_PAYMASTER_STUB_DATA - Parameters: [UserOperationStringValues, string, string, { token?: string }?] + Parameters: [UserOperationStringValues, string, string, { token: string }?] ReturnType: | { paymasterAndData: string @@ -226,7 +226,7 @@ export type PimlicoCustomRpcSchema = [ }, { Method: RPC_4337_CALLS.GET_PAYMASTER_DATA - Parameters: [UserOperationStringValues, string, string, { token?: string }?] + Parameters: [UserOperationStringValues, string, string, { token: string }?] ReturnType: | { paymasterAndData: string From 19b63c7898cf3c5827c4deb18e67dd96404a9ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Thu, 23 Jan 2025 18:05:41 +0100 Subject: [PATCH 12/55] Update playgrounds --- playground/config/run.ts | 13 +- .../usdc-transfer-4337-counterfactual.ts | 120 ------------------ ...usdc-transfer-4337-erc20-counterfactual.ts | 108 ---------------- .../relay-kit/usdc-transfer-4337-erc20.ts | 102 --------------- ...-transfer-4337-sponsored-counterfactual.ts | 110 ---------------- playground/relay-kit/userop-counterfactual.ts | 69 ++++++++++ .../userop-erc20-paymaster-counterfactual.ts | 77 +++++++++++ .../relay-kit/userop-erc20-paymaster.ts | 75 +++++++++++ ...erop-verifying-paymaster-counterfactual.ts | 77 +++++++++++ ...sored.ts => userop-verifying-paymaster.ts} | 34 ++--- .../{usdc-transfer-4337.ts => userop.ts} | 43 +++---- playground/utils.ts | 81 +++++++++++- 12 files changed, 408 insertions(+), 501 deletions(-) delete mode 100644 playground/relay-kit/usdc-transfer-4337-counterfactual.ts delete mode 100644 playground/relay-kit/usdc-transfer-4337-erc20-counterfactual.ts delete mode 100644 playground/relay-kit/usdc-transfer-4337-erc20.ts delete mode 100644 playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts create mode 100644 playground/relay-kit/userop-counterfactual.ts create mode 100644 playground/relay-kit/userop-erc20-paymaster-counterfactual.ts create mode 100644 playground/relay-kit/userop-erc20-paymaster.ts create mode 100644 playground/relay-kit/userop-verifying-paymaster-counterfactual.ts rename playground/relay-kit/{usdc-transfer-4337-sponsored.ts => userop-verifying-paymaster.ts} (59%) rename playground/relay-kit/{usdc-transfer-4337.ts => userop.ts} (51%) diff --git a/playground/config/run.ts b/playground/config/run.ts index 6f7e6148a..1fe6dca49 100644 --- a/playground/config/run.ts +++ b/playground/config/run.ts @@ -20,13 +20,12 @@ const playgroundRelayKitPaths = { 'api-kit-interoperability': 'relay-kit/api-kit-interoperability', 'relay-paid-transaction': 'relay-kit/paid-transaction', 'relay-sponsored-transaction': 'relay-kit/sponsored-transaction', - 'usdc-transfer-4337': 'relay-kit/usdc-transfer-4337', - 'usdc-transfer-4337-erc20': 'relay-kit/usdc-transfer-4337-erc20', - 'usdc-transfer-4337-sponsored': 'relay-kit/usdc-transfer-4337-sponsored', - 'usdc-transfer-4337-counterfactual': 'relay-kit/usdc-transfer-4337-counterfactual', - 'usdc-transfer-4337-erc20-counterfactual': 'relay-kit/usdc-transfer-4337-erc20-counterfactual', - 'usdc-transfer-4337-sponsored-counterfactual': - 'relay-kit/usdc-transfer-4337-sponsored-counterfactual' + userop: 'relay-kit/userop', + 'userop-counterfactual': 'relay-kit/userop-counterfactual', + 'userop-erc20-paymaster': 'relay-kit/userop-erc20-paymaster', + 'userop-erc20-paymaster-counterfactual': 'relay-kit/userop-erc20-paymaster-counterfactual', + 'userop-verifying-paymaster': 'relay-kit/userop-verifying-paymaster', + 'userop-verifying-paymaster-counterfactual': 'relay-kit/userop-verifying-paymaster-counterfactual' } const playgroundStarterKitPaths = { diff --git a/playground/relay-kit/usdc-transfer-4337-counterfactual.ts b/playground/relay-kit/usdc-transfer-4337-counterfactual.ts deleted file mode 100644 index 28bcd026f..000000000 --- a/playground/relay-kit/usdc-transfer-4337-counterfactual.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { parseEther } from 'viem' -import { getBlock, waitForTransactionReceipt } from 'viem/actions' -import { sepolia } from 'viem/chains' -import { Safe4337Pack } from '@safe-global/relay-kit' -import { waitForOperationToFinish, transfer, generateTransferCallData } from '../utils' - -// Safe owner PK -const PRIVATE_KEY = '' - -const PIMLICO_API_KEY = '' - -// Safe owner address -const OWNER_ADDRESS = '' - -// RPC URL -const RPC_URL = 'https://rpc.sepolia.org' // SEPOLIA -// const RPC_URL = 'https://rpc.gnosischain.com/' // GNOSIS - -// CHAIN -const CHAIN_NAME = 'sepolia' -// const CHAIN_NAME = 'gnosis' - -// Bundler URL -const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO - -// USDC CONTRACT ADDRESS IN SEPOLIA -// faucet: https://faucet.circle.com/ -const usdcTokenAddress = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // SEPOLIA -// const usdcTokenAddress = '0xddafbb505ad214d7b80b1f830fccc89b60fb7a83' // GNOSIS - -async function main() { - // 1) Initialize pack - const safe4337Pack = await Safe4337Pack.init({ - provider: RPC_URL, - signer: PRIVATE_KEY, - bundlerUrl: BUNDLER_URL, - options: { - owners: [OWNER_ADDRESS], - threshold: 1, - saltNonce: '4337' + '1' // to update the address - } - }) - - // Log supported entry points and chain id - console.log('Supported Entry Points', await safe4337Pack.getSupportedEntryPoints()) - console.log('Chain Id', await safe4337Pack.getChainId()) - - const senderAddress = await safe4337Pack.protocolKit.getAddress() - - console.log('senderAddress: ', senderAddress) - - console.log('is Safe Account deployed: ', await safe4337Pack.protocolKit.isSafeDeployed()) - - // funding the Safe with USDC and ETH - - const nativeTokenAmount = '0.5' - - const fundingSafe = { - to: senderAddress, - value: parseEther(nativeTokenAmount), - chain: sepolia - } - - console.log(`sending ${nativeTokenAmount} ETH...`) - - const externalSigner = await safe4337Pack.protocolKit.getSafeProvider().getExternalSigner() - const signerAddress = await safe4337Pack.protocolKit.getSafeProvider().getSignerAddress() - const externalProvider = safe4337Pack.protocolKit.getSafeProvider().getExternalProvider() - - if (!externalSigner || !signerAddress) { - throw new Error('No signer found!') - } - - const hash = await externalSigner?.sendTransaction(fundingSafe) - - await waitForTransactionReceipt(externalProvider, { hash }) - - // Create transaction batch with two 0.1 USDC transfers - - const usdcAmount = 100_000n // 0.1 USDC - - console.log(`sending USDC...`) - - // send 0.2 USDC to the Safe - await transfer(externalSigner, usdcTokenAddress, senderAddress, usdcAmount * 2n) - - console.log(`creating the Safe batch...`) - - const transferUSDC = { - to: usdcTokenAddress, - data: generateTransferCallData(signerAddress, usdcAmount), - value: '0' - } - - const transactions = [transferUSDC, transferUSDC] - const timestamp = (await getBlock(externalProvider))?.timestamp || 0n - - // 2) Create transaction batch - const safeOperation = await safe4337Pack.createTransaction({ - transactions, - options: { - validAfter: Number(timestamp - 60_000n), - validUntil: Number(timestamp + 60_000n) - } - }) - - // 3) Sign SafeOperation - const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) - - console.log('SafeOperation', signedSafeOperation) - - // 4) Execute SafeOperation - const userOperationHash = await safe4337Pack.executeTransaction({ - executable: signedSafeOperation - }) - - await waitForOperationToFinish(userOperationHash, CHAIN_NAME, safe4337Pack) -} - -main() diff --git a/playground/relay-kit/usdc-transfer-4337-erc20-counterfactual.ts b/playground/relay-kit/usdc-transfer-4337-erc20-counterfactual.ts deleted file mode 100644 index 974f4eff8..000000000 --- a/playground/relay-kit/usdc-transfer-4337-erc20-counterfactual.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { getBlock } from 'viem/actions' -import { Safe4337Pack } from '@safe-global/relay-kit' -import { waitForOperationToFinish, transfer, generateTransferCallData } from '../utils' - -// Safe owner PK -const PRIVATE_KEY = '' - -const PIMLICO_API_KEY = '' - -// Safe owner address -const OWNER_ADDRESS = '' - -// CHAIN -const CHAIN_NAME = 'sepolia' -// const CHAIN_NAME = 'gnosis' - -// RPC URL -const RPC_URL = 'https://rpc.sepolia.org' // SEPOLIA -// const RPC_URL = 'https://rpc.gnosischain.com/' // GNOSIS - -// Bundler URL -const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO - -// PAYMASTER ADDRESS -const paymasterAddress = '0x0000000000325602a77416A16136FDafd04b299f' // SEPOLIA -// const paymasterAddress = '0x000000000034B78bfe02Be30AE4D324c8702803d' // GNOSIS - -// USDC CONTRACT ADDRESS IN SEPOLIA -// faucet: https://faucet.circle.com/ -const usdcTokenAddress = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // SEPOLIA -// const usdcTokenAddress = '0xddafbb505ad214d7b80b1f830fccc89b60fb7a83' // GNOSIS - -async function main() { - // 1) Initialize pack with the paymaster data - const safe4337Pack = await Safe4337Pack.init({ - provider: RPC_URL, - signer: PRIVATE_KEY, - bundlerUrl: BUNDLER_URL, - paymasterOptions: { - paymasterTokenAddress: usdcTokenAddress, - paymasterAddress - // amountToApprove?: bigint // optional value to set the paymaster approve amount on the deployment - }, - options: { - owners: [OWNER_ADDRESS], - threshold: 1, - saltNonce: '4337' + '1' // to update the address - } - }) - - // Log supported entry points and chain id - console.log('Supported Entry Points', await safe4337Pack.getSupportedEntryPoints()) - console.log('Chain Id', await safe4337Pack.getChainId()) - - // Create transaction batch with two 0.1 USDC transfers - const senderAddress = await safe4337Pack.protocolKit.getAddress() - - console.log('senderAddress: ', senderAddress) - - console.log('is Safe Account deployed: ', await safe4337Pack.protocolKit.isSafeDeployed()) - - const usdcAmount = 100_000n // 0.1 USDC - - console.log(`sending USDC...`) - - const externalSigner = await safe4337Pack.protocolKit.getSafeProvider().getExternalSigner() - const externalProvider = safe4337Pack.protocolKit.getSafeProvider().getExternalProvider() - - if (!externalSigner) { - throw new Error('No signer found!') - } - - // send 15 USDC to the Safe - await transfer(externalSigner, usdcTokenAddress, senderAddress, usdcAmount * 150n) - - console.log(`creating the Safe batch...`) - - const transferUSDC = { - to: usdcTokenAddress, - data: generateTransferCallData(senderAddress, usdcAmount), - value: '0' - } - const transactions = [transferUSDC, transferUSDC] - const timestamp = (await getBlock(externalProvider))?.timestamp || 0n - - // 2) Create transaction batch - const safeOperation = await safe4337Pack.createTransaction({ - transactions, - options: { - validAfter: Number(timestamp - 60_000n), - validUntil: Number(timestamp + 60_000n) - } - }) - - // 3) Sign SafeOperation - const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) - - console.log('SafeOperation', signedSafeOperation) - - // 4) Execute SafeOperation - const userOperationHash = await safe4337Pack.executeTransaction({ - executable: signedSafeOperation - }) - - await waitForOperationToFinish(userOperationHash, CHAIN_NAME, safe4337Pack) -} - -main() diff --git a/playground/relay-kit/usdc-transfer-4337-erc20.ts b/playground/relay-kit/usdc-transfer-4337-erc20.ts deleted file mode 100644 index 5f54a2db0..000000000 --- a/playground/relay-kit/usdc-transfer-4337-erc20.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { getBlock } from 'viem/actions' -import { Safe4337Pack } from '@safe-global/relay-kit' -import { waitForOperationToFinish, transfer, generateTransferCallData } from '../utils' - -// Safe owner PK -const PRIVATE_KEY = '' - -const PIMLICO_API_KEY = '' - -// Safe 4337 compatible -const SAFE_ADDRESS = '' - -// CHAIN -const CHAIN_NAME = 'sepolia' -// const CHAIN_NAME = 'gnosis' - -// RPC URL -const RPC_URL = 'https://rpc.sepolia.org' // SEPOLIA -// const RPC_URL = 'https://rpc.gnosischain.com/' // GNOSIS - -// Bundler URL -const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO - -// PAYMASTER ADDRESS -const paymasterAddress = '0x0000000000325602a77416A16136FDafd04b299f' // SEPOLIA -// const paymasterAddress = '0x000000000034B78bfe02Be30AE4D324c8702803d' // GNOSIS - -// USDC CONTRACT ADDRESS IN SEPOLIA -// faucet: https://faucet.circle.com/ -const usdcTokenAddress = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // SEPOLIA -// const usdcTokenAddress = '0xddafbb505ad214d7b80b1f830fccc89b60fb7a83' // GNOSIS - -async function main() { - // 1) Initialize pack with the paymaster data - const safe4337Pack = await Safe4337Pack.init({ - provider: RPC_URL, - signer: PRIVATE_KEY, - bundlerUrl: BUNDLER_URL, - paymasterOptions: { - paymasterTokenAddress: usdcTokenAddress, - paymasterAddress - // amountToApprove?: bigint // optional value to set the paymaster approve amount on the deployment - }, - options: { - safeAddress: SAFE_ADDRESS - } - }) - - // Log supported entry points and chain id - console.log('Supported Entry Points', await safe4337Pack.getSupportedEntryPoints()) - console.log('Chain Id', await safe4337Pack.getChainId()) - - // Create transaction batch with two 0.1 USDC transfers - const senderAddress = await safe4337Pack.protocolKit.getAddress() - - const usdcAmount = 100_000n // 0.1 USDC - - console.log(`sending USDC...`) - - const externalSigner = await safe4337Pack.protocolKit.getSafeProvider().getExternalSigner() - const externalProvider = safe4337Pack.protocolKit.getSafeProvider().getExternalProvider() - - if (!externalSigner) { - throw new Error('No signer found!') - } - - // send 5 USDC to the Safe - await transfer(externalSigner, usdcTokenAddress, senderAddress, usdcAmount * 50n) - - console.log(`creating the Safe batch...`) - - const transferUSDC = { - to: usdcTokenAddress, - data: generateTransferCallData(senderAddress, usdcAmount), - value: '0' - } - const transactions = [transferUSDC, transferUSDC] - const timestamp = (await getBlock(externalProvider))?.timestamp || 0n - - // 2) Create transaction batch - const safeOperation = await safe4337Pack.createTransaction({ - transactions, - options: { - validAfter: Number(timestamp - 60_000n), - validUntil: Number(timestamp + 60_000n) - } - }) - - // 3) Sign SafeOperation - const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) - - console.log('SafeOperation', signedSafeOperation) - - // 4) Execute SafeOperation - const userOperationHash = await safe4337Pack.executeTransaction({ - executable: signedSafeOperation - }) - - await waitForOperationToFinish(userOperationHash, CHAIN_NAME, safe4337Pack) -} - -main() diff --git a/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts b/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts deleted file mode 100644 index 38d2024aa..000000000 --- a/playground/relay-kit/usdc-transfer-4337-sponsored-counterfactual.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { getBlock } from 'viem/actions' -import { Safe4337Pack } from '@safe-global/relay-kit' -import { waitForOperationToFinish, transfer, generateTransferCallData } from '../utils' - -// Safe owner PK -const PRIVATE_KEY = '' - -const PIMLICO_API_KEY = '' - -// Safe owner address -const OWNER_ADDRESS = '' - -// PolicyId is an optional parameter, you can create one here: https://dashboard.pimlico.io/sponsorship-policies -const POLICY_ID = '' - -// CHAIN -const CHAIN_NAME = '11155111' -// const CHAIN_NAME = 'gnosis' - -// RPC URL -const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // SEPOLIA -// const RPC_URL = 'https://rpc.gnosischain.com/' // GNOSIS - -// Bundler URL -const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO - -// Paymaster URL -const PAYMASTER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO - -// USDC CONTRACT ADDRESS IN SEPOLIA -// faucet: https://faucet.circle.com/ -const usdcTokenAddress = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // SEPOLIA -// const usdcTokenAddress = '0xddafbb505ad214d7b80b1f830fccc89b60fb7a83' // GNOSIS - -async function main() { - // 1) Initialize pack with the paymaster data - const safe4337Pack = await Safe4337Pack.init({ - provider: RPC_URL, - signer: PRIVATE_KEY, - bundlerUrl: BUNDLER_URL, - paymasterOptions: { - isSponsored: true, - sponsorshipPolicyId: POLICY_ID, - paymasterUrl: PAYMASTER_URL - }, - options: { - owners: [OWNER_ADDRESS], - threshold: 1, - saltNonce: '4337' + '1' // to update the address - } - }) - - // Log supported entry points and chain id - console.log('Supported Entry Points', await safe4337Pack.getSupportedEntryPoints()) - console.log('Chain Id', await safe4337Pack.getChainId()) - - // Create transaction batch with two 0.1 USDC transfers - const senderAddress = await safe4337Pack.protocolKit.getAddress() - - console.log('senderAddress: ', senderAddress) - - console.log('is Safe Account deployed: ', await safe4337Pack.protocolKit.isSafeDeployed()) - - const usdcAmount = 100_000n // 0.1 USDC - - console.log(`sending USDC...`) - - const externalSigner = await safe4337Pack.protocolKit.getSafeProvider().getExternalSigner() - const externalProvider = safe4337Pack.protocolKit.getSafeProvider().getExternalProvider() - - if (!externalSigner) { - throw new Error('No signer found!') - } - - // send 0.2 USDC to the Safe - await transfer(externalSigner, usdcTokenAddress, senderAddress, usdcAmount * 2n) - - console.log(`creating the Safe batch...`) - - const transferUSDC = { - to: usdcTokenAddress, - data: generateTransferCallData(senderAddress, usdcAmount), - value: '0' - } - const transactions = [transferUSDC, transferUSDC] - const timestamp = (await getBlock(externalProvider))?.timestamp || 0n - - // 2) Create transaction batch - const safeOperation = await safe4337Pack.createTransaction({ - transactions, - options: { - validAfter: Number(timestamp - 60_000n), - validUntil: Number(timestamp + 60_000n) - } - }) - - // 3) Sign SafeOperation - const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) - - console.log('SafeOperation', signedSafeOperation) - - // 4) Execute SafeOperation - const userOperationHash = await safe4337Pack.executeTransaction({ - executable: signedSafeOperation - }) - - await waitForOperationToFinish(userOperationHash, CHAIN_NAME, safe4337Pack) -} - -main() diff --git a/playground/relay-kit/userop-counterfactual.ts b/playground/relay-kit/userop-counterfactual.ts new file mode 100644 index 000000000..9c7320787 --- /dev/null +++ b/playground/relay-kit/userop-counterfactual.ts @@ -0,0 +1,69 @@ +import { Safe4337Pack } from '@safe-global/relay-kit' +import { parseEther } from 'viem' +import { waitForOperationToFinish, setup4337Playground } from '../utils' + +// Safe owner PK +const PRIVATE_KEY = '' + +// Pimlico API key +const PIMLICO_API_KEY = '' + +// Safe owner address +const OWNER_ADDRESS = '' + +// RPC URL +const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // SEPOLIA + +// CHAIN +const CHAIN_NAME = '11155111' + +// Bundler URL +const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO + +// PIM test token contract address +// faucet: https://dashboard.pimlico.io/test-erc20-faucet +const pimlicoTokenAddress = '0xFC3e86566895Fb007c6A0d3809eb2827DF94F751' + +async function main() { + // 1) Initialize pack + const safe4337Pack = await Safe4337Pack.init({ + provider: RPC_URL, + signer: PRIVATE_KEY, + bundlerUrl: BUNDLER_URL, + options: { + owners: [OWNER_ADDRESS], + threshold: 1, + saltNonce: '4337' + '100' + } + }) + + // 1) Setup Playground + const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { + nativeTokenAmount: parseEther('0.1'), + erc20TokenAmount: 200_000n, + erc20TokenContractAddress: pimlicoTokenAddress + }) + + // 2) Create transaction batch + const safeOperation = await safe4337Pack.createTransaction({ + transactions, + options: { + validAfter: Number(timestamp - 60_000n), + validUntil: Number(timestamp + 60_000n) + } + }) + + // 3) Sign SafeOperation + const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) + + console.log('SafeOperation', signedSafeOperation) + + // 4) Execute SafeOperation + const userOperationHash = await safe4337Pack.executeTransaction({ + executable: signedSafeOperation + }) + + await waitForOperationToFinish(userOperationHash, CHAIN_NAME, safe4337Pack) +} + +main() diff --git a/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts b/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts new file mode 100644 index 000000000..85a57f0f1 --- /dev/null +++ b/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts @@ -0,0 +1,77 @@ +import { Safe4337Pack } from '@safe-global/relay-kit' +import { waitForOperationToFinish, setup4337Playground } from '../utils' + +// Safe owner PK +const PRIVATE_KEY = '' + +const PIMLICO_API_KEY = '' + +// Safe owner address +const OWNER_ADDRESS = '' + +// CHAIN +const CHAIN_NAME = '11155111' + +// RPC URL +const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // SEPOLIA + +// Bundler URL +const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO + +// Paymaster addresses +const paymasterAddress_v07 = '0x0000000000000039cd5e8ae05257ce51c473ddd1' +const paymasterAddress_v06 = '0x00000000000000fb866daaa79352cc568a005d96' + +// PIM test token contract address +// faucet: https://dashboard.pimlico.io/test-erc20-faucet +const pimlicoTokenAddress = '0xFC3e86566895Fb007c6A0d3809eb2827DF94F751' + +async function main() { + // 1) Initialize pack with the paymaster data + const safe4337Pack = await Safe4337Pack.init({ + provider: RPC_URL, + signer: PRIVATE_KEY, + bundlerUrl: BUNDLER_URL, + safeModulesVersion: '0.3.0', + paymasterOptions: { + paymasterUrl: BUNDLER_URL, + paymasterTokenAddress: pimlicoTokenAddress, + paymasterAddress: paymasterAddress_v07, + amountToApprove: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn + }, + options: { + owners: [OWNER_ADDRESS], + threshold: 1, + saltNonce: '4337' + '112321' // to update the address + } + }) + + // 1) Setup Playground + const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { + erc20TokenAmount: 200_000n, + erc20TokenContractAddress: pimlicoTokenAddress + }) + + // 2) Create transaction batch + const safeOperation = await safe4337Pack.createTransaction({ + transactions, + options: { + validAfter: Number(timestamp - 60_000n), + validUntil: Number(timestamp + 60_000n) + } + }) + + // 3) Sign SafeOperation + const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) + + console.log('SafeOperation', signedSafeOperation) + + // 4) Execute SafeOperation + const userOperationHash = await safe4337Pack.executeTransaction({ + executable: signedSafeOperation + }) + + await waitForOperationToFinish(userOperationHash, CHAIN_NAME, safe4337Pack) +} + +main() diff --git a/playground/relay-kit/userop-erc20-paymaster.ts b/playground/relay-kit/userop-erc20-paymaster.ts new file mode 100644 index 000000000..cfca2d644 --- /dev/null +++ b/playground/relay-kit/userop-erc20-paymaster.ts @@ -0,0 +1,75 @@ +import { Safe4337Pack } from '@safe-global/relay-kit' +import { waitForOperationToFinish, setup4337Playground } from '../utils' + +// Safe owner PK +const PRIVATE_KEY = '' + +const PIMLICO_API_KEY = '' + +// Safe 4337 compatible +const SAFE_ADDRESS = '' + +// CHAIN +const CHAIN_NAME = '11155111' + +// RPC URL +const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // SEPOLIA +// const RPC_URL = 'https://rpc.gnosischain.com/' // GNOSIS + +// Bundler URL +const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO + +// PAYMASTER ADDRESSES +const paymasterAddress_v07 = '0x0000000000000039cd5e8ae05257ce51c473ddd1' +const paymasterAddress_v06 = '0x00000000000000fb866daaa79352cc568a005d96' + +// PIM test token contract address +// faucet: https://dashboard.pimlico.io/test-erc20-faucet +const pimlicoTokenAddress = '0xFC3e86566895Fb007c6A0d3809eb2827DF94F751' + +async function main() { + // 1) Initialize pack with the paymaster data + const safe4337Pack = await Safe4337Pack.init({ + provider: RPC_URL, + signer: PRIVATE_KEY, + bundlerUrl: BUNDLER_URL, + safeModulesVersion: '0.3.0', + paymasterOptions: { + paymasterUrl: BUNDLER_URL, + paymasterTokenAddress: pimlicoTokenAddress, + paymasterAddress: paymasterAddress_v07 + }, + options: { + safeAddress: SAFE_ADDRESS + } + }) + + // 1) Setup Playground + const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { + erc20TokenAmount: 200_000n, + erc20TokenContractAddress: pimlicoTokenAddress + }) + + // 2) Create transaction batch + const safeOperation = await safe4337Pack.createTransaction({ + transactions, + options: { + validAfter: Number(timestamp - 60_000n), + validUntil: Number(timestamp + 60_000n) + } + }) + + // 3) Sign SafeOperation + const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) + + console.log('SafeOperation', signedSafeOperation) + + // 4) Execute SafeOperation + const userOperationHash = await safe4337Pack.executeTransaction({ + executable: signedSafeOperation + }) + + await waitForOperationToFinish(userOperationHash, CHAIN_NAME, safe4337Pack) +} + +main() diff --git a/playground/relay-kit/userop-verifying-paymaster-counterfactual.ts b/playground/relay-kit/userop-verifying-paymaster-counterfactual.ts new file mode 100644 index 000000000..0fdb71ae9 --- /dev/null +++ b/playground/relay-kit/userop-verifying-paymaster-counterfactual.ts @@ -0,0 +1,77 @@ +import { Safe4337Pack } from '@safe-global/relay-kit' +import { setup4337Playground, waitForOperationToFinish } from '../utils' + +// Safe owner PK +const PRIVATE_KEY = '' + +const PIMLICO_API_KEY = '' + +// Safe owner address +const OWNER_ADDRESS = '' + +// PolicyId is an optional parameter, you can create one here: https://dashboard.pimlico.io/sponsorship-policies +const POLICY_ID = '' + +// CHAIN +const CHAIN_NAME = '11155111' + +// RPC URL +const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // SEPOLIA + +// Bundler URL +const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO + +// Paymaster URL +const PAYMASTER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO + +// PIM test token contract address +// faucet: https://dashboard.pimlico.io/test-erc20-faucet +const pimlicoTokenAddress = '0xFC3e86566895Fb007c6A0d3809eb2827DF94F751' + +async function main() { + // 1) Initialize pack with the paymaster data + const safe4337Pack = await Safe4337Pack.init({ + provider: RPC_URL, + signer: PRIVATE_KEY, + bundlerUrl: BUNDLER_URL, + paymasterOptions: { + isSponsored: true, + sponsorshipPolicyId: POLICY_ID, + paymasterUrl: PAYMASTER_URL + }, + options: { + owners: [OWNER_ADDRESS], + threshold: 1, + saltNonce: '4337' + '1' + } + }) + + // 1) Setup Playground + const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { + erc20TokenAmount: 200_000n, + erc20TokenContractAddress: pimlicoTokenAddress + }) + + // 2) Create transaction batch + const safeOperation = await safe4337Pack.createTransaction({ + transactions, + options: { + validAfter: Number(timestamp - 60_000n), + validUntil: Number(timestamp + 60_000n) + } + }) + + // 3) Sign SafeOperation + const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) + + console.log('SafeOperation', signedSafeOperation) + + // 4) Execute SafeOperation + const userOperationHash = await safe4337Pack.executeTransaction({ + executable: signedSafeOperation + }) + + await waitForOperationToFinish(userOperationHash, CHAIN_NAME, safe4337Pack) +} + +main() diff --git a/playground/relay-kit/usdc-transfer-4337-sponsored.ts b/playground/relay-kit/userop-verifying-paymaster.ts similarity index 59% rename from playground/relay-kit/usdc-transfer-4337-sponsored.ts rename to playground/relay-kit/userop-verifying-paymaster.ts index 32687692e..ca5f6a968 100644 --- a/playground/relay-kit/usdc-transfer-4337-sponsored.ts +++ b/playground/relay-kit/userop-verifying-paymaster.ts @@ -1,6 +1,5 @@ -import { getBlock } from 'viem/actions' import { Safe4337Pack } from '@safe-global/relay-kit' -import { generateTransferCallData, waitForOperationToFinish } from '../utils' +import { setup4337Playground, waitForOperationToFinish } from '../utils' // Safe owner PK const PRIVATE_KEY = '' @@ -15,11 +14,9 @@ const POLICY_ID = '' // CHAIN const CHAIN_NAME = '11155111' -// const CHAIN_NAME = 'gnosis' // RPC URL const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // SEPOLIA -// const RPC_URL = 'https://rpc.gnosischain.com/' // GNOSIS // Bundler URL const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO @@ -27,10 +24,9 @@ const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLIC // Paymaster URL const PAYMASTER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO -// USDC CONTRACT ADDRESS IN SEPOLIA -// faucet: https://faucet.circle.com/ -const usdcTokenAddress = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // SEPOLIA -// const usdcTokenAddress = '0xddafbb505ad214d7b80b1f830fccc89b60fb7a83' // GNOSIS +// PIM test token contract address +// faucet: https://dashboard.pimlico.io/test-erc20-faucet +const pimlicoTokenAddress = '0xFC3e86566895Fb007c6A0d3809eb2827DF94F751' async function main() { // 1) Initialize pack with the paymaster data @@ -48,23 +44,11 @@ async function main() { } }) - // Log supported entry points and chain id - console.log('Supported Entry Points', await safe4337Pack.getSupportedEntryPoints()) - console.log('Chain Id', await safe4337Pack.getChainId()) - - // Create transaction batch with two 0.1 USDC transfers - const senderAddress = await safe4337Pack.protocolKit.getAddress() - - const usdcAmount = 100_000n // 0.1 USDC - - const transferUSDC = { - to: usdcTokenAddress, - data: generateTransferCallData(senderAddress, usdcAmount), - value: '0' - } - const transactions = [transferUSDC, transferUSDC] - const externalProvider = safe4337Pack.protocolKit.getSafeProvider().getExternalProvider() - const timestamp = (await getBlock(externalProvider))?.timestamp || 0n + // 1) Setup Playground + const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { + erc20TokenAmount: 200_000n, + erc20TokenContractAddress: pimlicoTokenAddress + }) // 2) Create transaction batch const safeOperation = await safe4337Pack.createTransaction({ diff --git a/playground/relay-kit/usdc-transfer-4337.ts b/playground/relay-kit/userop.ts similarity index 51% rename from playground/relay-kit/usdc-transfer-4337.ts rename to playground/relay-kit/userop.ts index 3b56e313e..c7ea70654 100644 --- a/playground/relay-kit/usdc-transfer-4337.ts +++ b/playground/relay-kit/userop.ts @@ -1,6 +1,5 @@ -import { getBlock } from 'viem/actions' import { Safe4337Pack } from '@safe-global/relay-kit' -import { generateTransferCallData, waitForOperationToFinish } from '../utils' +import { setup4337Playground, waitForOperationToFinish } from '../utils' // Safe owner PK const PRIVATE_KEY = '' @@ -10,17 +9,17 @@ const PIMLICO_API_KEY = '' // Safe 4337 compatible const SAFE_ADDRESS = '' -// Bundler URL -const BUNDLER_URL = `https://api.pimlico.io/v2/sepolia/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO - // RPC URL -const RPC_URL = 'https://rpc.sepolia.org' +const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // SEPOLIA -const CHAIN_NAME = 'sepolia' +const CHAIN_NAME = '11155111' + +// Bundler URL +const BUNDLER_URL = `https://api.pimlico.io/v2/sepolia/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO -// USDC CONTRACT ADDRESS IN SEPOLIA -// faucet: https://faucet.circle.com/ -const usdcTokenAddress = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' +// PIM test token contract address +// faucet: https://dashboard.pimlico.io/test-erc20-faucet +const pimlicoTokenAddress = '0xFC3e86566895Fb007c6A0d3809eb2827DF94F751' async function main() { // 1) Initialize pack @@ -33,24 +32,12 @@ async function main() { } }) - // Log supported entry points and chain id - console.log('Supported Entry Points', await safe4337Pack.getSupportedEntryPoints()) - console.log('Chain Id', await safe4337Pack.getChainId()) - - // Create transaction batch with two 0.1 USDC transfers - const senderAddress = await safe4337Pack.protocolKit.getAddress() - - const usdcAmount = 100_000n // 0.1 USDC - - // we transfer the USDC to the Safe Account itself - const transferUSDC = { - to: usdcTokenAddress, - data: generateTransferCallData(senderAddress, usdcAmount), - value: '0' - } - const transactions = [transferUSDC, transferUSDC] - const externalProvider = safe4337Pack.protocolKit.getSafeProvider().getExternalProvider() - const timestamp = (await getBlock(externalProvider))?.timestamp || 0n + // 1) Setup Playground + const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { + // nativeTokenAmount: parseEther('0.1'), + erc20TokenAmount: 200_000n, + erc20TokenContractAddress: pimlicoTokenAddress + }) // 2) Create transaction batch const safeOperation = await safe4337Pack.createTransaction({ diff --git a/playground/utils.ts b/playground/utils.ts index 461eeaf34..194113fe3 100644 --- a/playground/utils.ts +++ b/playground/utils.ts @@ -1,6 +1,16 @@ -import { Address, createPublicClient, custom, encodeFunctionData, parseAbi } from 'viem' +import { + Address, + createPublicClient, + custom, + encodeFunctionData, + formatEther, + parseAbi +} from 'viem' import { Safe4337Pack } from '@safe-global/relay-kit' import { ExternalSigner } from '@safe-global/protocol-kit' +import { getBlock, waitForTransactionReceipt } from 'viem/actions' +import { MetaTransactionData } from 'packages/types-kit/dist/src' +import { sepolia } from 'viem/chains' export const generateTransferCallData = (to: string, value: bigint) => { const functionAbi = parseAbi(['function transfer(address _to, uint256 _value) returns (bool)']) @@ -56,3 +66,72 @@ export async function transfer( return await publicClient.waitForTransactionReceipt({ hash }) } + +export async function setup4337Playground( + safe4337Pack: Safe4337Pack, + { + nativeTokenAmount, + erc20TokenAmount, + erc20TokenContractAddress + }: { + nativeTokenAmount?: bigint + erc20TokenAmount?: bigint + erc20TokenContractAddress: string + } = { + erc20TokenAmount: 200_000n, + erc20TokenContractAddress: '0xFC3e86566895Fb007c6A0d3809eb2827DF94F751' + } +): Promise<{ transactions: MetaTransactionData[]; timestamp: bigint }> { + const senderAddress = await safe4337Pack.protocolKit.getAddress() + + // Log supported entry points and Safe state + console.log('Supported Entry Points', await safe4337Pack.getSupportedEntryPoints()) + console.log('Chain Id', await safe4337Pack.getChainId()) + console.log('senderAddress: ', senderAddress) + console.log('is Safe Account deployed: ', await safe4337Pack.protocolKit.isSafeDeployed()) + + const externalProvider = safe4337Pack.protocolKit.getSafeProvider().getExternalProvider() + const externalSigner = await safe4337Pack.protocolKit.getSafeProvider().getExternalSigner() + const signerAddress = await safe4337Pack.protocolKit.getSafeProvider().getSignerAddress() + + if (!externalSigner || !signerAddress) { + throw new Error('No signer found!') + } + + // Fund Safe + if (nativeTokenAmount) { + console.log(`sending ${formatEther(nativeTokenAmount)} native tokens...`) + + const hash = await externalSigner?.sendTransaction({ + to: senderAddress, + value: nativeTokenAmount, + chain: sepolia + }) + + await waitForTransactionReceipt(externalProvider, { hash }) + } + + if (erc20TokenAmount && erc20TokenContractAddress) { + console.log(`sending test tokens...`) + + await transfer(externalSigner, erc20TokenContractAddress, senderAddress, erc20TokenAmount) + } + + // Create transaction batch + console.log(`creating the Safe batch ...`) + + const transferPIM = { + to: erc20TokenContractAddress, + data: generateTransferCallData(signerAddress, 100_000n), + value: '0' + } + + const timestamp = + (await getBlock(safe4337Pack.protocolKit.getSafeProvider().getExternalProvider()))?.timestamp || + 0n + + return { + transactions: [transferPIM, transferPIM], + timestamp + } +} From 71048b4aa7ee0102befda5abafd6ba010350212e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Thu, 23 Jan 2025 18:11:48 +0100 Subject: [PATCH 13/55] Update comments in playgrounds --- playground/relay-kit/userop-counterfactual.ts | 8 ++++---- .../relay-kit/userop-erc20-paymaster-counterfactual.ts | 8 ++++---- playground/relay-kit/userop-erc20-paymaster.ts | 8 ++++---- .../userop-verifying-paymaster-counterfactual.ts | 8 ++++---- playground/relay-kit/userop-verifying-paymaster.ts | 8 ++++---- playground/relay-kit/userop.ts | 8 ++++---- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/playground/relay-kit/userop-counterfactual.ts b/playground/relay-kit/userop-counterfactual.ts index 9c7320787..c875038fc 100644 --- a/playground/relay-kit/userop-counterfactual.ts +++ b/playground/relay-kit/userop-counterfactual.ts @@ -37,14 +37,14 @@ async function main() { } }) - // 1) Setup Playground + // 2) Setup Playground const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { nativeTokenAmount: parseEther('0.1'), erc20TokenAmount: 200_000n, erc20TokenContractAddress: pimlicoTokenAddress }) - // 2) Create transaction batch + // 3) Create SafeOperation const safeOperation = await safe4337Pack.createTransaction({ transactions, options: { @@ -53,12 +53,12 @@ async function main() { } }) - // 3) Sign SafeOperation + // 4) Sign SafeOperation const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) console.log('SafeOperation', signedSafeOperation) - // 4) Execute SafeOperation + // 5) Execute SafeOperation const userOperationHash = await safe4337Pack.executeTransaction({ executable: signedSafeOperation }) diff --git a/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts b/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts index 85a57f0f1..56551d6a9 100644 --- a/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts +++ b/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts @@ -46,13 +46,13 @@ async function main() { } }) - // 1) Setup Playground + // 2) Setup Playground const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { erc20TokenAmount: 200_000n, erc20TokenContractAddress: pimlicoTokenAddress }) - // 2) Create transaction batch + // 3) Create SafeOperation const safeOperation = await safe4337Pack.createTransaction({ transactions, options: { @@ -61,12 +61,12 @@ async function main() { } }) - // 3) Sign SafeOperation + // 4) Sign SafeOperation const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) console.log('SafeOperation', signedSafeOperation) - // 4) Execute SafeOperation + // 5) Execute SafeOperation const userOperationHash = await safe4337Pack.executeTransaction({ executable: signedSafeOperation }) diff --git a/playground/relay-kit/userop-erc20-paymaster.ts b/playground/relay-kit/userop-erc20-paymaster.ts index cfca2d644..a8e13af8b 100644 --- a/playground/relay-kit/userop-erc20-paymaster.ts +++ b/playground/relay-kit/userop-erc20-paymaster.ts @@ -44,13 +44,13 @@ async function main() { } }) - // 1) Setup Playground + // 2) Setup Playground const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { erc20TokenAmount: 200_000n, erc20TokenContractAddress: pimlicoTokenAddress }) - // 2) Create transaction batch + // 3) Create SafeOperation const safeOperation = await safe4337Pack.createTransaction({ transactions, options: { @@ -59,12 +59,12 @@ async function main() { } }) - // 3) Sign SafeOperation + // 4) Sign SafeOperation const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) console.log('SafeOperation', signedSafeOperation) - // 4) Execute SafeOperation + // 5) Execute SafeOperation const userOperationHash = await safe4337Pack.executeTransaction({ executable: signedSafeOperation }) diff --git a/playground/relay-kit/userop-verifying-paymaster-counterfactual.ts b/playground/relay-kit/userop-verifying-paymaster-counterfactual.ts index 0fdb71ae9..fd260c234 100644 --- a/playground/relay-kit/userop-verifying-paymaster-counterfactual.ts +++ b/playground/relay-kit/userop-verifying-paymaster-counterfactual.ts @@ -46,13 +46,13 @@ async function main() { } }) - // 1) Setup Playground + // 2) Setup Playground const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { erc20TokenAmount: 200_000n, erc20TokenContractAddress: pimlicoTokenAddress }) - // 2) Create transaction batch + // 3) Create SafeOperation const safeOperation = await safe4337Pack.createTransaction({ transactions, options: { @@ -61,12 +61,12 @@ async function main() { } }) - // 3) Sign SafeOperation + // 4) Sign SafeOperation const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) console.log('SafeOperation', signedSafeOperation) - // 4) Execute SafeOperation + // 5) Execute SafeOperation const userOperationHash = await safe4337Pack.executeTransaction({ executable: signedSafeOperation }) diff --git a/playground/relay-kit/userop-verifying-paymaster.ts b/playground/relay-kit/userop-verifying-paymaster.ts index ca5f6a968..9eae591c2 100644 --- a/playground/relay-kit/userop-verifying-paymaster.ts +++ b/playground/relay-kit/userop-verifying-paymaster.ts @@ -44,13 +44,13 @@ async function main() { } }) - // 1) Setup Playground + // 2) Setup Playground const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { erc20TokenAmount: 200_000n, erc20TokenContractAddress: pimlicoTokenAddress }) - // 2) Create transaction batch + // 3) Create SafeOperation const safeOperation = await safe4337Pack.createTransaction({ transactions, options: { @@ -59,12 +59,12 @@ async function main() { } }) - // 3) Sign SafeOperation + // 4) Sign SafeOperation const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) console.log('SafeOperation', signedSafeOperation) - // 4) Execute SafeOperation + // 5) Execute SafeOperation const userOperationHash = await safe4337Pack.executeTransaction({ executable: signedSafeOperation }) diff --git a/playground/relay-kit/userop.ts b/playground/relay-kit/userop.ts index c7ea70654..1cac4b6dd 100644 --- a/playground/relay-kit/userop.ts +++ b/playground/relay-kit/userop.ts @@ -32,14 +32,14 @@ async function main() { } }) - // 1) Setup Playground + // 2) Setup Playground const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { // nativeTokenAmount: parseEther('0.1'), erc20TokenAmount: 200_000n, erc20TokenContractAddress: pimlicoTokenAddress }) - // 2) Create transaction batch + // 3) Create SafeOperation const safeOperation = await safe4337Pack.createTransaction({ transactions, options: { @@ -48,12 +48,12 @@ async function main() { } }) - // 3) Sign SafeOperation + // 4) Sign SafeOperation const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) console.log('SafeOperation', signedSafeOperation) - // 4) Execute SafeOperation + // 5) Execute SafeOperation const userOperationHash = await safe4337Pack.executeTransaction({ executable: signedSafeOperation }) From a27f78d7624389ad0d9c1bac2ef1774decadab17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Fri, 24 Jan 2025 10:47:44 +0100 Subject: [PATCH 14/55] Improve SafeOperation --- .../src/packs/safe-4337/Safe4337Pack.ts | 84 ++--------------- .../src/packs/safe-4337/SafeOperation.ts | 93 ++++++++++++++++++- .../relay-kit/src/packs/safe-4337/utils.ts | 46 --------- .../packs/safe-4337/utils/userOperations.ts | 35 +------ packages/types-kit/src/types.ts | 2 + 5 files changed, 103 insertions(+), 157 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index fc4e3b563..f5754ecc6 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -13,8 +13,7 @@ import { isSafeOperationResponse, OperationType, SafeOperationConfirmation, - SafeOperationResponse, - SafeSignature + SafeOperationResponse } from '@safe-global/types-kit' import { getSafeModuleSetupDeployment, @@ -40,12 +39,7 @@ import { DEFAULT_SAFE_MODULES_VERSION, RPC_4337_CALLS } from './constants' -import { - addDummySignature, - getEip4337BundlerProvider, - signSafeOp, - userOperationToHexValues -} from './utils' +import { addDummySignature, getEip4337BundlerProvider, userOperationToHexValues } from './utils' import { entryPointToSafeModules } from './utils/entrypoint' import { PimlicoFeeEstimator } from './estimators/PimlicoFeeEstimator' import getRelayKitVersion from './utils/getRelayKitVersion' @@ -497,10 +491,11 @@ export class Safe4337Pack extends RelayKitBasePack<{ userOperation.callData += this.#onchainIdentifier } - const safeOperation = new EthSafeOperation(userOperation, { + const safeOperation = new EthSafeOperation(userOperation, this.protocolKit, { chainId: this.#chainId, moduleAddress: this.#SAFE_4337_MODULE_ADDRESS, entryPoint: this.#ENTRYPOINT_ADDRESS, + sharedSigner: this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, validUntil, validAfter }) @@ -536,9 +531,11 @@ export class Safe4337Pack extends RelayKitBasePack<{ paymasterAndData: concat([paymaster, paymasterData]), signature: safeOperationResponse.preparedSignature || '0x' }, + this.protocolKit, { chainId: this.#chainId, moduleAddress: this.#SAFE_4337_MODULE_ADDRESS, + sharedSigner: this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, entryPoint: userOperation?.entryPoint || this.#ENTRYPOINT_ADDRESS, validAfter: this.#timestamp(validAfter), validUntil: this.#timestamp(validUntil) @@ -584,74 +581,9 @@ export class Safe4337Pack extends RelayKitBasePack<{ safeOp = safeOperation } - const safeProvider = this.protocolKit.getSafeProvider() - const signerAddress = await safeProvider.getSignerAddress() - const isPasskeySigner = await safeProvider.isPasskeySigner() - - if (!signerAddress) { - throw new Error('There is no signer address available to sign the SafeOperation') - } - - const isOwner = await this.protocolKit.isOwner(signerAddress) - const isSafeDeployed = await this.protocolKit.isSafeDeployed() - - if ((!isOwner && isSafeDeployed) || (!isSafeDeployed && !isPasskeySigner && !isOwner)) { - throw new Error('UserOperations can only be signed by Safe owners') - } - - let signature: SafeSignature - - if (isPasskeySigner) { - const safeOpHash = safeOp.getHash() - - // if the Safe is not deployed we force the Shared Signer signature - if (!isSafeDeployed) { - const passkeySignature = await this.protocolKit.signHash(safeOpHash) - // SafeWebAuthnSharedSigner signature - signature = new EthSafeSignature( - this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, - passkeySignature.data, - true // passkeys are contract signatures - ) - } else { - signature = await this.protocolKit.signHash(safeOpHash) - } - } else { - if ( - [ - SigningMethod.ETH_SIGN_TYPED_DATA_V4, - SigningMethod.ETH_SIGN_TYPED_DATA_V3, - SigningMethod.ETH_SIGN_TYPED_DATA - ].includes(signingMethod) - ) { - signature = await signSafeOp( - safeOp.getSafeOperation(), - this.protocolKit.getSafeProvider(), - this.#SAFE_4337_MODULE_ADDRESS, - this.#ENTRYPOINT_ADDRESS - ) - } else { - const safeOpHash = safeOp.getHash() - - signature = await this.protocolKit.signHash(safeOpHash) - } - } - - const signedSafeOperation = new EthSafeOperation(safeOp.getUserOperation(), { - chainId: this.#chainId, - moduleAddress: this.#SAFE_4337_MODULE_ADDRESS, - entryPoint: this.#ENTRYPOINT_ADDRESS, - validUntil: safeOp.options.validUntil, - validAfter: safeOp.options.validAfter - }) - - safeOp.signatures.forEach((signature: SafeSignature) => { - signedSafeOperation.addSignature(signature) - }) - - signedSafeOperation.addSignature(signature) + await safeOp.sign(signingMethod) - return signedSafeOperation + return safeOp } /** diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts index 15cfabd68..8dbb1efd3 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts @@ -9,17 +9,24 @@ import { UserOperationV06, UserOperationV07 } from '@safe-global/types-kit' -import { buildSignatureBytes } from '@safe-global/protocol-kit' +import Safe, { + buildSignatureBytes, + EthSafeSignature, + SigningMethod +} from '@safe-global/protocol-kit' import { calculateSafeUserOperationHash } from './utils' import { isEntryPointV7 } from './utils/entrypoint' +import { EIP712_SAFE_OPERATION_TYPE_V06, EIP712_SAFE_OPERATION_TYPE_V07 } from './constants' class EthSafeOperation implements SafeOperation { - options: SafeOperationOptions userOperation: UserOperation + protocolKit: Safe + options: SafeOperationOptions signatures: Map = new Map() - constructor(userOperation: UserOperation, options: SafeOperationOptions) { + constructor(userOperation: UserOperation, protocolKit: Safe, options: SafeOperationOptions) { this.userOperation = userOperation + this.protocolKit = protocolKit this.options = options } @@ -111,6 +118,86 @@ class EthSafeOperation implements SafeOperation { } } + async sign(signingMethod: SigningMethod = SigningMethod.ETH_SIGN_TYPED_DATA_V4) { + const safeProvider = this.protocolKit.getSafeProvider() + const signerAddress = await safeProvider.getSignerAddress() + const isPasskeySigner = await safeProvider.isPasskeySigner() + + if (!signerAddress) { + throw new Error('There is no signer address available to sign the SafeOperation') + } + + const isOwner = await this.protocolKit.isOwner(signerAddress) + const isSafeDeployed = await this.protocolKit.isSafeDeployed() + + if ((!isOwner && isSafeDeployed) || (!isSafeDeployed && !isPasskeySigner && !isOwner)) { + throw new Error('UserOperations can only be signed by Safe owners') + } + + let safeSignature: SafeSignature + + if (isPasskeySigner) { + const safeOpHash = this.getHash() + + // if the Safe is not deployed we force the Shared Signer signature + if (!isSafeDeployed) { + const passkeySignature = await this.protocolKit.signHash(safeOpHash) + // SafeWebAuthnSharedSigner signature + safeSignature = new EthSafeSignature( + this.options.sharedSigner, + passkeySignature.data, + true // passkeys are contract signatures + ) + } else { + safeSignature = await this.protocolKit.signHash(safeOpHash) + } + } else { + if ( + [ + SigningMethod.ETH_SIGN_TYPED_DATA_V4, + SigningMethod.ETH_SIGN_TYPED_DATA_V3, + SigningMethod.ETH_SIGN_TYPED_DATA + ].includes(signingMethod) + ) { + const signer = await safeProvider.getExternalSigner() + + if (!signer) { + throw new Error('No signer found') + } + + const chainId = await safeProvider.getChainId() + const signerAddress = signer.account.address + const safeOperation = this.getSafeOperation() + const signature = await signer.signTypedData({ + domain: { + chainId: Number(chainId), + verifyingContract: this.options.moduleAddress + }, + types: isEntryPointV7(this.options.entryPoint) + ? EIP712_SAFE_OPERATION_TYPE_V07 + : EIP712_SAFE_OPERATION_TYPE_V06, + message: { + ...safeOperation, + nonce: toHex(safeOperation.nonce), + validAfter: toHex(safeOperation.validAfter), + validUntil: toHex(safeOperation.validUntil), + maxFeePerGas: toHex(safeOperation.maxFeePerGas), + maxPriorityFeePerGas: toHex(safeOperation.maxPriorityFeePerGas) + }, + primaryType: 'SafeOp' + }) + + safeSignature = new EthSafeSignature(signerAddress, signature) + } else { + const safeOpHash = this.getHash() + + safeSignature = await this.protocolKit.signHash(safeOpHash) + } + } + + this.addSignature(safeSignature) + } + getUserOperation(): UserOperation { return { ...this.userOperation, diff --git a/packages/relay-kit/src/packs/safe-4337/utils.ts b/packages/relay-kit/src/packs/safe-4337/utils.ts index bc1e53dec..6a85188e1 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils.ts @@ -12,13 +12,11 @@ import { SafeUserOperation, OperationType, MetaTransactionData, - SafeSignature, UserOperation, UserOperationV07 } from '@safe-global/types-kit' import { EthSafeSignature, - SafeProvider, encodeMultiSendData, buildSignatureBytes } from '@safe-global/protocol-kit' @@ -41,50 +39,6 @@ export function getEip4337BundlerProvider(bundlerUrl: string): BundlerClient { return provider } -/** - * Signs typed data. - * - * @param {SafeUserOperation} safeUserOperation - Safe user operation to sign. - * @param {SafeProvider} safeProvider - Safe provider. - * @param {string} safe4337ModuleAddress - Safe 4337 module address. - * @return {Promise} The SafeSignature object containing the data and the signatures. - */ -export async function signSafeOp( - safeUserOperation: SafeUserOperation, - safeProvider: SafeProvider, - safe4337ModuleAddress: string, - entryPoint: string -): Promise { - const signer = await safeProvider.getExternalSigner() - - if (!signer) { - throw new Error('No signer found') - } - - const chainId = await safeProvider.getChainId() - const signerAddress = signer.account.address - const signature = await signer.signTypedData({ - domain: { - chainId: Number(chainId), - verifyingContract: safe4337ModuleAddress - }, - types: isEntryPointV7(entryPoint) - ? EIP712_SAFE_OPERATION_TYPE_V07 - : EIP712_SAFE_OPERATION_TYPE_V06, - message: { - ...safeUserOperation, - nonce: toHex(safeUserOperation.nonce), - validAfter: toHex(safeUserOperation.validAfter), - validUntil: toHex(safeUserOperation.validUntil), - maxFeePerGas: toHex(safeUserOperation.maxFeePerGas), - maxPriorityFeePerGas: toHex(safeUserOperation.maxPriorityFeePerGas) - }, - primaryType: 'SafeOp' - }) - - return new EthSafeSignature(signerAddress, signature) -} - /** * Encodes multi-send data from transactions batch. * diff --git a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts index 29f031c27..1bbec3742 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts @@ -17,7 +17,7 @@ import { encodeMultiSendCallData } from '../utils' * @param {MetaTransactionData} transaction - The transaction data to encode. * @return {string} The encoded call data string. */ -export function encodeExecuteUserOpCallData(transaction: MetaTransactionData): string { +function encodeExecuteUserOpCallData(transaction: MetaTransactionData): string { return encodeFunctionData({ abi: ABI, functionName: 'executeUserOp', @@ -30,7 +30,7 @@ export function encodeExecuteUserOpCallData(transaction: MetaTransactionData): s }) } -export async function getCallData( +async function getCallData( protocolKit: Safe, transactions: MetaTransactionData[], paymasterOptions: ERC20PaymasterOption, @@ -66,36 +66,7 @@ export async function getCallData( return callData } -export function unpackPaymasterAndData( - paymasterAndData: string -): Pick< - UserOperationV07, - 'paymaster' | 'paymasterVerificationGasLimit' | 'paymasterPostOpGasLimit' | 'paymasterData' -> { - const paymasterAndDataBytes = hexToBytes(paymasterAndData as Hex) - const isZero = paymasterAndDataBytes.every((byte) => byte === 0) - - const unpackedData = - paymasterAndDataBytes.length > 0 && !isZero - ? { - paymaster: getAddress(sliceHex(paymasterAndData as Hex, 0, 20)), - paymasterVerificationGasLimit: BigInt(sliceHex(paymasterAndData as Hex, 20, 36)), - paymasterPostOpGasLimit: BigInt(sliceHex(paymasterAndData as Hex, 36, 52)), - paymasterData: sliceHex(paymasterAndData as Hex, 52) - } - : { - paymaster: '0x', - paymasterData: '0x', - paymasterVerificationGasLimit: undefined, - paymasterPostOpGasLimit: undefined - } - - return unpackedData -} - -export function unpackInitCode( - initCode: string -): Pick { +function unpackInitCode(initCode: string): Pick { const initCodeBytes = hexToBytes(initCode as Hex) return initCodeBytes.length > 0 diff --git a/packages/types-kit/src/types.ts b/packages/types-kit/src/types.ts index 349d8da34..b1cc07a66 100644 --- a/packages/types-kit/src/types.ts +++ b/packages/types-kit/src/types.ts @@ -327,6 +327,7 @@ export type SafeOperationOptions = { moduleAddress: string entryPoint: string chainId: bigint + sharedSigner: string validAfter?: number validUntil?: number } @@ -336,6 +337,7 @@ export interface SafeOperation { options: SafeOperationOptions readonly signatures: Map + sign(signingMethod: string): void getSignature(signer: string): SafeSignature | undefined addSignature(signature: SafeSignature): void encodedSignatures(): string From d94bbe862b0d86c99345057ef4201b6ebcc0b911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Fri, 24 Jan 2025 12:24:33 +0100 Subject: [PATCH 15/55] Remove calculateSafeUserOperationHash --- .../src/packs/safe-4337/SafeOperation.ts | 23 ++++++++------ .../relay-kit/src/packs/safe-4337/utils.ts | 31 +------------------ 2 files changed, 14 insertions(+), 40 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts index 8dbb1efd3..d0835ece3 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts @@ -1,4 +1,4 @@ -import { Hex, concat, encodePacked, isAddress, pad, toHex } from 'viem' +import { Hex, concat, encodePacked, hashTypedData, isAddress, pad, toHex } from 'viem' import { EstimateGasData, SafeOperation, @@ -14,7 +14,6 @@ import Safe, { EthSafeSignature, SigningMethod } from '@safe-global/protocol-kit' -import { calculateSafeUserOperationHash } from './utils' import { isEntryPointV7 } from './utils/entrypoint' import { EIP712_SAFE_OPERATION_TYPE_V06, EIP712_SAFE_OPERATION_TYPE_V07 } from './constants' @@ -165,12 +164,11 @@ class EthSafeOperation implements SafeOperation { throw new Error('No signer found') } - const chainId = await safeProvider.getChainId() const signerAddress = signer.account.address const safeOperation = this.getSafeOperation() const signature = await signer.signTypedData({ domain: { - chainId: Number(chainId), + chainId: Number(this.options.chainId), verifyingContract: this.options.moduleAddress }, types: isEntryPointV7(this.options.entryPoint) @@ -213,12 +211,17 @@ class EthSafeOperation implements SafeOperation { } getHash(): string { - return calculateSafeUserOperationHash( - this.getSafeOperation(), - this.options.chainId, - this.options.moduleAddress, - this.options.entryPoint - ) + return hashTypedData({ + domain: { + chainId: Number(this.options.chainId), + verifyingContract: this.options.moduleAddress + }, + types: isEntryPointV7(this.options.entryPoint) + ? EIP712_SAFE_OPERATION_TYPE_V07 + : EIP712_SAFE_OPERATION_TYPE_V06, + primaryType: 'SafeOp', + message: this.getSafeOperation() + }) } } diff --git a/packages/relay-kit/src/packs/safe-4337/utils.ts b/packages/relay-kit/src/packs/safe-4337/utils.ts index 6a85188e1..fdb08870b 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils.ts @@ -3,13 +3,11 @@ import { createPublicClient, encodeFunctionData, encodePacked, - hashTypedData, http, rpcSchema, toHex } from 'viem' import { - SafeUserOperation, OperationType, MetaTransactionData, UserOperation, @@ -20,7 +18,7 @@ import { encodeMultiSendData, buildSignatureBytes } from '@safe-global/protocol-kit' -import { ABI, EIP712_SAFE_OPERATION_TYPE_V06, EIP712_SAFE_OPERATION_TYPE_V07 } from './constants' +import { ABI } from './constants' import { BundlerClient, PimlicoCustomRpcSchema } from './types' import { isEntryPointV7 } from './utils/entrypoint' @@ -57,33 +55,6 @@ export function encodeMultiSendCallData(transactions: MetaTransactionData[]): st }) } -/** - * Gets the safe user operation hash. - * - * @param {SafeUserOperation} safeUserOperation - The SafeUserOperation. - * @param {bigint} chainId - The chain id. - * @param {string} safe4337ModuleAddress - The Safe 4337 module address. - * @return {string} The hash of the safe operation. - */ -export function calculateSafeUserOperationHash( - safeUserOperation: SafeUserOperation, - chainId: bigint, - safe4337ModuleAddress: string, - entryPoint: string -): string { - return hashTypedData({ - domain: { - chainId: Number(chainId), - verifyingContract: safe4337ModuleAddress - }, - types: isEntryPointV7(entryPoint) - ? EIP712_SAFE_OPERATION_TYPE_V07 - : EIP712_SAFE_OPERATION_TYPE_V06, - primaryType: 'SafeOp', - message: safeUserOperation - }) -} - /** * Converts various bigint values from a UserOperation to their hexadecimal representation. * From 0cd4cfac874769e801dee406d7db3cba084d2cbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Fri, 24 Jan 2025 13:21:30 +0100 Subject: [PATCH 16/55] Improve dummySignatures retrieval --- .../src/packs/safe-4337/Safe4337Pack.ts | 27 +++++++------------ .../relay-kit/src/packs/safe-4337/utils.ts | 22 +++++---------- 2 files changed, 17 insertions(+), 32 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index f5754ecc6..af839e2f6 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -39,7 +39,7 @@ import { DEFAULT_SAFE_MODULES_VERSION, RPC_4337_CALLS } from './constants' -import { addDummySignature, getEip4337BundlerProvider, userOperationToHexValues } from './utils' +import { getDummySignature, getEip4337BundlerProvider, userOperationToHexValues } from './utils' import { entryPointToSafeModules } from './utils/entrypoint' import { PimlicoFeeEstimator } from './estimators/PimlicoFeeEstimator' import getRelayKitVersion from './utils/getRelayKitVersion' @@ -416,14 +416,10 @@ export class Safe4337Pack extends RelayKitBasePack<{ const estimateUserOperationGas = await this.#bundlerClient.request({ method: RPC_4337_CALLS.ESTIMATE_USER_OPERATION_GAS, params: [ - userOperationToHexValues( - addDummySignature( - safeOperation.getUserOperation(), - this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, - threshold - ), - this.#ENTRYPOINT_ADDRESS - ), + { + ...userOperationToHexValues(safeOperation.getUserOperation(), this.#ENTRYPOINT_ADDRESS), + signature: getDummySignature(this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, threshold) + }, this.#ENTRYPOINT_ADDRESS ] }) @@ -448,11 +444,10 @@ export class Safe4337Pack extends RelayKitBasePack<{ if (this.#paymasterOptions) { const paymasterEstimation = await feeEstimator?.getPaymasterEstimation?.({ - userOperation: addDummySignature( - safeOperation.getUserOperation(), - this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, - threshold - ), + userOperation: { + ...safeOperation.getUserOperation(), + signature: getDummySignature(this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, threshold) + }, bundlerUrl: this.#BUNDLER_URL, entryPoint: this.#ENTRYPOINT_ADDRESS, paymasterOptions: this.#paymasterOptions @@ -604,12 +599,10 @@ export class Safe4337Pack extends RelayKitBasePack<{ safeOperation = executable } - const userOperation = safeOperation.getUserOperation() - return this.#bundlerClient.request({ method: RPC_4337_CALLS.SEND_USER_OPERATION, params: [ - userOperationToHexValues(userOperation, this.#ENTRYPOINT_ADDRESS), + userOperationToHexValues(safeOperation.getUserOperation(), this.#ENTRYPOINT_ADDRESS), this.#ENTRYPOINT_ADDRESS ] }) diff --git a/packages/relay-kit/src/packs/safe-4337/utils.ts b/packages/relay-kit/src/packs/safe-4337/utils.ts index fdb08870b..694a867f1 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils.ts @@ -19,7 +19,7 @@ import { buildSignatureBytes } from '@safe-global/protocol-kit' import { ABI } from './constants' -import { BundlerClient, PimlicoCustomRpcSchema } from './types' +import { BundlerClient, PimlicoCustomRpcSchema, UserOperationStringValues } from './types' import { isEntryPointV7 } from './utils/entrypoint' /** @@ -61,7 +61,10 @@ export function encodeMultiSendCallData(transactions: MetaTransactionData[]): st * @param {UserOperation} userOperation - The UserOperation object whose values are to be converted. * @returns {UserOperation} A new UserOperation object with the values converted to hexadecimal. */ -export function userOperationToHexValues(userOperation: UserOperation, entryPointAddress: string) { +export function userOperationToHexValues( + userOperation: UserOperation, + entryPointAddress: string +): UserOperationStringValues { const userOpV07 = userOperation as UserOperationV07 const userOperationWithHexValues = { @@ -114,16 +117,11 @@ DUMMY_AUTHENTICATOR_DATA[32] = 0x04 /** * This method creates a dummy signature for the SafeOperation based on the Safe threshold. We assume that all owners are passkeys * This is useful for gas estimations - * @param userOperation - The user operation * @param signer - The signer * @param threshold - The Safe threshold * @returns The user operation with the dummy passkey signature */ -export function addDummySignature( - userOperation: UserOperation, - signer: string, - threshold: number -): UserOperation { +export function getDummySignature(signer: string, threshold: number): string { const signatures = [] for (let i = 0; i < threshold; i++) { @@ -138,13 +136,7 @@ export function addDummySignature( signatures.push(new EthSafeSignature(signer, passkeySignature, isContractSignature)) } - return { - ...userOperation, - signature: encodePacked( - ['uint48', 'uint48', 'bytes'], - [0, 0, buildSignatureBytes(signatures) as Hex] - ) - } + return encodePacked(['uint48', 'uint48', 'bytes'], [0, 0, buildSignatureBytes(signatures) as Hex]) } /** From ce907704f0b9bbf223ab598735ff740fd899a4fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Fri, 24 Jan 2025 15:28:28 +0100 Subject: [PATCH 17/55] Improve version management for SafeOperations --- packages/relay-kit/src/index.ts | 5 +- .../src/packs/safe-4337/Safe4337Pack.test.ts | 2 +- .../src/packs/safe-4337/Safe4337Pack.ts | 65 ++++++----- .../src/packs/safe-4337/SafeOperation.test.ts | 2 +- ...{SafeOperation.ts => SafeOperationBase.ts} | 107 +++--------------- .../packs/safe-4337/SafeOperationFactory.ts | 27 +++++ .../src/packs/safe-4337/SafeOperationV06.ts | 62 ++++++++++ .../src/packs/safe-4337/SafeOperationV07.ts | 89 +++++++++++++++ .../relay-kit/src/packs/safe-4337/types.ts | 6 +- 9 files changed, 235 insertions(+), 130 deletions(-) rename packages/relay-kit/src/packs/safe-4337/{SafeOperation.ts => SafeOperationBase.ts} (50%) create mode 100644 packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts create mode 100644 packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts create mode 100644 packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts diff --git a/packages/relay-kit/src/index.ts b/packages/relay-kit/src/index.ts index 7c8aad52d..75dfad535 100644 --- a/packages/relay-kit/src/index.ts +++ b/packages/relay-kit/src/index.ts @@ -4,7 +4,10 @@ export * from './packs/gelato/GelatoRelayPack' export * from './packs/gelato/types' export * from './packs/safe-4337/Safe4337Pack' -export { default as EthSafeOperation } from './packs/safe-4337/SafeOperation' +export { default as SafeOperationBase } from './packs/safe-4337/SafeOperationBase' +export { default as SafeOperationV07 } from './packs/safe-4337/SafeOperationV07' +export { default as SafeOperationV06 } from './packs/safe-4337/SafeOperationV06' +export { default as SafeOperationFactory } from './packs/safe-4337/SafeOperationFactory' export * from './packs/safe-4337/estimators' export * from './packs/safe-4337/types' diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts index 0eec33ea5..48fe39ecc 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts @@ -10,7 +10,7 @@ import { } from '@safe-global/safe-modules-deployments' import { MetaTransactionData, OperationType } from '@safe-global/types-kit' import { Safe4337Pack } from './Safe4337Pack' -import EthSafeOperation from './SafeOperation' +import EthSafeOperation from './SafeOperationBase' import * as constants from './constants' import * as fixtures from './testing-utils/fixtures' import { createSafe4337Pack, generateTransferCallData } from './testing-utils/helpers' diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index af839e2f6..d29d46051 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -21,7 +21,7 @@ import { getSafeWebAuthnShareSignerDeployment } from '@safe-global/safe-modules-deployments' import { Hash, encodeFunctionData, zeroAddress, Hex, concat } from 'viem' -import EthSafeOperation from './SafeOperation' +import SafeOperationBase from './SafeOperationBase' import { EstimateFeeProps, Safe4337CreateTransactionProps, @@ -44,6 +44,7 @@ import { entryPointToSafeModules } from './utils/entrypoint' import { PimlicoFeeEstimator } from './estimators/PimlicoFeeEstimator' import getRelayKitVersion from './utils/getRelayKitVersion' import { createUserOperation } from './utils/userOperations' +import SafeOperationFactory from './SafeOperationFactory' const MAX_ERC20_AMOUNT_TO_APPROVE = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn @@ -61,9 +62,9 @@ const EQ_OR_GT_1_4_1 = '>=1.4.1' */ export class Safe4337Pack extends RelayKitBasePack<{ EstimateFeeProps: EstimateFeeProps - EstimateFeeResult: EthSafeOperation + EstimateFeeResult: SafeOperationBase CreateTransactionProps: Safe4337CreateTransactionProps - CreateTransactionResult: EthSafeOperation + CreateTransactionResult: SafeOperationBase ExecuteTransactionProps: Safe4337ExecutableProps ExecuteTransactionResult: string }> { @@ -390,15 +391,15 @@ export class Safe4337Pack extends RelayKitBasePack<{ * Estimates gas for the SafeOperation. * * @param {EstimateFeeProps} props - The parameters for the gas estimation. - * @param {EthSafeOperation} props.safeOperation - The SafeOperation to estimate the gas. + * @param {SafeOperationBase} props.safeOperation - The SafeOperation to estimate the gas. * @param {IFeeEstimator} props.feeEstimator - The function to estimate the gas. - * @return {Promise} The Promise object that will be resolved into the gas estimation. + * @return {Promise} The Promise object that will be resolved into the gas estimation. */ async getEstimateFee({ safeOperation, feeEstimator = new PimlicoFeeEstimator() - }: EstimateFeeProps): Promise { + }: EstimateFeeProps): Promise { const threshold = await this.protocolKit.getThreshold() const setupEstimationData = await feeEstimator?.setupEstimation?.({ bundlerUrl: this.#BUNDLER_URL, @@ -468,12 +469,12 @@ export class Safe4337Pack extends RelayKitBasePack<{ * * @param {MetaTransactionData[]} transactions - The transactions to batch in a SafeOperation. * @param options - Optional configuration options for the transaction creation. - * @return {Promise} The Promise object will resolve a SafeOperation. + * @return {Promise} The Promise object will resolve a SafeOperation. */ async createTransaction({ transactions, options = {} - }: Safe4337CreateTransactionProps): Promise { + }: Safe4337CreateTransactionProps): Promise { const { amountToApprove, validUntil, validAfter, feeEstimator } = options const userOperation = await createUserOperation(this.protocolKit, transactions, { @@ -486,14 +487,18 @@ export class Safe4337Pack extends RelayKitBasePack<{ userOperation.callData += this.#onchainIdentifier } - const safeOperation = new EthSafeOperation(userOperation, this.protocolKit, { - chainId: this.#chainId, - moduleAddress: this.#SAFE_4337_MODULE_ADDRESS, - entryPoint: this.#ENTRYPOINT_ADDRESS, - sharedSigner: this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, - validUntil, - validAfter - }) + const safeOperation = SafeOperationFactory.createSafeOperation( + userOperation, + this.protocolKit, + { + chainId: this.#chainId, + moduleAddress: this.#SAFE_4337_MODULE_ADDRESS, + entryPoint: this.#ENTRYPOINT_ADDRESS, + sharedSigner: this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, + validUntil, + validAfter + } + ) return await this.getEstimateFee({ safeOperation, @@ -502,17 +507,17 @@ export class Safe4337Pack extends RelayKitBasePack<{ } /** - * Converts a SafeOperationResponse to an EthSafeOperation. + * Converts a SafeOperationResponse to an SafeOperationBase. * - * @param {SafeOperationResponse} safeOperationResponse - The SafeOperationResponse to convert to EthSafeOperation - * @returns {EthSafeOperation} - The EthSafeOperation object + * @param {SafeOperationResponse} safeOperationResponse - The SafeOperationResponse to convert to SafeOperationBase + * @returns {SafeOperationBase} - The SafeOperationBase object */ - #toSafeOperation(safeOperationResponse: SafeOperationResponse): EthSafeOperation { + #toSafeOperation(safeOperationResponse: SafeOperationResponse): SafeOperationBase { const { validUntil, validAfter, userOperation } = safeOperationResponse const paymaster = (userOperation?.paymaster as Hex) || '0x' const paymasterData = (userOperation?.paymasterData as Hex) || '0x' - const safeOperation = new EthSafeOperation( + const safeOperation = SafeOperationFactory.createSafeOperation( { sender: userOperation?.sender || '0x', nonce: userOperation?.nonce?.toString() || '0', @@ -558,17 +563,17 @@ export class Safe4337Pack extends RelayKitBasePack<{ /** * Signs a safe operation. * - * @param {EthSafeOperation | SafeOperationResponse} safeOperation - The SafeOperation to sign. It can be: + * @param {SafeOperationBase | SafeOperationResponse} safeOperation - The SafeOperation to sign. It can be: * - A response from the API (Tx Service) - * - An instance of EthSafeOperation + * - An instance of SafeOperationBase * @param {SigningMethod} signingMethod - The signing method to use. - * @return {Promise} The Promise object will resolve to the signed SafeOperation. + * @return {Promise} The Promise object will resolve to the signed SafeOperation. */ async signSafeOperation( - safeOperation: EthSafeOperation | SafeOperationResponse, + safeOperation: SafeOperationBase | SafeOperationResponse, signingMethod: SigningMethod = SigningMethod.ETH_SIGN_TYPED_DATA_V4 - ): Promise { - let safeOp: EthSafeOperation + ): Promise { + let safeOp: SafeOperationBase if (isSafeOperationResponse(safeOperation)) { safeOp = this.#toSafeOperation(safeOperation) @@ -585,13 +590,13 @@ export class Safe4337Pack extends RelayKitBasePack<{ * Executes the relay transaction. * * @param {Safe4337ExecutableProps} props - The parameters for the transaction execution. - * @param {EthSafeOperation | SafeOperationResponse} props.executable - The SafeOperation to execute. It can be: + * @param {SafeOperationBase | SafeOperationResponse} props.executable - The SafeOperation to execute. It can be: * - A response from the API (Tx Service) - * - An instance of EthSafeOperation + * - An instance of SafeOperationBase * @return {Promise} The user operation hash. */ async executeTransaction({ executable }: Safe4337ExecutableProps): Promise { - let safeOperation: EthSafeOperation + let safeOperation: SafeOperationBase if (isSafeOperationResponse(executable)) { safeOperation = this.#toSafeOperation(executable) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts index 9ea2645dd..bad68fc25 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts @@ -1,6 +1,6 @@ import { Hex, encodePacked } from 'viem' import { EthSafeSignature } from '@safe-global/protocol-kit' -import EthSafeOperation from './SafeOperation' +import EthSafeOperation from './SafeOperationBase' import * as fixtures from './testing-utils/fixtures' describe('SafeOperation', () => { diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts similarity index 50% rename from packages/relay-kit/src/packs/safe-4337/SafeOperation.ts rename to packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts index d0835ece3..b2367aa52 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts @@ -1,23 +1,20 @@ -import { Hex, concat, encodePacked, hashTypedData, isAddress, pad, toHex } from 'viem' +import { Hex, encodePacked, hashTypedData, toHex } from 'viem' import { EstimateGasData, SafeOperation, SafeOperationOptions, SafeSignature, SafeUserOperation, - UserOperation, - UserOperationV06, - UserOperationV07 + UserOperation } from '@safe-global/types-kit' import Safe, { buildSignatureBytes, EthSafeSignature, SigningMethod } from '@safe-global/protocol-kit' -import { isEntryPointV7 } from './utils/entrypoint' import { EIP712_SAFE_OPERATION_TYPE_V06, EIP712_SAFE_OPERATION_TYPE_V07 } from './constants' -class EthSafeOperation implements SafeOperation { +abstract class SafeOperationBase implements SafeOperation { userOperation: UserOperation protocolKit: Safe options: SafeOperationOptions @@ -41,81 +38,9 @@ class EthSafeOperation implements SafeOperation { return buildSignatureBytes(Array.from(this.signatures.values())) } - addEstimations(estimations: EstimateGasData): void { - const userOpV06 = this.userOperation as UserOperationV06 - userOpV06.maxFeePerGas = BigInt(estimations.maxFeePerGas || userOpV06.maxFeePerGas) - userOpV06.maxPriorityFeePerGas = BigInt( - estimations.maxPriorityFeePerGas || userOpV06.maxPriorityFeePerGas - ) - userOpV06.verificationGasLimit = BigInt( - estimations.verificationGasLimit || userOpV06.verificationGasLimit - ) - userOpV06.preVerificationGas = BigInt( - estimations.preVerificationGas || userOpV06.preVerificationGas - ) - userOpV06.callGasLimit = BigInt(estimations.callGasLimit || userOpV06.callGasLimit) - - userOpV06.paymasterAndData = estimations.paymasterAndData || userOpV06.paymasterAndData - - if (isEntryPointV7(this.options.entryPoint)) { - const userOp = this.userOperation as UserOperationV07 - userOp.paymasterPostOpGasLimit = estimations.paymasterPostOpGasLimit - ? BigInt(estimations.paymasterPostOpGasLimit) - : userOp.paymasterPostOpGasLimit - userOp.paymasterVerificationGasLimit = estimations.paymasterVerificationGasLimit - ? BigInt(estimations.paymasterVerificationGasLimit) - : userOp.paymasterVerificationGasLimit - userOp.paymaster = estimations.paymaster || userOp.paymaster - userOp.paymasterData = estimations.paymasterData || userOp.paymasterData - } - } - - getSafeOperation(): SafeUserOperation { - let initCode - let paymasterAndData - - if (isEntryPointV7(this.options.entryPoint)) { - const userOpV07 = this.userOperation as UserOperationV07 - - initCode = userOpV07.factory - ? concat([userOpV07.factory as Hex, (userOpV07.factoryData as Hex) || ('0x' as Hex)]) - : '0x' - - paymasterAndData = isAddress(userOpV07.paymaster || '') - ? concat([ - userOpV07.paymaster as Hex, - pad(toHex(userOpV07.paymasterVerificationGasLimit || 0n), { - size: 16 - }), - pad(toHex(userOpV07.paymasterPostOpGasLimit || 0n), { - size: 16 - }), - (userOpV07.paymasterData as Hex) || ('0x' as Hex) - ]) - : '0x' - } else { - const userOpV06 = this.userOperation as UserOperationV06 - - initCode = userOpV06.initCode - paymasterAndData = userOpV06.paymasterAndData - } + abstract addEstimations(estimations: EstimateGasData): void - return { - safe: this.userOperation.sender, - nonce: BigInt(this.userOperation.nonce), - initCode, - callData: this.userOperation.callData, - callGasLimit: this.userOperation.callGasLimit, - verificationGasLimit: this.userOperation.verificationGasLimit, - preVerificationGas: this.userOperation.preVerificationGas, - maxFeePerGas: this.userOperation.maxFeePerGas, - maxPriorityFeePerGas: this.userOperation.maxPriorityFeePerGas, - paymasterAndData, - validAfter: this.options.validAfter || 0, - validUntil: this.options.validUntil || 0, - entryPoint: this.options.entryPoint - } - } + abstract getSafeOperation(): SafeUserOperation async sign(signingMethod: SigningMethod = SigningMethod.ETH_SIGN_TYPED_DATA_V4) { const safeProvider = this.protocolKit.getSafeProvider() @@ -138,15 +63,9 @@ class EthSafeOperation implements SafeOperation { if (isPasskeySigner) { const safeOpHash = this.getHash() - // if the Safe is not deployed we force the Shared Signer signature if (!isSafeDeployed) { const passkeySignature = await this.protocolKit.signHash(safeOpHash) - // SafeWebAuthnSharedSigner signature - safeSignature = new EthSafeSignature( - this.options.sharedSigner, - passkeySignature.data, - true // passkeys are contract signatures - ) + safeSignature = new EthSafeSignature(this.options.sharedSigner, passkeySignature.data, true) } else { safeSignature = await this.protocolKit.signHash(safeOpHash) } @@ -171,9 +90,7 @@ class EthSafeOperation implements SafeOperation { chainId: Number(this.options.chainId), verifyingContract: this.options.moduleAddress }, - types: isEntryPointV7(this.options.entryPoint) - ? EIP712_SAFE_OPERATION_TYPE_V07 - : EIP712_SAFE_OPERATION_TYPE_V06, + types: this.getEIP712Type(), message: { ...safeOperation, nonce: toHex(safeOperation.nonce), @@ -216,13 +133,15 @@ class EthSafeOperation implements SafeOperation { chainId: Number(this.options.chainId), verifyingContract: this.options.moduleAddress }, - types: isEntryPointV7(this.options.entryPoint) - ? EIP712_SAFE_OPERATION_TYPE_V07 - : EIP712_SAFE_OPERATION_TYPE_V06, + types: this.getEIP712Type(), primaryType: 'SafeOp', message: this.getSafeOperation() }) } + + protected abstract getEIP712Type(): + | typeof EIP712_SAFE_OPERATION_TYPE_V06 + | typeof EIP712_SAFE_OPERATION_TYPE_V07 } -export default EthSafeOperation +export default SafeOperationBase diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts new file mode 100644 index 000000000..6c1c1c9df --- /dev/null +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts @@ -0,0 +1,27 @@ +import Safe from '@safe-global/protocol-kit' +import { + UserOperation, + UserOperationV06, + UserOperationV07, + SafeOperationOptions +} from '@safe-global/types-kit' +import SafeOperationV06 from './SafeOperationV06' +import SafeOperationV07 from './SafeOperationV07' +import SafeOperationBase from './SafeOperationBase' +import { isEntryPointV7 } from './utils/entrypoint' + +class SafeOperationFactory { + static createSafeOperation( + userOperation: UserOperation, + protocolKit: Safe, + options: SafeOperationOptions + ): SafeOperationBase { + if (isEntryPointV7(options.entryPoint)) { + return new SafeOperationV07(userOperation as UserOperationV07, protocolKit, options) + } else { + return new SafeOperationV06(userOperation as UserOperationV06, protocolKit, options) + } + } +} + +export default SafeOperationFactory diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts new file mode 100644 index 000000000..c02a9caa1 --- /dev/null +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts @@ -0,0 +1,62 @@ +import Safe from '@safe-global/protocol-kit' +import { + UserOperationV06, + EstimateGasData, + SafeUserOperation, + SafeOperationOptions +} from '@safe-global/types-kit' +import SafeOperationBase from './SafeOperationBase' +import { EIP712_SAFE_OPERATION_TYPE_V06 } from './constants' + +class SafeOperationV06 extends SafeOperationBase { + userOperation!: UserOperationV06 + + constructor(userOperation: UserOperationV06, protocolKit: Safe, options: SafeOperationOptions) { + super(userOperation, protocolKit, options) + } + + addEstimations(estimations: EstimateGasData): void { + this.userOperation.maxFeePerGas = BigInt( + estimations.maxFeePerGas || this.userOperation.maxFeePerGas + ) + this.userOperation.maxPriorityFeePerGas = BigInt( + estimations.maxPriorityFeePerGas || this.userOperation.maxPriorityFeePerGas + ) + this.userOperation.verificationGasLimit = BigInt( + estimations.verificationGasLimit || this.userOperation.verificationGasLimit + ) + this.userOperation.preVerificationGas = BigInt( + estimations.preVerificationGas || this.userOperation.preVerificationGas + ) + this.userOperation.callGasLimit = BigInt( + estimations.callGasLimit || this.userOperation.callGasLimit + ) + + this.userOperation.paymasterAndData = + estimations.paymasterAndData || this.userOperation.paymasterAndData + } + + getSafeOperation(): SafeUserOperation { + return { + safe: this.userOperation.sender, + nonce: BigInt(this.userOperation.nonce), + initCode: this.userOperation.initCode, + callData: this.userOperation.callData, + callGasLimit: this.userOperation.callGasLimit, + verificationGasLimit: this.userOperation.verificationGasLimit, + preVerificationGas: this.userOperation.preVerificationGas, + maxFeePerGas: this.userOperation.maxFeePerGas, + maxPriorityFeePerGas: this.userOperation.maxPriorityFeePerGas, + paymasterAndData: this.userOperation.paymasterAndData, + validAfter: this.options.validAfter || 0, + validUntil: this.options.validUntil || 0, + entryPoint: this.options.entryPoint + } + } + + protected getEIP712Type() { + return EIP712_SAFE_OPERATION_TYPE_V06 + } +} + +export default SafeOperationV06 diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts new file mode 100644 index 000000000..ad4b2d278 --- /dev/null +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts @@ -0,0 +1,89 @@ +import { + UserOperationV07, + EstimateGasData, + SafeUserOperation, + SafeOperationOptions +} from '@safe-global/types-kit' +import { concat, Hex, isAddress, pad, toHex } from 'viem' +import SafeOperationBase from './SafeOperationBase' +import { EIP712_SAFE_OPERATION_TYPE_V07 } from './constants' +import Safe from '@safe-global/protocol-kit' + +class SafeOperationV07 extends SafeOperationBase { + userOperation!: UserOperationV07 + + constructor(userOperation: UserOperationV07, protocolKit: Safe, options: SafeOperationOptions) { + super(userOperation, protocolKit, options) + } + + addEstimations(estimations: EstimateGasData): void { + this.userOperation.maxFeePerGas = BigInt( + estimations.maxFeePerGas || this.userOperation.maxFeePerGas + ) + this.userOperation.maxPriorityFeePerGas = BigInt( + estimations.maxPriorityFeePerGas || this.userOperation.maxPriorityFeePerGas + ) + this.userOperation.verificationGasLimit = BigInt( + estimations.verificationGasLimit || this.userOperation.verificationGasLimit + ) + this.userOperation.preVerificationGas = BigInt( + estimations.preVerificationGas || this.userOperation.preVerificationGas + ) + this.userOperation.callGasLimit = BigInt( + estimations.callGasLimit || this.userOperation.callGasLimit + ) + + this.userOperation.paymasterPostOpGasLimit = estimations.paymasterPostOpGasLimit + ? BigInt(estimations.paymasterPostOpGasLimit) + : this.userOperation.paymasterPostOpGasLimit + this.userOperation.paymasterVerificationGasLimit = estimations.paymasterVerificationGasLimit + ? BigInt(estimations.paymasterVerificationGasLimit) + : this.userOperation.paymasterVerificationGasLimit + this.userOperation.paymaster = estimations.paymaster || this.userOperation.paymaster + this.userOperation.paymasterData = estimations.paymasterData || this.userOperation.paymasterData + } + + getSafeOperation(): SafeUserOperation { + const initCode = this.userOperation.factory + ? concat([ + this.userOperation.factory as Hex, + (this.userOperation.factoryData as Hex) || ('0x' as Hex) + ]) + : '0x' + + const paymasterAndData = isAddress(this.userOperation.paymaster || '') + ? concat([ + this.userOperation.paymaster as Hex, + pad(toHex(this.userOperation.paymasterVerificationGasLimit || 0n), { + size: 16 + }), + pad(toHex(this.userOperation.paymasterPostOpGasLimit || 0n), { + size: 16 + }), + (this.userOperation.paymasterData as Hex) || ('0x' as Hex) + ]) + : '0x' + + return { + safe: this.userOperation.sender, + nonce: BigInt(this.userOperation.nonce), + initCode, + callData: this.userOperation.callData, + callGasLimit: this.userOperation.callGasLimit, + verificationGasLimit: this.userOperation.verificationGasLimit, + preVerificationGas: this.userOperation.preVerificationGas, + maxFeePerGas: this.userOperation.maxFeePerGas, + maxPriorityFeePerGas: this.userOperation.maxPriorityFeePerGas, + paymasterAndData, + validAfter: this.options.validAfter || 0, + validUntil: this.options.validUntil || 0, + entryPoint: this.options.entryPoint + } + } + + protected getEIP712Type() { + return EIP712_SAFE_OPERATION_TYPE_V07 + } +} + +export default SafeOperationV07 diff --git a/packages/relay-kit/src/packs/safe-4337/types.ts b/packages/relay-kit/src/packs/safe-4337/types.ts index 96e9ec899..a82e551bf 100644 --- a/packages/relay-kit/src/packs/safe-4337/types.ts +++ b/packages/relay-kit/src/packs/safe-4337/types.ts @@ -11,7 +11,7 @@ import { SafeVersion, UserOperation } from '@safe-global/types-kit' -import EthSafeOperation from './SafeOperation' +import SafeOperationBase from './SafeOperationBase' import { RPC_4337_CALLS } from './constants' type ExistingSafeOptions = { @@ -81,7 +81,7 @@ export type Safe4337CreateTransactionProps = { } export type Safe4337ExecutableProps = { - executable: EthSafeOperation | SafeOperationResponse + executable: SafeOperationBase | SafeOperationResponse } export type EstimateSponsoredGasData = ( @@ -159,7 +159,7 @@ export interface IFeeEstimator { } export type EstimateFeeProps = { - safeOperation: EthSafeOperation + safeOperation: SafeOperationBase feeEstimator?: IFeeEstimator } From 958316c7a91201c97e8c2cbe05a57d7819b9463e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Fri, 24 Jan 2025 16:03:27 +0100 Subject: [PATCH 18/55] Fix other kits --- packages/api-kit/src/SafeApiKit.ts | 11 +++++++---- packages/api-kit/src/utils/safeOperation.ts | 12 ++++++------ packages/protocol-kit/src/Safe.ts | 6 +++--- packages/protocol-kit/src/types/index.ts | 1 - packages/protocol-kit/src/types/signing.ts | 9 --------- .../protocol-kit/src/utils/signatures/utils.ts | 8 ++++++-- .../relay-kit/src/packs/safe-4337/Safe4337Pack.ts | 4 ++-- .../src/packs/safe-4337/SafeOperationBase.ts | 14 ++++++-------- packages/types-kit/src/types.ts | 14 ++++++++++++-- .../protocol-kit/create-execute-transaction.ts | 4 ++-- playground/protocol-kit/validate-signatures.ts | 4 ++-- 11 files changed, 46 insertions(+), 41 deletions(-) delete mode 100644 packages/protocol-kit/src/types/signing.ts diff --git a/packages/api-kit/src/SafeApiKit.ts b/packages/api-kit/src/SafeApiKit.ts index 5e6babc8a..079b3b0dc 100644 --- a/packages/api-kit/src/SafeApiKit.ts +++ b/packages/api-kit/src/SafeApiKit.ts @@ -41,7 +41,8 @@ import { SafeMultisigTransactionResponse, SafeOperation, SafeOperationConfirmationListResponse, - SafeOperationResponse + SafeOperationResponse, + UserOperationV06 } from '@safe-global/types-kit' import { TRANSACTION_SERVICE_URLS } from './utils/config' import { isEmptyData } from './utils' @@ -907,21 +908,23 @@ class SafeApiKit { const getISOString = (date: number | undefined) => !date ? null : new Date(date * 1000).toISOString() + const userOperationV06 = userOperation as UserOperationV06 + return sendRequest({ url: `${this.#txServiceBaseUrl}/v1/safes/${safeAddress}/safe-operations/`, method: HttpMethod.Post, body: { nonce: Number(userOperation.nonce), - initCode: isEmptyData(userOperation.initCode) ? null : userOperation.initCode, + initCode: isEmptyData(userOperationV06.initCode) ? null : userOperationV06.initCode, callData: userOperation.callData, callGasLimit: userOperation.callGasLimit.toString(), verificationGasLimit: userOperation.verificationGasLimit.toString(), preVerificationGas: userOperation.preVerificationGas.toString(), maxFeePerGas: userOperation.maxFeePerGas.toString(), maxPriorityFeePerGas: userOperation.maxPriorityFeePerGas.toString(), - paymasterAndData: isEmptyData(userOperation.paymasterAndData) + paymasterAndData: isEmptyData(userOperationV06.paymasterAndData) ? null - : userOperation.paymasterAndData, + : userOperationV06.paymasterAndData, entryPoint, validAfter: getISOString(options?.validAfter), validUntil: getISOString(options?.validUntil), diff --git a/packages/api-kit/src/utils/safeOperation.ts b/packages/api-kit/src/utils/safeOperation.ts index b2477cd90..b19650e4e 100644 --- a/packages/api-kit/src/utils/safeOperation.ts +++ b/packages/api-kit/src/utils/safeOperation.ts @@ -1,17 +1,17 @@ import { SafeOperation } from '@safe-global/types-kit' export const getAddSafeOperationProps = async (safeOperation: SafeOperation) => { - const userOperation = safeOperation.toUserOperation() + const userOperation = safeOperation.getUserOperation() userOperation.signature = safeOperation.encodedSignatures() // Without validity dates return { - entryPoint: safeOperation.data.entryPoint, - moduleAddress: safeOperation.moduleAddress, - safeAddress: safeOperation.data.safe, + entryPoint: safeOperation.options.entryPoint, + moduleAddress: safeOperation.options.moduleAddress, + safeAddress: userOperation.sender, userOperation, options: { - validAfter: safeOperation.data.validAfter, - validUntil: safeOperation.data.validUntil + validAfter: safeOperation.options.validAfter, + validUntil: safeOperation.options.validUntil } } } diff --git a/packages/protocol-kit/src/Safe.ts b/packages/protocol-kit/src/Safe.ts index b9482711f..3f789796d 100644 --- a/packages/protocol-kit/src/Safe.ts +++ b/packages/protocol-kit/src/Safe.ts @@ -13,7 +13,9 @@ import { Transaction, EIP712TypedData, SafeTransactionData, - CompatibilityFallbackHandlerContractType + CompatibilityFallbackHandlerContractType, + SigningMethod, + SigningMethodType } from '@safe-global/types-kit' import { encodeSetupCallData, @@ -39,8 +41,6 @@ import { RemoveOwnerTxParams, SafeConfig, SafeConfigProps, - SigningMethod, - SigningMethodType, SwapOwnerTxParams, SafeModulesPaginated, RemovePasskeyOwnerTxParams, diff --git a/packages/protocol-kit/src/types/index.ts b/packages/protocol-kit/src/types/index.ts index 67ce5f8a8..a9534739b 100644 --- a/packages/protocol-kit/src/types/index.ts +++ b/packages/protocol-kit/src/types/index.ts @@ -1,6 +1,5 @@ export * from './contracts' export * from './safeConfig' export * from './safeProvider' -export * from './signing' export * from './transactions' export * from './passkeys' diff --git a/packages/protocol-kit/src/types/signing.ts b/packages/protocol-kit/src/types/signing.ts deleted file mode 100644 index 3cb5f5792..000000000 --- a/packages/protocol-kit/src/types/signing.ts +++ /dev/null @@ -1,9 +0,0 @@ -export enum SigningMethod { - ETH_SIGN = 'eth_sign', - ETH_SIGN_TYPED_DATA = 'eth_signTypedData', - ETH_SIGN_TYPED_DATA_V3 = 'eth_signTypedData_v3', - ETH_SIGN_TYPED_DATA_V4 = 'eth_signTypedData_v4', - SAFE_SIGNATURE = 'safe_sign' -} - -export type SigningMethodType = SigningMethod | string diff --git a/packages/protocol-kit/src/utils/signatures/utils.ts b/packages/protocol-kit/src/utils/signatures/utils.ts index 230071311..ca6dc28f7 100644 --- a/packages/protocol-kit/src/utils/signatures/utils.ts +++ b/packages/protocol-kit/src/utils/signatures/utils.ts @@ -1,11 +1,15 @@ import { recoverAddress } from 'viem' import SafeProvider from '@safe-global/protocol-kit/SafeProvider' -import { SafeSignature, SafeEIP712Args, SafeTransactionData } from '@safe-global/types-kit' +import { + SafeSignature, + SafeEIP712Args, + SafeTransactionData, + SigningMethod +} from '@safe-global/types-kit' import semverSatisfies from 'semver/functions/satisfies' import { sameString } from '../address' import { EthSafeSignature } from './SafeSignature' import { getEip712MessageTypes, getEip712TxTypes } from '../eip-712' -import { SigningMethod } from '@safe-global/protocol-kit/types' import { hashTypedData } from '../eip-712' import { encodeTypedData } from '../eip-712/encode' import { asHash, asHex } from '../types' diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index cf80f1d74..50a65de4a 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -1,7 +1,6 @@ import semverSatisfies from 'semver/functions/satisfies' import Safe, { EthSafeSignature, - SigningMethod, encodeMultiSendData, getMultiSendContract, PasskeyClient, @@ -13,7 +12,8 @@ import { isSafeOperationResponse, OperationType, SafeOperationConfirmation, - SafeOperationResponse + SafeOperationResponse, + SigningMethod } from '@safe-global/types-kit' import { getSafeModuleSetupDeployment, diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts index b2367aa52..584494b26 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts @@ -5,13 +5,10 @@ import { SafeOperationOptions, SafeSignature, SafeUserOperation, - UserOperation -} from '@safe-global/types-kit' -import Safe, { - buildSignatureBytes, - EthSafeSignature, + UserOperation, SigningMethod -} from '@safe-global/protocol-kit' +} from '@safe-global/types-kit' +import Safe, { buildSignatureBytes, EthSafeSignature } from '@safe-global/protocol-kit' import { EIP712_SAFE_OPERATION_TYPE_V06, EIP712_SAFE_OPERATION_TYPE_V07 } from './constants' abstract class SafeOperationBase implements SafeOperation { @@ -42,7 +39,7 @@ abstract class SafeOperationBase implements SafeOperation { abstract getSafeOperation(): SafeUserOperation - async sign(signingMethod: SigningMethod = SigningMethod.ETH_SIGN_TYPED_DATA_V4) { + async sign(signingMethod: string = SigningMethod.ETH_SIGN_TYPED_DATA_V4) { const safeProvider = this.protocolKit.getSafeProvider() const signerAddress = await safeProvider.getSignerAddress() const isPasskeySigner = await safeProvider.isPasskeySigner() @@ -71,11 +68,12 @@ abstract class SafeOperationBase implements SafeOperation { } } else { if ( + signingMethod in [ SigningMethod.ETH_SIGN_TYPED_DATA_V4, SigningMethod.ETH_SIGN_TYPED_DATA_V3, SigningMethod.ETH_SIGN_TYPED_DATA - ].includes(signingMethod) + ] ) { const signer = await safeProvider.getExternalSigner() diff --git a/packages/types-kit/src/types.ts b/packages/types-kit/src/types.ts index dc1010036..f28fdd805 100644 --- a/packages/types-kit/src/types.ts +++ b/packages/types-kit/src/types.ts @@ -164,6 +164,16 @@ export interface EIP712TypedDataMessage { } } +export enum SigningMethod { + ETH_SIGN = 'eth_sign', + ETH_SIGN_TYPED_DATA = 'eth_signTypedData', + ETH_SIGN_TYPED_DATA_V3 = 'eth_signTypedData_v3', + ETH_SIGN_TYPED_DATA_V4 = 'eth_signTypedData_v4', + SAFE_SIGNATURE = 'safe_sign' +} + +export type SigningMethodType = SigningMethod | string + export interface TypedDataDomain { name?: string version?: string @@ -351,13 +361,13 @@ export interface SafeOperation { options: SafeOperationOptions readonly signatures: Map - sign(signingMethod: string): void getSignature(signer: string): SafeSignature | undefined addSignature(signature: SafeSignature): void encodedSignatures(): string addEstimations(estimations: EstimateGasData): void - getUserOperation(): UserOperation getSafeOperation(): SafeUserOperation + sign(signingMethod: SigningMethod): void + getUserOperation(): UserOperation getHash(): string } diff --git a/playground/protocol-kit/create-execute-transaction.ts b/playground/protocol-kit/create-execute-transaction.ts index e4a9a3b9a..42d6bf6f5 100644 --- a/playground/protocol-kit/create-execute-transaction.ts +++ b/playground/protocol-kit/create-execute-transaction.ts @@ -1,6 +1,6 @@ import * as dotenv from 'dotenv' -import Safe, { SigningMethod } from '@safe-global/protocol-kit' -import { OperationType, SafeTransactionDataPartial } from '@safe-global/types-kit' +import Safe from '@safe-global/protocol-kit' +import { OperationType, SafeTransactionDataPartial, SigningMethod } from '@safe-global/types-kit' dotenv.config() diff --git a/playground/protocol-kit/validate-signatures.ts b/playground/protocol-kit/validate-signatures.ts index 5bb0ae896..1f5a22e9c 100644 --- a/playground/protocol-kit/validate-signatures.ts +++ b/playground/protocol-kit/validate-signatures.ts @@ -1,5 +1,5 @@ -import Safe, { SigningMethod, buildContractSignature } from '@safe-global/protocol-kit' -import { hashSafeMessage } from '@safe-global/protocol-kit' +import Safe, { buildContractSignature, hashSafeMessage } from '@safe-global/protocol-kit' +import { SigningMethod } from '@safe-global/types-kit' // This file can be used to play around with the Safe Core SDK From 503a7ccedb502cf8c55d9b12a31ccb253e2db927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Mon, 27 Jan 2025 12:51:16 +0100 Subject: [PATCH 19/55] Refactor rpc schemas --- .../src/packs/safe-4337/Safe4337Pack.ts | 4 +- .../src/packs/safe-4337/constants.ts | 1 - .../src/packs/safe-4337/estimators/index.ts | 2 +- .../{ => pimlico}/PimlicoFeeEstimator.test.ts | 4 +- .../{ => pimlico}/PimlicoFeeEstimator.ts | 35 ++++++++------- .../safe-4337/estimators/pimlico/types.ts | 40 +++++++++++++++++ .../relay-kit/src/packs/safe-4337/types.ts | 43 ++++--------------- .../relay-kit/src/packs/safe-4337/utils.ts | 14 ++++-- 8 files changed, 82 insertions(+), 61 deletions(-) rename packages/relay-kit/src/packs/safe-4337/estimators/{ => pimlico}/PimlicoFeeEstimator.test.ts (94%) rename packages/relay-kit/src/packs/safe-4337/estimators/{ => pimlico}/PimlicoFeeEstimator.ts (75%) create mode 100644 packages/relay-kit/src/packs/safe-4337/estimators/pimlico/types.ts diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index 50a65de4a..a0e644a75 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -41,7 +41,7 @@ import { } from './constants' import { getDummySignature, getEip4337BundlerProvider, userOperationToHexValues } from './utils' import { entryPointToSafeModules } from './utils/entrypoint' -import { PimlicoFeeEstimator } from './estimators/PimlicoFeeEstimator' +import { PimlicoFeeEstimator } from './estimators/pimlico/PimlicoFeeEstimator' import { getRelayKitVersion } from './utils/getRelayKitVersion' import { createUserOperation } from './utils/userOperations' import SafeOperationFactory from './SafeOperationFactory' @@ -362,7 +362,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ throw new Error('No entrypoint provided or available through the bundler') } - selectedEntryPoint = supportedEntryPoints.find((entryPoint) => { + selectedEntryPoint = supportedEntryPoints.find((entryPoint: string) => { const requiredSafeModulesVersion = entryPointToSafeModules(entryPoint) return semverSatisfies(safeModulesVersion, requiredSafeModulesVersion) }) diff --git a/packages/relay-kit/src/packs/safe-4337/constants.ts b/packages/relay-kit/src/packs/safe-4337/constants.ts index cd128d27d..dd7211ecc 100644 --- a/packages/relay-kit/src/packs/safe-4337/constants.ts +++ b/packages/relay-kit/src/packs/safe-4337/constants.ts @@ -70,7 +70,6 @@ export enum RPC_4337_CALLS { GET_USER_OPERATION_RECEIPT = 'eth_getUserOperationReceipt', SUPPORTED_ENTRY_POINTS = 'eth_supportedEntryPoints', CHAIN_ID = 'eth_chainId', - SPONSOR_USER_OPERATION = 'pm_sponsorUserOperation', GET_PAYMASTER_STUB_DATA = 'pm_getPaymasterStubData', GET_PAYMASTER_DATA = 'pm_getPaymasterData' } diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/index.ts b/packages/relay-kit/src/packs/safe-4337/estimators/index.ts index ac2db8c85..bb11224a0 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/index.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/index.ts @@ -1,3 +1,3 @@ -import { PimlicoFeeEstimator } from './PimlicoFeeEstimator' +import { PimlicoFeeEstimator } from './pimlico/PimlicoFeeEstimator' export { PimlicoFeeEstimator } diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.test.ts b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts similarity index 94% rename from packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.test.ts rename to packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts index c6f263ef7..2de4ec2b4 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts @@ -1,6 +1,6 @@ import { PimlicoFeeEstimator } from './PimlicoFeeEstimator' -import * as fixtures from '../testing-utils/fixtures' -import * as constants from '../constants' +import * as fixtures from '../../testing-utils/fixtures' +import * as constants from '../../constants' jest.mock('../utils', () => ({ ...jest.requireActual('../utils'), diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.ts b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts similarity index 75% rename from packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.ts rename to packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts index ede01e8d7..e64b5415b 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/PimlicoFeeEstimator.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts @@ -6,9 +6,13 @@ import { IFeeEstimator, SponsoredPaymasterOption, UserOperationStringValues -} from '../types' -import { getEip4337BundlerProvider, userOperationToHexValues } from '../utils' -import { RPC_4337_CALLS } from '../constants' +} from '@safe-global/relay-kit/packs/safe-4337/types' +import { + getEip4337BundlerProvider, + userOperationToHexValues +} from '@safe-global/relay-kit/packs/safe-4337/utils' +import { RPC_4337_CALLS } from '@safe-global/relay-kit/packs/safe-4337/constants' +import { PIMLICO_CUSTOM_RPC_4337_CALLS, PimlicoCustomRpcSchema } from './types' export class PimlicoFeeEstimator implements IFeeEstimator { async setupEstimation({ @@ -17,13 +21,15 @@ export class PimlicoFeeEstimator implements IFeeEstimator { entryPoint, paymasterOptions }: EstimateFeeFunctionProps): Promise { - const bundlerClient = getEip4337BundlerProvider(bundlerUrl) + const bundlerClient = getEip4337BundlerProvider(bundlerUrl) const feeData = await this.#getFeeData(bundlerClient) const chainId = await bundlerClient.request({ method: 'eth_chainId' }) let paymasterStubData = {} if (paymasterOptions) { - const paymasterClient = getEip4337BundlerProvider(paymasterOptions.paymasterUrl) + const paymasterClient = getEip4337BundlerProvider( + paymasterOptions.paymasterUrl + ) const context = (paymasterOptions as ERC20PaymasterOption).paymasterTokenAddress ? { token: (paymasterOptions as ERC20PaymasterOption).paymasterTokenAddress @@ -43,14 +49,6 @@ export class PimlicoFeeEstimator implements IFeeEstimator { } } - // async adjustEstimation({ userOperation }: EstimateFeeFunctionProps): Promise { - // return { - // callGasLimit: userOperation.callGasLimit + userOperation.callGasLimit / 2n, // +50% - // verificationGasLimit: userOperation.verificationGasLimit * 4n, // +300% - // preVerificationGas: userOperation.preVerificationGas + userOperation.preVerificationGas / 20n // +5% - // } - // } - async getPaymasterEstimation({ userOperation, entryPoint, @@ -58,10 +56,11 @@ export class PimlicoFeeEstimator implements IFeeEstimator { }: EstimateFeeFunctionProps): Promise { if (!paymasterOptions) throw new Error("Paymaster options can't be empty") - const paymasterClient = getEip4337BundlerProvider(paymasterOptions.paymasterUrl) + const paymasterClient = getEip4337BundlerProvider( + paymasterOptions.paymasterUrl + ) let gasEstimate: EstimateGasData - if (paymasterOptions.isSponsored) { const params: [UserOperationStringValues, string, { sponsorshipPolicyId: string }?] = [ userOperationToHexValues(userOperation, entryPoint), @@ -73,7 +72,7 @@ export class PimlicoFeeEstimator implements IFeeEstimator { }) } gasEstimate = await paymasterClient.request({ - method: RPC_4337_CALLS.SPONSOR_USER_OPERATION, + method: PIMLICO_CUSTOM_RPC_4337_CALLS.SPONSOR_USER_OPERATION, params }) } else { @@ -93,10 +92,10 @@ export class PimlicoFeeEstimator implements IFeeEstimator { } async #getFeeData( - bundlerClient: BundlerClient + bundlerClient: BundlerClient ): Promise> { const feeData = await bundlerClient.request({ - method: 'pimlico_getUserOperationGasPrice' + method: PIMLICO_CUSTOM_RPC_4337_CALLS.GET_USEROPERATION_GAS_PRICE }) const { diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/types.ts b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/types.ts new file mode 100644 index 000000000..a420ae5d5 --- /dev/null +++ b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/types.ts @@ -0,0 +1,40 @@ +import { UserOperationStringValues } from '../../types' + +export enum PIMLICO_CUSTOM_RPC_4337_CALLS { + GET_USEROPERATION_GAS_PRICE = 'pimlico_getUserOperationGasPrice', + SPONSOR_USER_OPERATION = 'pm_sponsorUserOperation' +} + +export type PimlicoCustomRpcSchema = [ + { + Method: PIMLICO_CUSTOM_RPC_4337_CALLS.GET_USEROPERATION_GAS_PRICE + Parameters: never + ReturnType: { + slow: { maxFeePerGas: string; maxPriorityFeePerGas: string } + standard: { maxFeePerGas: string; maxPriorityFeePerGas: string } + fast: { maxFeePerGas: string; maxPriorityFeePerGas: string } + } + }, + { + Method: PIMLICO_CUSTOM_RPC_4337_CALLS.SPONSOR_USER_OPERATION + Parameters: [UserOperationStringValues, string, { sponsorshipPolicyId: string }?] + ReturnType: + | { + paymasterAndData: string + callGasLimit: string + verificationGasLimit: string + verificationGas: string + preVerificationGas: string + } + | { + paymaster: string + paymasterData: string + callGasLimit: string + verificationGasLimit: string + verificationGas: string + preVerificationGas: string + paymasterVerificationGasLimit: string + paymasterPostOpGasLimit: string + } + } +] diff --git a/packages/relay-kit/src/packs/safe-4337/types.ts b/packages/relay-kit/src/packs/safe-4337/types.ts index a82e551bf..2ebadf755 100644 --- a/packages/relay-kit/src/packs/safe-4337/types.ts +++ b/packages/relay-kit/src/packs/safe-4337/types.ts @@ -178,38 +178,7 @@ export type UserOperationStringValues = Omit< maxPriorityFeePerGas: string } -export type PimlicoCustomRpcSchema = [ - { - Method: 'pimlico_getUserOperationGasPrice' - Parameters: never - ReturnType: { - slow: { maxFeePerGas: string; maxPriorityFeePerGas: string } - standard: { maxFeePerGas: string; maxPriorityFeePerGas: string } - fast: { maxFeePerGas: string; maxPriorityFeePerGas: string } - } - }, - { - Method: RPC_4337_CALLS.SPONSOR_USER_OPERATION - Parameters: [UserOperationStringValues, string, { sponsorshipPolicyId: string }?] - ReturnType: - | { - paymasterAndData: string - callGasLimit: string - verificationGasLimit: string - verificationGas: string - preVerificationGas: string - } - | { - paymaster: string - paymasterData: string - callGasLimit: string - verificationGasLimit: string - verificationGas: string - preVerificationGas: string - paymasterVerificationGasLimit: string - paymasterPostOpGasLimit: string - } - }, +export type Safe4337RpcSchema = [ { Method: RPC_4337_CALLS.GET_PAYMASTER_STUB_DATA Parameters: [UserOperationStringValues, string, string, { token: string }?] @@ -278,9 +247,15 @@ export type PimlicoCustomRpcSchema = [ } ] -export type BundlerClient = PublicClient< +export type RpcSchemaEntry = { + Method: string + Parameters: unknown[] + ReturnType: unknown +} + +export type BundlerClient = PublicClient< Transport, Chain | undefined, Account | undefined, - [...PimlicoCustomRpcSchema, ...PublicRpcSchema] + [...PublicRpcSchema, ...Safe4337RpcSchema, ...ProviderCustomRpcSchema] > diff --git a/packages/relay-kit/src/packs/safe-4337/utils.ts b/packages/relay-kit/src/packs/safe-4337/utils.ts index 694a867f1..46cd2439b 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils.ts @@ -1,5 +1,6 @@ import { Hex, + PublicRpcSchema, createPublicClient, encodeFunctionData, encodePacked, @@ -19,7 +20,12 @@ import { buildSignatureBytes } from '@safe-global/protocol-kit' import { ABI } from './constants' -import { BundlerClient, PimlicoCustomRpcSchema, UserOperationStringValues } from './types' +import { + BundlerClient, + RpcSchemaEntry, + Safe4337RpcSchema, + UserOperationStringValues +} from './types' import { isEntryPointV7 } from './utils/entrypoint' /** @@ -28,10 +34,12 @@ import { isEntryPointV7 } from './utils/entrypoint' * @param {string} bundlerUrl The EIP-4337 bundler URL. * @return {BundlerClient} The EIP-4337 bundler provider. */ -export function getEip4337BundlerProvider(bundlerUrl: string): BundlerClient { +export function getEip4337BundlerProvider( + bundlerUrl: string +): BundlerClient { const provider = createPublicClient({ transport: http(bundlerUrl), - rpcSchema: rpcSchema<[...PimlicoCustomRpcSchema]>() + rpcSchema: rpcSchema<[...PublicRpcSchema, ...Safe4337RpcSchema, ...ProviderCustomRpcSchema]>() }) return provider From c60dd14164537ff90b7b82a93b6ddadcb4c912ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Mon, 27 Jan 2025 17:30:17 +0100 Subject: [PATCH 20/55] Some improvements --- .../src/packs/safe-4337/Safe4337Pack.ts | 4 +- .../src/packs/safe-4337/SafeOperationBase.ts | 26 ++++---- .../src/packs/safe-4337/SafeOperationV06.ts | 5 +- .../src/packs/safe-4337/SafeOperationV07.ts | 2 +- .../estimators/pimlico/PimlicoFeeEstimator.ts | 66 ++++++++++++------- .../safe-4337/estimators/pimlico/types.ts | 6 +- .../relay-kit/src/packs/safe-4337/utils.ts | 2 +- .../src/packs/safe-4337/utils/entrypoint.ts | 6 -- .../packs/safe-4337/utils/userOperations.ts | 23 +++++++ .../userop-erc20-paymaster-counterfactual.ts | 2 +- .../relay-kit/userop-erc20-paymaster.ts | 2 +- 11 files changed, 89 insertions(+), 55 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index a0e644a75..a721f54c2 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -39,7 +39,7 @@ import { DEFAULT_SAFE_MODULES_VERSION, RPC_4337_CALLS } from './constants' -import { getDummySignature, getEip4337BundlerProvider, userOperationToHexValues } from './utils' +import { getDummySignature, createBundlerClient, userOperationToHexValues } from './utils' import { entryPointToSafeModules } from './utils/entrypoint' import { PimlicoFeeEstimator } from './estimators/pimlico/PimlicoFeeEstimator' import { getRelayKitVersion } from './utils/getRelayKitVersion' @@ -141,7 +141,7 @@ export class Safe4337Pack extends RelayKitBasePack<{ } = initOptions let protocolKit: Safe - const bundlerClient = getEip4337BundlerProvider(bundlerUrl) + const bundlerClient = createBundlerClient(bundlerUrl) const chainId = await bundlerClient.request({ method: RPC_4337_CALLS.CHAIN_ID }) let safeModulesSetupAddress = customContracts?.safeModulesSetupAddress diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts index 584494b26..fa536e3e5 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts @@ -23,18 +23,6 @@ abstract class SafeOperationBase implements SafeOperation { this.options = options } - getSignature(signer: string): SafeSignature | undefined { - return this.signatures.get(signer.toLowerCase()) - } - - addSignature(signature: SafeSignature): void { - this.signatures.set(signature.signer.toLowerCase(), signature) - } - - encodedSignatures(): string { - return buildSignatureBytes(Array.from(this.signatures.values())) - } - abstract addEstimations(estimations: EstimateGasData): void abstract getSafeOperation(): SafeUserOperation @@ -111,6 +99,18 @@ abstract class SafeOperationBase implements SafeOperation { this.addSignature(safeSignature) } + getSignature(signer: string): SafeSignature | undefined { + return this.signatures.get(signer.toLowerCase()) + } + + addSignature(signature: SafeSignature): void { + this.signatures.set(signature.signer.toLowerCase(), signature) + } + + encodedSignatures(): string { + return buildSignatureBytes(Array.from(this.signatures.values())) + } + getUserOperation(): UserOperation { return { ...this.userOperation, @@ -137,7 +137,7 @@ abstract class SafeOperationBase implements SafeOperation { }) } - protected abstract getEIP712Type(): + abstract getEIP712Type(): | typeof EIP712_SAFE_OPERATION_TYPE_V06 | typeof EIP712_SAFE_OPERATION_TYPE_V07 } diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts index c02a9caa1..96f0abaab 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts @@ -16,9 +16,6 @@ class SafeOperationV06 extends SafeOperationBase { } addEstimations(estimations: EstimateGasData): void { - this.userOperation.maxFeePerGas = BigInt( - estimations.maxFeePerGas || this.userOperation.maxFeePerGas - ) this.userOperation.maxPriorityFeePerGas = BigInt( estimations.maxPriorityFeePerGas || this.userOperation.maxPriorityFeePerGas ) @@ -54,7 +51,7 @@ class SafeOperationV06 extends SafeOperationBase { } } - protected getEIP712Type() { + getEIP712Type() { return EIP712_SAFE_OPERATION_TYPE_V06 } } diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts index ad4b2d278..566626436 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts @@ -81,7 +81,7 @@ class SafeOperationV07 extends SafeOperationBase { } } - protected getEIP712Type() { + getEIP712Type() { return EIP712_SAFE_OPERATION_TYPE_V07 } } diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts index e64b5415b..86cbb93d8 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts @@ -8,12 +8,18 @@ import { UserOperationStringValues } from '@safe-global/relay-kit/packs/safe-4337/types' import { - getEip4337BundlerProvider, + createBundlerClient, userOperationToHexValues } from '@safe-global/relay-kit/packs/safe-4337/utils' import { RPC_4337_CALLS } from '@safe-global/relay-kit/packs/safe-4337/constants' import { PIMLICO_CUSTOM_RPC_4337_CALLS, PimlicoCustomRpcSchema } from './types' +/** + * PimlicoFeeEstimator is a class that implements the IFeeEstimator interface. You can implement three optional methods that will be called during the estimation process: + * - setupEstimation: Setup the userOperation before calling the eth_estimateUserOperation gas method. + * - adjustEstimation: Adjust the userOperation values returned after calling the eth_adjustUserOperation method. + * - getPaymasterEstimation: Obtain the paymaster data and the paymaster gas values. + */ export class PimlicoFeeEstimator implements IFeeEstimator { async setupEstimation({ bundlerUrl, @@ -21,13 +27,14 @@ export class PimlicoFeeEstimator implements IFeeEstimator { entryPoint, paymasterOptions }: EstimateFeeFunctionProps): Promise { - const bundlerClient = getEip4337BundlerProvider(bundlerUrl) - const feeData = await this.#getFeeData(bundlerClient) - const chainId = await bundlerClient.request({ method: 'eth_chainId' }) + const bundlerClient = createBundlerClient(bundlerUrl) + const feeData = await this.#getUserOperationGasPrices(bundlerClient) + const chainId = await this.#getChainId(bundlerClient) let paymasterStubData = {} + if (paymasterOptions) { - const paymasterClient = getEip4337BundlerProvider( + const paymasterClient = createBundlerClient( paymasterOptions.paymasterUrl ) const context = (paymasterOptions as ERC20PaymasterOption).paymasterTokenAddress @@ -54,48 +61,55 @@ export class PimlicoFeeEstimator implements IFeeEstimator { entryPoint, paymasterOptions }: EstimateFeeFunctionProps): Promise { - if (!paymasterOptions) throw new Error("Paymaster options can't be empty") + if (!paymasterOptions) + throw new Error( + "Paymaster options can't be empty when trying to get the paymaster data and gas estimation" + ) - const paymasterClient = getEip4337BundlerProvider( + const paymasterClient = createBundlerClient( paymasterOptions.paymasterUrl ) - let gasEstimate: EstimateGasData if (paymasterOptions.isSponsored) { const params: [UserOperationStringValues, string, { sponsorshipPolicyId: string }?] = [ userOperationToHexValues(userOperation, entryPoint), entryPoint ] + if (paymasterOptions.sponsorshipPolicyId) { params.push({ sponsorshipPolicyId: paymasterOptions.sponsorshipPolicyId }) } - gasEstimate = await paymasterClient.request({ + + const sponsoredData = await paymasterClient.request({ method: PIMLICO_CUSTOM_RPC_4337_CALLS.SPONSOR_USER_OPERATION, params }) - } else { - const chainId = await paymasterClient.request({ method: 'eth_chainId' }) - gasEstimate = await paymasterClient.request({ - method: RPC_4337_CALLS.GET_PAYMASTER_DATA, - params: [ - userOperationToHexValues(userOperation, entryPoint), - entryPoint, - chainId, - { token: paymasterOptions.paymasterTokenAddress } - ] - }) + + return sponsoredData } - return gasEstimate + const chainId = await this.#getChainId(paymasterClient) + + const erc20PaymasterData = await paymasterClient.request({ + method: RPC_4337_CALLS.GET_PAYMASTER_DATA, + params: [ + userOperationToHexValues(userOperation, entryPoint), + entryPoint, + chainId, + { token: paymasterOptions.paymasterTokenAddress } + ] + }) + + return erc20PaymasterData } - async #getFeeData( + async #getUserOperationGasPrices( bundlerClient: BundlerClient ): Promise> { const feeData = await bundlerClient.request({ - method: PIMLICO_CUSTOM_RPC_4337_CALLS.GET_USEROPERATION_GAS_PRICE + method: PIMLICO_CUSTOM_RPC_4337_CALLS.GET_USER_OPERATION_GAS_PRICE }) const { @@ -107,4 +121,10 @@ export class PimlicoFeeEstimator implements IFeeEstimator { maxPriorityFeePerGas: maxPriorityFeePerGas } } + + async #getChainId(client: BundlerClient): Promise { + const chainId = await client.request({ method: 'eth_chainId' }) + + return chainId + } } diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/types.ts b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/types.ts index a420ae5d5..11b5a3a78 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/types.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/types.ts @@ -1,13 +1,13 @@ -import { UserOperationStringValues } from '../../types' +import { UserOperationStringValues } from '@safe-global/relay-kit/packs/safe-4337/types' export enum PIMLICO_CUSTOM_RPC_4337_CALLS { - GET_USEROPERATION_GAS_PRICE = 'pimlico_getUserOperationGasPrice', + GET_USER_OPERATION_GAS_PRICE = 'pimlico_getUserOperationGasPrice', SPONSOR_USER_OPERATION = 'pm_sponsorUserOperation' } export type PimlicoCustomRpcSchema = [ { - Method: PIMLICO_CUSTOM_RPC_4337_CALLS.GET_USEROPERATION_GAS_PRICE + Method: PIMLICO_CUSTOM_RPC_4337_CALLS.GET_USER_OPERATION_GAS_PRICE Parameters: never ReturnType: { slow: { maxFeePerGas: string; maxPriorityFeePerGas: string } diff --git a/packages/relay-kit/src/packs/safe-4337/utils.ts b/packages/relay-kit/src/packs/safe-4337/utils.ts index 46cd2439b..bc2bee9d5 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils.ts @@ -34,7 +34,7 @@ import { isEntryPointV7 } from './utils/entrypoint' * @param {string} bundlerUrl The EIP-4337 bundler URL. * @return {BundlerClient} The EIP-4337 bundler provider. */ -export function getEip4337BundlerProvider( +export function createBundlerClient( bundlerUrl: string ): BundlerClient { const provider = createPublicClient({ diff --git a/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts b/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts index 52824992f..13f9be0da 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts @@ -26,12 +26,6 @@ export function isEntryPointV7(address: string): boolean { return sameString(address, ENTRYPOINT_ADDRESS_V07) } -/** - * Gets account nonce from the bundler. - * - * @param {string} safeAddress - Account address for which the nonce is to be fetched. - * @returns {Promise} The Promise object will resolve to the account nonce. - */ export async function getSafeNonceFromEntrypoint( protocolKit: Safe, safeAddress: string, diff --git a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts index 1bbec3742..c778db224 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts @@ -30,6 +30,14 @@ function encodeExecuteUserOpCallData(transaction: MetaTransactionData): string { }) } +/** + * + * @param {Safe} protocolKit - The Safe instance + * @param {MetaTransactionData[]} transactions - The transactions to batch + * @param {ERC20PaymasterOption} paymasterOptions - The options for the paymaster + * @param {bigint} amountToApprove - The amount to approve. Useful for ERC20 paymasters to include an approve transaction for the ERC20 token ruling the paymaster + * @returns {string} - An hexadecimal string with the call data + */ async function getCallData( protocolKit: Safe, transactions: MetaTransactionData[], @@ -66,6 +74,11 @@ async function getCallData( return callData } +/** + * Unpack initCode into factory and factoryData fields for an V07 UserOperation + * @param {string} initCode - The initializer code for the Safe deployment + * @returns {Pick} The factory and factoryData fields for an V07 UserOperation + */ function unpackInitCode(initCode: string): Pick { const initCodeBytes = hexToBytes(initCode as Hex) @@ -77,6 +90,16 @@ function unpackInitCode(initCode: string): Pick} The initialized UserOperation + */ export async function createUserOperation( protocolKit: Safe, transactions: MetaTransactionData[], diff --git a/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts b/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts index 56551d6a9..a0b131899 100644 --- a/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts +++ b/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts @@ -20,7 +20,7 @@ const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLIC // Paymaster addresses const paymasterAddress_v07 = '0x0000000000000039cd5e8ae05257ce51c473ddd1' -const paymasterAddress_v06 = '0x00000000000000fb866daaa79352cc568a005d96' +// const paymasterAddress_v06 = '0x00000000000000fb866daaa79352cc568a005d96' // Use this with the 0.2.0 safeModulesVersion that is currently compatible with the v0.6 entrypoint // PIM test token contract address // faucet: https://dashboard.pimlico.io/test-erc20-faucet diff --git a/playground/relay-kit/userop-erc20-paymaster.ts b/playground/relay-kit/userop-erc20-paymaster.ts index a8e13af8b..ce38cd897 100644 --- a/playground/relay-kit/userop-erc20-paymaster.ts +++ b/playground/relay-kit/userop-erc20-paymaster.ts @@ -21,7 +21,7 @@ const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLIC // PAYMASTER ADDRESSES const paymasterAddress_v07 = '0x0000000000000039cd5e8ae05257ce51c473ddd1' -const paymasterAddress_v06 = '0x00000000000000fb866daaa79352cc568a005d96' +// const paymasterAddress_v06 = '0x00000000000000fb866daaa79352cc568a005d96' // Use this with the 0.2.0 safeModulesVersion that is currently compatible with the v0.6 entrypoint // PIM test token contract address // faucet: https://dashboard.pimlico.io/test-erc20-faucet From 38c85fbddfb571d2e7afc5bbc4496bed62655a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Mon, 27 Jan 2025 18:08:30 +0100 Subject: [PATCH 21/55] Improve playgrounds --- playground/relay-kit/.env-sample | 7 ++++ playground/relay-kit/userop-counterfactual.ts | 27 ++++++-------- .../userop-erc20-paymaster-counterfactual.ts | 26 ++++++-------- .../relay-kit/userop-erc20-paymaster.ts | 27 ++++++-------- ...erop-verifying-paymaster-counterfactual.ts | 36 +++++++------------ .../relay-kit/userop-verifying-paymaster.ts | 34 +++++++----------- playground/relay-kit/userop.ts | 25 ++++++------- 7 files changed, 72 insertions(+), 110 deletions(-) create mode 100644 playground/relay-kit/.env-sample diff --git a/playground/relay-kit/.env-sample b/playground/relay-kit/.env-sample new file mode 100644 index 000000000..7dcd4f137 --- /dev/null +++ b/playground/relay-kit/.env-sample @@ -0,0 +1,7 @@ +PRIVATE_KEY= +OWNER_ADDRESS=0x... +RPC_URL=https://ethereum-sepolia-rpc.publicnode.com +SAFE_ADDRESS=0x... +CHAIN_ID=11155111 +BUNDLER_URL= +PAYMASTER_URL= diff --git a/playground/relay-kit/userop-counterfactual.ts b/playground/relay-kit/userop-counterfactual.ts index c875038fc..f60372880 100644 --- a/playground/relay-kit/userop-counterfactual.ts +++ b/playground/relay-kit/userop-counterfactual.ts @@ -1,24 +1,17 @@ +import * as dotenv from 'dotenv' import { Safe4337Pack } from '@safe-global/relay-kit' import { parseEther } from 'viem' import { waitForOperationToFinish, setup4337Playground } from '../utils' -// Safe owner PK -const PRIVATE_KEY = '' +dotenv.config({ path: './playground/relay-kit/.env' }) -// Pimlico API key -const PIMLICO_API_KEY = '' - -// Safe owner address -const OWNER_ADDRESS = '' - -// RPC URL -const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // SEPOLIA - -// CHAIN -const CHAIN_NAME = '11155111' - -// Bundler URL -const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO +const { + PRIVATE_KEY, + OWNER_ADDRESS = '0x', + RPC_URL = '', + CHAIN_ID = '', + BUNDLER_URL = '' +} = process.env // PIM test token contract address // faucet: https://dashboard.pimlico.io/test-erc20-faucet @@ -63,7 +56,7 @@ async function main() { executable: signedSafeOperation }) - await waitForOperationToFinish(userOperationHash, CHAIN_NAME, safe4337Pack) + await waitForOperationToFinish(userOperationHash, CHAIN_ID, safe4337Pack) } main() diff --git a/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts b/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts index a0b131899..5ec5421ea 100644 --- a/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts +++ b/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts @@ -1,22 +1,16 @@ +import * as dotenv from 'dotenv' import { Safe4337Pack } from '@safe-global/relay-kit' import { waitForOperationToFinish, setup4337Playground } from '../utils' -// Safe owner PK -const PRIVATE_KEY = '' +dotenv.config({ path: './playground/relay-kit/.env' }) -const PIMLICO_API_KEY = '' - -// Safe owner address -const OWNER_ADDRESS = '' - -// CHAIN -const CHAIN_NAME = '11155111' - -// RPC URL -const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // SEPOLIA - -// Bundler URL -const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO +const { + PRIVATE_KEY, + OWNER_ADDRESS = '0x', + RPC_URL = '', + CHAIN_ID = '', + BUNDLER_URL = '' +} = process.env // Paymaster addresses const paymasterAddress_v07 = '0x0000000000000039cd5e8ae05257ce51c473ddd1' @@ -71,7 +65,7 @@ async function main() { executable: signedSafeOperation }) - await waitForOperationToFinish(userOperationHash, CHAIN_NAME, safe4337Pack) + await waitForOperationToFinish(userOperationHash, CHAIN_ID, safe4337Pack) } main() diff --git a/playground/relay-kit/userop-erc20-paymaster.ts b/playground/relay-kit/userop-erc20-paymaster.ts index ce38cd897..9f1efeffe 100644 --- a/playground/relay-kit/userop-erc20-paymaster.ts +++ b/playground/relay-kit/userop-erc20-paymaster.ts @@ -1,23 +1,16 @@ +import * as dotenv from 'dotenv' import { Safe4337Pack } from '@safe-global/relay-kit' import { waitForOperationToFinish, setup4337Playground } from '../utils' -// Safe owner PK -const PRIVATE_KEY = '' +dotenv.config({ path: './playground/relay-kit/.env' }) -const PIMLICO_API_KEY = '' - -// Safe 4337 compatible -const SAFE_ADDRESS = '' - -// CHAIN -const CHAIN_NAME = '11155111' - -// RPC URL -const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // SEPOLIA -// const RPC_URL = 'https://rpc.gnosischain.com/' // GNOSIS - -// Bundler URL -const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO +const { + PRIVATE_KEY, + SAFE_ADDRESS = '0x', + RPC_URL = '', + CHAIN_ID = '', + BUNDLER_URL = '' +} = process.env // PAYMASTER ADDRESSES const paymasterAddress_v07 = '0x0000000000000039cd5e8ae05257ce51c473ddd1' @@ -69,7 +62,7 @@ async function main() { executable: signedSafeOperation }) - await waitForOperationToFinish(userOperationHash, CHAIN_NAME, safe4337Pack) + await waitForOperationToFinish(userOperationHash, CHAIN_ID, safe4337Pack) } main() diff --git a/playground/relay-kit/userop-verifying-paymaster-counterfactual.ts b/playground/relay-kit/userop-verifying-paymaster-counterfactual.ts index fd260c234..a5fff3bb2 100644 --- a/playground/relay-kit/userop-verifying-paymaster-counterfactual.ts +++ b/playground/relay-kit/userop-verifying-paymaster-counterfactual.ts @@ -1,28 +1,18 @@ +import * as dotenv from 'dotenv' import { Safe4337Pack } from '@safe-global/relay-kit' import { setup4337Playground, waitForOperationToFinish } from '../utils' -// Safe owner PK -const PRIVATE_KEY = '' +dotenv.config({ path: './playground/relay-kit/.env' }) -const PIMLICO_API_KEY = '' - -// Safe owner address -const OWNER_ADDRESS = '' - -// PolicyId is an optional parameter, you can create one here: https://dashboard.pimlico.io/sponsorship-policies -const POLICY_ID = '' - -// CHAIN -const CHAIN_NAME = '11155111' - -// RPC URL -const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // SEPOLIA - -// Bundler URL -const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO - -// Paymaster URL -const PAYMASTER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO +const { + PRIVATE_KEY, + OWNER_ADDRESS = '0x', + RPC_URL = '', + CHAIN_ID = '', + BUNDLER_URL = '', + PAYMASTER_URL = '', + POLICY_ID +} = process.env // PIM test token contract address // faucet: https://dashboard.pimlico.io/test-erc20-faucet @@ -42,7 +32,7 @@ async function main() { options: { owners: [OWNER_ADDRESS], threshold: 1, - saltNonce: '4337' + '1' + saltNonce: '4337' + '112' } }) @@ -71,7 +61,7 @@ async function main() { executable: signedSafeOperation }) - await waitForOperationToFinish(userOperationHash, CHAIN_NAME, safe4337Pack) + await waitForOperationToFinish(userOperationHash, CHAIN_ID, safe4337Pack) } main() diff --git a/playground/relay-kit/userop-verifying-paymaster.ts b/playground/relay-kit/userop-verifying-paymaster.ts index 9eae591c2..f6fa93242 100644 --- a/playground/relay-kit/userop-verifying-paymaster.ts +++ b/playground/relay-kit/userop-verifying-paymaster.ts @@ -1,28 +1,18 @@ +import * as dotenv from 'dotenv' import { Safe4337Pack } from '@safe-global/relay-kit' import { setup4337Playground, waitForOperationToFinish } from '../utils' -// Safe owner PK -const PRIVATE_KEY = '' +dotenv.config({ path: './playground/relay-kit/.env' }) -const PIMLICO_API_KEY = '' - -// Safe 4337 compatible -const SAFE_ADDRESS = '' - -// PolicyId is an optional parameter, you can create one here: https://dashboard.pimlico.io/sponsorship-policies -const POLICY_ID = '' - -// CHAIN -const CHAIN_NAME = '11155111' - -// RPC URL -const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // SEPOLIA - -// Bundler URL -const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO - -// Paymaster URL -const PAYMASTER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO +const { + PRIVATE_KEY, + SAFE_ADDRESS = '0x', + RPC_URL = '', + CHAIN_ID = '', + BUNDLER_URL = '', + PAYMASTER_URL = '', + POLICY_ID +} = process.env // PIM test token contract address // faucet: https://dashboard.pimlico.io/test-erc20-faucet @@ -69,7 +59,7 @@ async function main() { executable: signedSafeOperation }) - await waitForOperationToFinish(userOperationHash, CHAIN_NAME, safe4337Pack) + await waitForOperationToFinish(userOperationHash, CHAIN_ID, safe4337Pack) } main() diff --git a/playground/relay-kit/userop.ts b/playground/relay-kit/userop.ts index 1cac4b6dd..e36a010c3 100644 --- a/playground/relay-kit/userop.ts +++ b/playground/relay-kit/userop.ts @@ -1,21 +1,16 @@ +import dotenv from 'dotenv' import { Safe4337Pack } from '@safe-global/relay-kit' import { setup4337Playground, waitForOperationToFinish } from '../utils' -// Safe owner PK -const PRIVATE_KEY = '' +dotenv.config({ path: './playground/relay-kit/.env' }) -const PIMLICO_API_KEY = '' - -// Safe 4337 compatible -const SAFE_ADDRESS = '' - -// RPC URL -const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // SEPOLIA - -const CHAIN_NAME = '11155111' - -// Bundler URL -const BUNDLER_URL = `https://api.pimlico.io/v2/sepolia/rpc?apikey=${PIMLICO_API_KEY}` // PIMLICO +const { + PRIVATE_KEY, + SAFE_ADDRESS = '0x', + RPC_URL = '', + CHAIN_ID = '', + BUNDLER_URL = '' +} = process.env // PIM test token contract address // faucet: https://dashboard.pimlico.io/test-erc20-faucet @@ -58,7 +53,7 @@ async function main() { executable: signedSafeOperation }) - await waitForOperationToFinish(userOperationHash, CHAIN_NAME, safe4337Pack) + await waitForOperationToFinish(userOperationHash, CHAIN_ID, safe4337Pack) } main() From e1fa4b4a83806c9e7c06330cc083f0aa2fd1d2b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Tue, 28 Jan 2025 11:26:47 +0100 Subject: [PATCH 22/55] Fix SafeOperationV06 --- packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts index 96f0abaab..4e118f336 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts @@ -16,6 +16,9 @@ class SafeOperationV06 extends SafeOperationBase { } addEstimations(estimations: EstimateGasData): void { + this.userOperation.maxFeePerGas = BigInt( + estimations.maxFeePerGas || this.userOperation.maxFeePerGas + ) this.userOperation.maxPriorityFeePerGas = BigInt( estimations.maxPriorityFeePerGas || this.userOperation.maxPriorityFeePerGas ) @@ -28,7 +31,6 @@ class SafeOperationV06 extends SafeOperationBase { this.userOperation.callGasLimit = BigInt( estimations.callGasLimit || this.userOperation.callGasLimit ) - this.userOperation.paymasterAndData = estimations.paymasterAndData || this.userOperation.paymasterAndData } From 0a4ba1591b56c3da9d72c6647336f24510f1ebb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Tue, 28 Jan 2025 11:31:15 +0100 Subject: [PATCH 23/55] Update playgrounds --- playground/relay-kit/.env-sample | 8 +++++-- playground/relay-kit/userop-counterfactual.ts | 18 +++++++--------- .../userop-erc20-paymaster-counterfactual.ts | 21 ++++++++----------- .../relay-kit/userop-erc20-paymaster.ts | 6 +++--- ...erop-verifying-paymaster-counterfactual.ts | 9 +++++--- .../relay-kit/userop-verifying-paymaster.ts | 1 + playground/relay-kit/userop.ts | 4 +++- playground/utils.ts | 5 +++-- 8 files changed, 39 insertions(+), 33 deletions(-) diff --git a/playground/relay-kit/.env-sample b/playground/relay-kit/.env-sample index 7dcd4f137..ee89742b4 100644 --- a/playground/relay-kit/.env-sample +++ b/playground/relay-kit/.env-sample @@ -1,7 +1,11 @@ +# Private key of the account used to transfer funds. DEpending on the playground this account should have ETH and/or Test Token +# PIM token can be minted on the Pimlico Dashboard (https://dashboard.pimlico.io/test-erc20-faucet) and can be used for the test transfers and the ERC-20 Paymaster playgrounds +# The derived address will be the Safe owner when using the counterfactual deployment PRIVATE_KEY= -OWNER_ADDRESS=0x... -RPC_URL=https://ethereum-sepolia-rpc.publicnode.com +# Safe address when using the playgrounds where Safe already exists SAFE_ADDRESS=0x... +RPC_URL=https://ethereum-sepolia-rpc.publicnode.com CHAIN_ID=11155111 +# You can get Bundler and Paymaster URL's from your provider's dashboard BUNDLER_URL= PAYMASTER_URL= diff --git a/playground/relay-kit/userop-counterfactual.ts b/playground/relay-kit/userop-counterfactual.ts index f60372880..9c0f50821 100644 --- a/playground/relay-kit/userop-counterfactual.ts +++ b/playground/relay-kit/userop-counterfactual.ts @@ -2,16 +2,11 @@ import * as dotenv from 'dotenv' import { Safe4337Pack } from '@safe-global/relay-kit' import { parseEther } from 'viem' import { waitForOperationToFinish, setup4337Playground } from '../utils' +import { privateKeyToAccount } from 'viem/accounts' dotenv.config({ path: './playground/relay-kit/.env' }) -const { - PRIVATE_KEY, - OWNER_ADDRESS = '0x', - RPC_URL = '', - CHAIN_ID = '', - BUNDLER_URL = '' -} = process.env +const { PRIVATE_KEY, RPC_URL = '', CHAIN_ID = '', BUNDLER_URL = '' } = process.env // PIM test token contract address // faucet: https://dashboard.pimlico.io/test-erc20-faucet @@ -19,20 +14,23 @@ const pimlicoTokenAddress = '0xFC3e86566895Fb007c6A0d3809eb2827DF94F751' async function main() { // 1) Initialize pack + const account = privateKeyToAccount(`0x${PRIVATE_KEY}`) + const safe4337Pack = await Safe4337Pack.init({ provider: RPC_URL, signer: PRIVATE_KEY, bundlerUrl: BUNDLER_URL, + safeModulesVersion: '0.3.0', // Blank or 0.3.0 for Entrypoint v0.7, 0.2.0 for Entrypoint v0.6 options: { - owners: [OWNER_ADDRESS], + owners: [account.address], threshold: 1, - saltNonce: '4337' + '100' + saltNonce: '4337' + '1' } }) // 2) Setup Playground const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { - nativeTokenAmount: parseEther('0.1'), + nativeTokenAmount: parseEther('0.05'), // Increase this value when is not enough to cover the gas fees erc20TokenAmount: 200_000n, erc20TokenContractAddress: pimlicoTokenAddress }) diff --git a/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts b/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts index 5ec5421ea..80a74530e 100644 --- a/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts +++ b/playground/relay-kit/userop-erc20-paymaster-counterfactual.ts @@ -1,20 +1,15 @@ import * as dotenv from 'dotenv' import { Safe4337Pack } from '@safe-global/relay-kit' import { waitForOperationToFinish, setup4337Playground } from '../utils' +import { privateKeyToAccount } from 'viem/accounts' dotenv.config({ path: './playground/relay-kit/.env' }) -const { - PRIVATE_KEY, - OWNER_ADDRESS = '0x', - RPC_URL = '', - CHAIN_ID = '', - BUNDLER_URL = '' -} = process.env +const { PRIVATE_KEY, RPC_URL = '', CHAIN_ID = '', BUNDLER_URL = '' } = process.env // Paymaster addresses const paymasterAddress_v07 = '0x0000000000000039cd5e8ae05257ce51c473ddd1' -// const paymasterAddress_v06 = '0x00000000000000fb866daaa79352cc568a005d96' // Use this with the 0.2.0 safeModulesVersion that is currently compatible with the v0.6 entrypoint +const paymasterAddress_v06 = '0x00000000000000fb866daaa79352cc568a005d96' // Use this with the 0.2.0 safeModulesVersion that is currently compatible with the v0.6 entrypoint // PIM test token contract address // faucet: https://dashboard.pimlico.io/test-erc20-faucet @@ -22,11 +17,13 @@ const pimlicoTokenAddress = '0xFC3e86566895Fb007c6A0d3809eb2827DF94F751' async function main() { // 1) Initialize pack with the paymaster data + const account = privateKeyToAccount(`0x${PRIVATE_KEY}`) + const safe4337Pack = await Safe4337Pack.init({ provider: RPC_URL, signer: PRIVATE_KEY, bundlerUrl: BUNDLER_URL, - safeModulesVersion: '0.3.0', + safeModulesVersion: '0.3.0', // Blank or 0.3.0 for Entrypoint v0.7, 0.2.0 for Entrypoint v0.6 paymasterOptions: { paymasterUrl: BUNDLER_URL, paymasterTokenAddress: pimlicoTokenAddress, @@ -34,15 +31,15 @@ async function main() { amountToApprove: 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn }, options: { - owners: [OWNER_ADDRESS], + owners: [account.address], threshold: 1, - saltNonce: '4337' + '112321' // to update the address + saltNonce: '4337' + '1' // to update the address } }) // 2) Setup Playground const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { - erc20TokenAmount: 200_000n, + erc20TokenAmount: 200_000_000n, erc20TokenContractAddress: pimlicoTokenAddress }) diff --git a/playground/relay-kit/userop-erc20-paymaster.ts b/playground/relay-kit/userop-erc20-paymaster.ts index 9f1efeffe..2f418bba2 100644 --- a/playground/relay-kit/userop-erc20-paymaster.ts +++ b/playground/relay-kit/userop-erc20-paymaster.ts @@ -14,7 +14,7 @@ const { // PAYMASTER ADDRESSES const paymasterAddress_v07 = '0x0000000000000039cd5e8ae05257ce51c473ddd1' -// const paymasterAddress_v06 = '0x00000000000000fb866daaa79352cc568a005d96' // Use this with the 0.2.0 safeModulesVersion that is currently compatible with the v0.6 entrypoint +const paymasterAddress_v06 = '0x00000000000000fb866daaa79352cc568a005d96' // Use this with the 0.2.0 safeModulesVersion that is currently compatible with the v0.6 entrypoint // PIM test token contract address // faucet: https://dashboard.pimlico.io/test-erc20-faucet @@ -26,7 +26,7 @@ async function main() { provider: RPC_URL, signer: PRIVATE_KEY, bundlerUrl: BUNDLER_URL, - safeModulesVersion: '0.3.0', + safeModulesVersion: '0.3.0', // Blank or 0.3.0 for Entrypoint v0.7, 0.2.0 for Entrypoint v0.6 paymasterOptions: { paymasterUrl: BUNDLER_URL, paymasterTokenAddress: pimlicoTokenAddress, @@ -39,7 +39,7 @@ async function main() { // 2) Setup Playground const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { - erc20TokenAmount: 200_000n, + // erc20TokenAmount: 100_000_000n, erc20TokenContractAddress: pimlicoTokenAddress }) diff --git a/playground/relay-kit/userop-verifying-paymaster-counterfactual.ts b/playground/relay-kit/userop-verifying-paymaster-counterfactual.ts index a5fff3bb2..fb80ec1d2 100644 --- a/playground/relay-kit/userop-verifying-paymaster-counterfactual.ts +++ b/playground/relay-kit/userop-verifying-paymaster-counterfactual.ts @@ -1,12 +1,12 @@ import * as dotenv from 'dotenv' import { Safe4337Pack } from '@safe-global/relay-kit' import { setup4337Playground, waitForOperationToFinish } from '../utils' +import { privateKeyToAccount } from 'viem/accounts' dotenv.config({ path: './playground/relay-kit/.env' }) const { PRIVATE_KEY, - OWNER_ADDRESS = '0x', RPC_URL = '', CHAIN_ID = '', BUNDLER_URL = '', @@ -20,19 +20,22 @@ const pimlicoTokenAddress = '0xFC3e86566895Fb007c6A0d3809eb2827DF94F751' async function main() { // 1) Initialize pack with the paymaster data + const account = privateKeyToAccount(`0x${PRIVATE_KEY}`) + const safe4337Pack = await Safe4337Pack.init({ provider: RPC_URL, signer: PRIVATE_KEY, bundlerUrl: BUNDLER_URL, + safeModulesVersion: '0.3.0', // Blank or 0.3.0 for Entrypoint v0.7, 0.2.0 for Entrypoint v0.6 paymasterOptions: { isSponsored: true, sponsorshipPolicyId: POLICY_ID, paymasterUrl: PAYMASTER_URL }, options: { - owners: [OWNER_ADDRESS], + owners: [account.address], threshold: 1, - saltNonce: '4337' + '112' + saltNonce: '4337' + '1' } }) diff --git a/playground/relay-kit/userop-verifying-paymaster.ts b/playground/relay-kit/userop-verifying-paymaster.ts index f6fa93242..abdab7b59 100644 --- a/playground/relay-kit/userop-verifying-paymaster.ts +++ b/playground/relay-kit/userop-verifying-paymaster.ts @@ -23,6 +23,7 @@ async function main() { const safe4337Pack = await Safe4337Pack.init({ provider: RPC_URL, signer: PRIVATE_KEY, + safeModulesVersion: '0.3.0', // Blank or 0.3.0 for Entrypoint v0.7, 0.2.0 for Entrypoint v0.6 bundlerUrl: BUNDLER_URL, paymasterOptions: { isSponsored: true, diff --git a/playground/relay-kit/userop.ts b/playground/relay-kit/userop.ts index e36a010c3..785a3825a 100644 --- a/playground/relay-kit/userop.ts +++ b/playground/relay-kit/userop.ts @@ -1,4 +1,5 @@ import dotenv from 'dotenv' +import { parseEther } from 'viem' import { Safe4337Pack } from '@safe-global/relay-kit' import { setup4337Playground, waitForOperationToFinish } from '../utils' @@ -21,6 +22,7 @@ async function main() { const safe4337Pack = await Safe4337Pack.init({ provider: RPC_URL, signer: PRIVATE_KEY, + safeModulesVersion: '0.3.0', // Blank or 0.3.0 for Entrypoint v0.7, 0.2.0 for Entrypoint v0.6 bundlerUrl: BUNDLER_URL, options: { safeAddress: SAFE_ADDRESS @@ -29,7 +31,7 @@ async function main() { // 2) Setup Playground const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { - // nativeTokenAmount: parseEther('0.1'), + // nativeTokenAmount: parseEther('0.01'), // Increase this value when is not enough to cover the gas fees erc20TokenAmount: 200_000n, erc20TokenContractAddress: pimlicoTokenAddress }) diff --git a/playground/utils.ts b/playground/utils.ts index 194113fe3..2c6659c3a 100644 --- a/playground/utils.ts +++ b/playground/utils.ts @@ -86,8 +86,9 @@ export async function setup4337Playground( // Log supported entry points and Safe state console.log('Supported Entry Points', await safe4337Pack.getSupportedEntryPoints()) - console.log('Chain Id', await safe4337Pack.getChainId()) - console.log('senderAddress: ', senderAddress) + console.log('Chain id', await safe4337Pack.getChainId()) + console.log('Safe Address: ', senderAddress) + console.log('Safe Owners:', await safe4337Pack.protocolKit.getOwners()) console.log('is Safe Account deployed: ', await safe4337Pack.protocolKit.isSafeDeployed()) const externalProvider = safe4337Pack.protocolKit.getSafeProvider().getExternalProvider() From 3c524f302a5abec04956e27944684b623d8b6740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Tue, 28 Jan 2025 15:18:13 +0100 Subject: [PATCH 24/55] uncomment --- playground/relay-kit/userop.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/relay-kit/userop.ts b/playground/relay-kit/userop.ts index 785a3825a..93fe11b59 100644 --- a/playground/relay-kit/userop.ts +++ b/playground/relay-kit/userop.ts @@ -31,7 +31,7 @@ async function main() { // 2) Setup Playground const { transactions, timestamp } = await setup4337Playground(safe4337Pack, { - // nativeTokenAmount: parseEther('0.01'), // Increase this value when is not enough to cover the gas fees + nativeTokenAmount: parseEther('0.01'), // Increase this value when is not enough to cover the gas fees erc20TokenAmount: 200_000n, erc20TokenContractAddress: pimlicoTokenAddress }) From e6559c184f34ba6df19f79cbd2cecaf637f8e7d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Tue, 28 Jan 2025 15:43:14 +0100 Subject: [PATCH 25/55] Improve imports --- .../src/packs/safe-4337/Safe4337Pack.ts | 22 ++-- .../src/packs/safe-4337/SafeOperationBase.ts | 5 +- .../packs/safe-4337/SafeOperationFactory.ts | 8 +- .../src/packs/safe-4337/SafeOperationV06.ts | 4 +- .../src/packs/safe-4337/SafeOperationV07.ts | 4 +- .../relay-kit/src/packs/safe-4337/types.ts | 4 +- .../src/packs/safe-4337/utils/entrypoint.ts | 6 +- .../src/packs/safe-4337/utils/index.ts | 49 +++++++++ .../safe-4337/{utils.ts => utils/signing.ts} | 103 +----------------- .../packs/safe-4337/utils/userOperations.ts | 55 +++++++++- 10 files changed, 133 insertions(+), 127 deletions(-) create mode 100644 packages/relay-kit/src/packs/safe-4337/utils/index.ts rename packages/relay-kit/src/packs/safe-4337/{utils.ts => utils/signing.ts} (54%) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index a721f54c2..3e920f76a 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -21,7 +21,8 @@ import { getSafeWebAuthnShareSignerDeployment } from '@safe-global/safe-modules-deployments' import { Hash, encodeFunctionData, zeroAddress, Hex, concat } from 'viem' -import SafeOperationBase from './SafeOperationBase' +import SafeOperationBase from '@safe-global/relay-kit/packs/safe-4337/SafeOperationBase' +import SafeOperationFactory from '@safe-global/relay-kit/packs/safe-4337/SafeOperationFactory' import { EstimateFeeProps, Safe4337CreateTransactionProps, @@ -32,19 +33,22 @@ import { UserOperationWithPayload, PaymasterOptions, BundlerClient -} from './types' +} from '@safe-global/relay-kit/packs/safe-4337/types' import { ABI, DEFAULT_SAFE_VERSION, DEFAULT_SAFE_MODULES_VERSION, RPC_4337_CALLS -} from './constants' -import { getDummySignature, createBundlerClient, userOperationToHexValues } from './utils' -import { entryPointToSafeModules } from './utils/entrypoint' -import { PimlicoFeeEstimator } from './estimators/pimlico/PimlicoFeeEstimator' -import { getRelayKitVersion } from './utils/getRelayKitVersion' -import { createUserOperation } from './utils/userOperations' -import SafeOperationFactory from './SafeOperationFactory' +} from '@safe-global/relay-kit/packs/safe-4337/constants' +import { + entryPointToSafeModules, + getDummySignature, + createBundlerClient, + userOperationToHexValues, + getRelayKitVersion, + createUserOperation +} from '@safe-global/relay-kit/packs/safe-4337/utils' +import { PimlicoFeeEstimator } from '@safe-global/relay-kit/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator' const MAX_ERC20_AMOUNT_TO_APPROVE = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts index fa536e3e5..20609286c 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts @@ -9,7 +9,10 @@ import { SigningMethod } from '@safe-global/types-kit' import Safe, { buildSignatureBytes, EthSafeSignature } from '@safe-global/protocol-kit' -import { EIP712_SAFE_OPERATION_TYPE_V06, EIP712_SAFE_OPERATION_TYPE_V07 } from './constants' +import { + EIP712_SAFE_OPERATION_TYPE_V06, + EIP712_SAFE_OPERATION_TYPE_V07 +} from '@safe-global/relay-kit/packs/safe-4337/constants' abstract class SafeOperationBase implements SafeOperation { userOperation: UserOperation diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts index 6c1c1c9df..a4c034017 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts @@ -5,10 +5,10 @@ import { UserOperationV07, SafeOperationOptions } from '@safe-global/types-kit' -import SafeOperationV06 from './SafeOperationV06' -import SafeOperationV07 from './SafeOperationV07' -import SafeOperationBase from './SafeOperationBase' -import { isEntryPointV7 } from './utils/entrypoint' +import SafeOperationV06 from '@safe-global/relay-kit/packs/safe-4337/SafeOperationV06' +import SafeOperationV07 from '@safe-global/relay-kit/packs/safe-4337/SafeOperationV07' +import SafeOperationBase from '@safe-global/relay-kit/packs/safe-4337/SafeOperationBase' +import { isEntryPointV7 } from '@safe-global/relay-kit/packs/safe-4337/utils' class SafeOperationFactory { static createSafeOperation( diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts index 4e118f336..2ed41651d 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts @@ -5,8 +5,8 @@ import { SafeUserOperation, SafeOperationOptions } from '@safe-global/types-kit' -import SafeOperationBase from './SafeOperationBase' -import { EIP712_SAFE_OPERATION_TYPE_V06 } from './constants' +import SafeOperationBase from '@safe-global/relay-kit/packs/safe-4337/SafeOperationBase' +import { EIP712_SAFE_OPERATION_TYPE_V06 } from '@safe-global/relay-kit/packs/safe-4337/constants' class SafeOperationV06 extends SafeOperationBase { userOperation!: UserOperationV06 diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts index 566626436..67e25f555 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts @@ -5,8 +5,8 @@ import { SafeOperationOptions } from '@safe-global/types-kit' import { concat, Hex, isAddress, pad, toHex } from 'viem' -import SafeOperationBase from './SafeOperationBase' -import { EIP712_SAFE_OPERATION_TYPE_V07 } from './constants' +import SafeOperationBase from '@safe-global/relay-kit/packs/safe-4337/SafeOperationBase' +import { EIP712_SAFE_OPERATION_TYPE_V07 } from '@safe-global/relay-kit/packs/safe-4337/constants' import Safe from '@safe-global/protocol-kit' class SafeOperationV07 extends SafeOperationBase { diff --git a/packages/relay-kit/src/packs/safe-4337/types.ts b/packages/relay-kit/src/packs/safe-4337/types.ts index 2ebadf755..5bcf29319 100644 --- a/packages/relay-kit/src/packs/safe-4337/types.ts +++ b/packages/relay-kit/src/packs/safe-4337/types.ts @@ -11,8 +11,8 @@ import { SafeVersion, UserOperation } from '@safe-global/types-kit' -import SafeOperationBase from './SafeOperationBase' -import { RPC_4337_CALLS } from './constants' +import SafeOperationBase from '@safe-global/relay-kit/packs/safe-4337/SafeOperationBase' +import { RPC_4337_CALLS } from '@safe-global/relay-kit/packs/safe-4337/constants' type ExistingSafeOptions = { safeAddress: string diff --git a/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts b/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts index 13f9be0da..ae7aadf68 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils/entrypoint.ts @@ -1,5 +1,9 @@ import Safe from '@safe-global/protocol-kit' -import { ENTRYPOINT_ABI, ENTRYPOINT_ADDRESS_V06, ENTRYPOINT_ADDRESS_V07 } from '../constants' +import { + ENTRYPOINT_ABI, + ENTRYPOINT_ADDRESS_V06, + ENTRYPOINT_ADDRESS_V07 +} from '@safe-global/relay-kit/packs/safe-4337/constants' const EQ_0_2_0 = '0.2.0' diff --git a/packages/relay-kit/src/packs/safe-4337/utils/index.ts b/packages/relay-kit/src/packs/safe-4337/utils/index.ts new file mode 100644 index 000000000..f4a20a7f9 --- /dev/null +++ b/packages/relay-kit/src/packs/safe-4337/utils/index.ts @@ -0,0 +1,49 @@ +import { Hex, PublicRpcSchema, createPublicClient, encodeFunctionData, http, rpcSchema } from 'viem' +import { OperationType, MetaTransactionData } from '@safe-global/types-kit' +import { encodeMultiSendData } from '@safe-global/protocol-kit' +import { ABI } from '@safe-global/relay-kit/packs/safe-4337/constants' +import { + BundlerClient, + RpcSchemaEntry, + Safe4337RpcSchema +} from '@safe-global/relay-kit/packs/safe-4337/types' + +/** + * Gets the EIP-4337 bundler provider. + * + * @param {string} bundlerUrl The EIP-4337 bundler URL. + * @return {BundlerClient} The EIP-4337 bundler provider. + */ +export function createBundlerClient( + bundlerUrl: string +): BundlerClient { + const provider = createPublicClient({ + transport: http(bundlerUrl), + rpcSchema: rpcSchema<[...PublicRpcSchema, ...Safe4337RpcSchema, ...ProviderCustomRpcSchema]>() + }) + + return provider +} + +/** + * Encodes multi-send data from transactions batch. + * + * @param {MetaTransactionData[]} transactions - an array of transaction to to be encoded. + * @return {string} The encoded data string. + */ +export function encodeMultiSendCallData(transactions: MetaTransactionData[]): string { + return encodeFunctionData({ + abi: ABI, + functionName: 'multiSend', + args: [ + encodeMultiSendData( + transactions.map((tx) => ({ ...tx, operation: tx.operation ?? OperationType.Call })) + ) as Hex + ] + }) +} + +export * from './entrypoint' +export * from './signing' +export * from './userOperations' +export * from './getRelayKitVersion' diff --git a/packages/relay-kit/src/packs/safe-4337/utils.ts b/packages/relay-kit/src/packs/safe-4337/utils/signing.ts similarity index 54% rename from packages/relay-kit/src/packs/safe-4337/utils.ts rename to packages/relay-kit/src/packs/safe-4337/utils/signing.ts index bc2bee9d5..b94eb3be7 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils/signing.ts @@ -1,104 +1,5 @@ -import { - Hex, - PublicRpcSchema, - createPublicClient, - encodeFunctionData, - encodePacked, - http, - rpcSchema, - toHex -} from 'viem' -import { - OperationType, - MetaTransactionData, - UserOperation, - UserOperationV07 -} from '@safe-global/types-kit' -import { - EthSafeSignature, - encodeMultiSendData, - buildSignatureBytes -} from '@safe-global/protocol-kit' -import { ABI } from './constants' -import { - BundlerClient, - RpcSchemaEntry, - Safe4337RpcSchema, - UserOperationStringValues -} from './types' -import { isEntryPointV7 } from './utils/entrypoint' - -/** - * Gets the EIP-4337 bundler provider. - * - * @param {string} bundlerUrl The EIP-4337 bundler URL. - * @return {BundlerClient} The EIP-4337 bundler provider. - */ -export function createBundlerClient( - bundlerUrl: string -): BundlerClient { - const provider = createPublicClient({ - transport: http(bundlerUrl), - rpcSchema: rpcSchema<[...PublicRpcSchema, ...Safe4337RpcSchema, ...ProviderCustomRpcSchema]>() - }) - - return provider -} - -/** - * Encodes multi-send data from transactions batch. - * - * @param {MetaTransactionData[]} transactions - an array of transaction to to be encoded. - * @return {string} The encoded data string. - */ -export function encodeMultiSendCallData(transactions: MetaTransactionData[]): string { - return encodeFunctionData({ - abi: ABI, - functionName: 'multiSend', - args: [ - encodeMultiSendData( - transactions.map((tx) => ({ ...tx, operation: tx.operation ?? OperationType.Call })) - ) as Hex - ] - }) -} - -/** - * Converts various bigint values from a UserOperation to their hexadecimal representation. - * - * @param {UserOperation} userOperation - The UserOperation object whose values are to be converted. - * @returns {UserOperation} A new UserOperation object with the values converted to hexadecimal. - */ -export function userOperationToHexValues( - userOperation: UserOperation, - entryPointAddress: string -): UserOperationStringValues { - const userOpV07 = userOperation as UserOperationV07 - - const userOperationWithHexValues = { - ...userOperation, - nonce: toHex(BigInt(userOperation.nonce)), - callGasLimit: toHex(userOperation.callGasLimit), - verificationGasLimit: toHex(userOperation.verificationGasLimit), - preVerificationGas: toHex(userOperation.preVerificationGas), - maxFeePerGas: toHex(userOperation.maxFeePerGas), - maxPriorityFeePerGas: toHex(userOperation.maxPriorityFeePerGas), - ...(isEntryPointV7(entryPointAddress) - ? { - paymaster: userOpV07.paymaster !== '0x' ? userOpV07.paymaster : null, - paymasterData: userOpV07.paymasterData !== '0x' ? userOpV07.paymasterData : null, - paymasterVerificationGasLimit: userOpV07.paymasterVerificationGasLimit - ? toHex(userOpV07.paymasterVerificationGasLimit) - : null, - paymasterPostOpGasLimit: userOpV07.paymasterPostOpGasLimit - ? toHex(userOpV07.paymasterPostOpGasLimit) - : null - } - : {}) - } - - return userOperationWithHexValues -} +import { Hex, encodePacked, toHex } from 'viem' +import { EthSafeSignature, buildSignatureBytes } from '@safe-global/protocol-kit' /** * Passkey Dummy client data JSON fields. This can be used for gas estimations, as it pads the fields enough diff --git a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts index c778db224..49882292d 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts @@ -1,3 +1,4 @@ +import { encodeFunctionData, getAddress, Hex, hexToBytes, sliceHex, toHex } from 'viem' import Safe from '@safe-global/protocol-kit' import { MetaTransactionData, @@ -5,11 +6,18 @@ import { UserOperation, UserOperationV07 } from '@safe-global/types-kit' -import { getSafeNonceFromEntrypoint, isEntryPointV6 } from './entrypoint' -import { encodeFunctionData, getAddress, Hex, hexToBytes, sliceHex, toHex } from 'viem' -import { ABI } from '../constants' -import { ERC20PaymasterOption, PaymasterOptions } from '../types' -import { encodeMultiSendCallData } from '../utils' +import { + getSafeNonceFromEntrypoint, + isEntryPointV6, + isEntryPointV7, + encodeMultiSendCallData +} from '@safe-global/relay-kit/packs/safe-4337/utils' +import { ABI } from '@safe-global/relay-kit/packs/safe-4337/constants' +import { + ERC20PaymasterOption, + PaymasterOptions, + UserOperationStringValues +} from '@safe-global/relay-kit/packs/safe-4337/types' /** * Encode the UserOperation execution from a transaction. @@ -157,3 +165,40 @@ export async function createUserOperation( signature: '0x' } } + +/** + * Converts various bigint values from a UserOperation to their hexadecimal representation. + * + * @param {UserOperation} userOperation - The UserOperation object whose values are to be converted. + * @returns {UserOperation} A new UserOperation object with the values converted to hexadecimal. + */ +export function userOperationToHexValues( + userOperation: UserOperation, + entryPointAddress: string +): UserOperationStringValues { + const userOpV07 = userOperation as UserOperationV07 + + const userOperationWithHexValues = { + ...userOperation, + nonce: toHex(BigInt(userOperation.nonce)), + callGasLimit: toHex(userOperation.callGasLimit), + verificationGasLimit: toHex(userOperation.verificationGasLimit), + preVerificationGas: toHex(userOperation.preVerificationGas), + maxFeePerGas: toHex(userOperation.maxFeePerGas), + maxPriorityFeePerGas: toHex(userOperation.maxPriorityFeePerGas), + ...(isEntryPointV7(entryPointAddress) + ? { + paymaster: userOpV07.paymaster !== '0x' ? userOpV07.paymaster : null, + paymasterData: userOpV07.paymasterData !== '0x' ? userOpV07.paymasterData : null, + paymasterVerificationGasLimit: userOpV07.paymasterVerificationGasLimit + ? toHex(userOpV07.paymasterVerificationGasLimit) + : null, + paymasterPostOpGasLimit: userOpV07.paymasterPostOpGasLimit + ? toHex(userOpV07.paymasterPostOpGasLimit) + : null + } + : {}) + } + + return userOperationWithHexValues +} From a204a8a1c722c656a2abd0180798729ae517f063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Wed, 29 Jan 2025 11:42:52 +0100 Subject: [PATCH 26/55] Fix tests --- .../src/packs/safe-4337/Safe4337Pack.test.ts | 112 +++++++----------- .../src/packs/safe-4337/Safe4337Pack.ts | 8 -- .../src/packs/safe-4337/SafeOperation.test.ts | 59 ++++----- .../pimlico/PimlicoFeeEstimator.test.ts | 19 +-- .../estimators/pimlico/PimlicoFeeEstimator.ts | 5 + .../packs/safe-4337/testing-utils/fixtures.ts | 1 + .../packs/safe-4337/testing-utils/helpers.ts | 1 + .../packs/safe-4337/utils/userOperations.ts | 2 +- 8 files changed, 94 insertions(+), 113 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts index 48fe39ecc..3dccb68e0 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts @@ -34,7 +34,7 @@ const requestMock = jest.fn(async ({ method }: { method: keyof typeof requestRes jest.mock('./utils', () => ({ ...jest.requireActual('./utils'), - getEip4337BundlerProvider: jest.fn(() => ({ request: requestMock })) + createBundlerClient: jest.fn(() => ({ request: requestMock })) })) let safe4337ModuleAddress: viem.Hash @@ -87,16 +87,6 @@ describe('Safe4337Pack', () => { 'Incompatibility detected: The EIP-4337 fallbackhandler is not attached to the Safe Account. Attach this fallbackhandler (address: 0xa581c4A4DB7175302464fF3C06380BC3270b4037) to ensure compatibility.' ) }) - - it('should throw an error if the Safe Modules do not match the supported version', async () => { - await expect( - createSafe4337Pack({ - safeModulesVersion: fixtures.SAFE_MODULES_V0_3_0 - }) - ).rejects.toThrow( - 'Incompatibility detected: Safe modules version 0.3.0 is not supported. The SDK can use 0.2.0 only.' - ) - }) }) describe('When using existing Safe Accounts with version 1.4.1 or greater', () => { @@ -117,7 +107,7 @@ describe('Safe4337Pack', () => { }) const mockedUtils = jest.requireMock('./utils') - mockedUtils.getEip4337BundlerProvider.mockImplementationOnce(() => ({ + mockedUtils.createBundlerClient.mockImplementationOnce(() => ({ request: jest.fn( async ({ method }: { method: keyof typeof overridenMap }) => overridenMap[method] ) @@ -275,6 +265,7 @@ describe('Safe4337Pack', () => { threshold: 1 }, paymasterOptions: { + paymasterUrl: fixtures.PAYMASTER_URL, paymasterAddress: fixtures.PAYMASTER_ADDRESS, paymasterTokenAddress: fixtures.PAYMASTER_TOKEN_ADDRESS } @@ -372,7 +363,7 @@ describe('Safe4337Pack', () => { }) expect(safeOperation).toBeInstanceOf(EthSafeOperation) - expect(safeOperation.data).toMatchObject({ + expect(safeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1, entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', initCode: '0x', @@ -392,13 +383,13 @@ describe('Safe4337Pack', () => { ] }), nonce: 1n, - callGasLimit: 150000n, + callGasLimit: 100000n, validAfter: 0, validUntil: 0, maxFeePerGas: 100000n, maxPriorityFeePerGas: 200000n, - verificationGasLimit: 400000n, - preVerificationGas: 105000n + verificationGasLimit: 100000n, + preVerificationGas: 100000n }) }) @@ -408,7 +399,7 @@ describe('Safe4337Pack', () => { }) expect(safeOperation).toBeInstanceOf(EthSafeOperation) - expect(safeOperation.data).toMatchObject({ + expect(safeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1, entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', initCode: '0x', @@ -424,13 +415,13 @@ describe('Safe4337Pack', () => { ] }), nonce: 1n, - callGasLimit: 150000n, + callGasLimit: 100000n, validAfter: 0, validUntil: 0, maxFeePerGas: 100000n, maxPriorityFeePerGas: 200000n, - verificationGasLimit: 400000n, - preVerificationGas: 105000n + verificationGasLimit: 100000n, + preVerificationGas: 100000n }) }) @@ -449,7 +440,7 @@ describe('Safe4337Pack', () => { }) expect(getInitCodeSpy).toHaveBeenCalled() - expect(safeOperation.data.initCode).toBe( + expect(safeOperation.getSafeOperation().initCode).toBe( '0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec671688f0b900000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c7620000000000000000000000000000000000000000000000000000000000000060ad27de2a410652abce96ea0fdfc30c2f0fd35952b78f554667111999a28ff33800000000000000000000000000000000000000000000000000000000000001e4b63e800d000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008ecd4ec46d4d2a6b64fe960b3d64e8b94b2234eb0000000000000000000000000000000000000000000000000000000000000140000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b40370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000ffac5578be8ac1b2b9d13b34caf4a074b96b8a1b00000000000000000000000000000000000000000000000000000000000000648d0dc49f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b40370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' ) }) @@ -470,7 +461,7 @@ describe('Safe4337Pack', () => { }) expect(sponsoredSafeOperation).toBeInstanceOf(EthSafeOperation) - expect(sponsoredSafeOperation.data).toMatchObject({ + expect(sponsoredSafeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1, entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', initCode: '0x', @@ -486,32 +477,14 @@ describe('Safe4337Pack', () => { ] }), nonce: 1n, - callGasLimit: 150000n, + callGasLimit: 100000n, validAfter: 0, validUntil: 0, maxFeePerGas: 100000n, maxPriorityFeePerGas: 200000n, - verificationGasLimit: 400000n, - preVerificationGas: 105000n - }) - }) - - it('createTransaction should throw an error if paymasterUrl is not present in sponsored transactions', async () => { - const safe4337Pack = await createSafe4337Pack({ - options: { - safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 - }, - // @ts-expect-error - An error will be thrown - paymasterOptions: { - isSponsored: true - } + verificationGasLimit: 100000n, + preVerificationGas: 100000n }) - - await expect( - safe4337Pack.createTransaction({ - transactions: [transferUSDC] - }) - ).rejects.toThrow('No paymaster url provided for a sponsored transaction') }) it('should add the approve transaction to the batch when amountToApprove is provided', async () => { @@ -520,6 +493,7 @@ describe('Safe4337Pack', () => { safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 }, paymasterOptions: { + paymasterUrl: fixtures.PAYMASTER_URL, paymasterTokenAddress: fixtures.PAYMASTER_TOKEN_ADDRESS, paymasterAddress: fixtures.PAYMASTER_ADDRESS } @@ -548,7 +522,7 @@ describe('Safe4337Pack', () => { const batch = [transferUSDC, approveTransaction] expect(sponsoredSafeOperation).toBeInstanceOf(EthSafeOperation) - expect(sponsoredSafeOperation.data).toMatchObject({ + expect(sponsoredSafeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1, entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', initCode: '0x', @@ -568,13 +542,13 @@ describe('Safe4337Pack', () => { ] }), nonce: 1n, - callGasLimit: 150000n, + callGasLimit: 100000n, validAfter: 0, validUntil: 0, maxFeePerGas: 100000n, maxPriorityFeePerGas: 200000n, - verificationGasLimit: 400000n, - preVerificationGas: 105000n + verificationGasLimit: 100000n, + preVerificationGas: 100000n }) }) }) @@ -719,12 +693,7 @@ describe('Safe4337Pack', () => { transactions: [transferUSDC] }) - const safeOpHash = utils.calculateSafeUserOperationHash( - safeOperation.data, - BigInt(fixtures.CHAIN_ID), - fixtures.MODULE_ADDRESS, - fixtures.ENTRYPOINTS[0] - ) + const safeOpHash = safeOperation.getHash() const passkeySignature = await safe4337Pack.protocolKit.signHash(safeOpHash) @@ -765,7 +734,7 @@ describe('Safe4337Pack', () => { expect(requestMock).toHaveBeenCalledWith({ method: constants.RPC_4337_CALLS.SEND_USER_OPERATION, params: [ - utils.userOperationToHexValues(safeOperation.toUserOperation(), fixtures.ENTRYPOINTS[0]), + utils.userOperationToHexValues(safeOperation.getUserOperation(), fixtures.ENTRYPOINTS[0]), fixtures.ENTRYPOINTS[0] ] }) @@ -862,7 +831,7 @@ describe('Safe4337Pack', () => { expect(requestMock).toHaveBeenCalledWith({ method: constants.RPC_4337_CALLS.SEND_USER_OPERATION, params: [ - utils.userOperationToHexValues(safeOperation.toUserOperation(), fixtures.ENTRYPOINTS[0]), + utils.userOperationToHexValues(safeOperation.getUserOperation(), fixtures.ENTRYPOINTS[0]), fixtures.ENTRYPOINTS[0] ] }) @@ -880,21 +849,24 @@ describe('Safe4337Pack', () => { expect(requestMock).toHaveBeenCalledWith({ method: constants.RPC_4337_CALLS.SEND_USER_OPERATION, params: [ - utils.userOperationToHexValues({ - sender: '0xE322e721bCe76cE7FCf3A475f139A9314571ad3D', - nonce: '3', - initCode: '0x', - callData: - '0x7bb37428000000000000000000000000e322e721bce76ce7fcf3a475f139a9314571ad3d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - callGasLimit: 122497n, - verificationGasLimit: 123498n, - preVerificationGas: 50705n, - maxFeePerGas: 105183831060n, - maxPriorityFeePerGas: 1380000000n, - paymasterAndData: '0x', - signature: - '0x000000000000000000000000cb28e74375889e400a4d8aca46b8c59e1cf8825e373c26fa99c2fd7c078080e64fe30eaf1125257bdfe0b358b5caef68aa0420478145f52decc8e74c979d43ab1d' - }), + utils.userOperationToHexValues( + { + sender: '0xE322e721bCe76cE7FCf3A475f139A9314571ad3D', + nonce: '3', + initCode: '0x', + callData: + '0x7bb37428000000000000000000000000e322e721bce76ce7fcf3a475f139a9314571ad3d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + callGasLimit: 122497n, + verificationGasLimit: 123498n, + preVerificationGas: 50705n, + maxFeePerGas: 105183831060n, + maxPriorityFeePerGas: 1380000000n, + paymasterAndData: '0x', + signature: + '0x000000000000000000000000cb28e74375889e400a4d8aca46b8c59e1cf8825e373c26fa99c2fd7c078080e64fe30eaf1125257bdfe0b358b5caef68aa0420478145f52decc8e74c979d43ab1d' + }, + fixtures.ENTRYPOINTS[0] + ), fixtures.ENTRYPOINTS[0] ] }) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index 3e920f76a..b1e0381bd 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -412,8 +412,6 @@ export class Safe4337Pack extends RelayKitBasePack<{ paymasterOptions: this.#paymasterOptions }) - console.log('1.setupEstimationData', setupEstimationData) - if (setupEstimationData) { safeOperation.addEstimations(setupEstimationData) } @@ -429,8 +427,6 @@ export class Safe4337Pack extends RelayKitBasePack<{ ] }) - console.log('2.estimateUserOperationGas', estimateUserOperationGas) - if (estimateUserOperationGas) { safeOperation.addEstimations(estimateUserOperationGas) } @@ -441,8 +437,6 @@ export class Safe4337Pack extends RelayKitBasePack<{ userOperation: safeOperation.getUserOperation() }) - console.log('3.adjustEstimationData', adjustEstimationData) - if (adjustEstimationData) { safeOperation.addEstimations(adjustEstimationData) } @@ -458,8 +452,6 @@ export class Safe4337Pack extends RelayKitBasePack<{ paymasterOptions: this.#paymasterOptions }) - console.log('4.paymasterEstimation', paymasterEstimation) - if (paymasterEstimation) { safeOperation.addEstimations(paymasterEstimation) } diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts index bad68fc25..36e45d0e3 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts @@ -1,17 +1,18 @@ import { Hex, encodePacked } from 'viem' -import { EthSafeSignature } from '@safe-global/protocol-kit' -import EthSafeOperation from './SafeOperationBase' +import Safe, { EthSafeSignature } from '@safe-global/protocol-kit' +import SafeOperationV06 from './SafeOperationV06' import * as fixtures from './testing-utils/fixtures' describe('SafeOperation', () => { it('should create a SafeOperation from an UserOperation', () => { - const safeOperation = new EthSafeOperation(fixtures.USER_OPERATION, { + const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, new Safe(), { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[0] + entryPoint: fixtures.ENTRYPOINTS[0], + sharedSigner: fixtures.SHARED_SIGNER }) - expect(safeOperation.data).toMatchObject({ + expect(safeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.USER_OPERATION.sender, nonce: BigInt(fixtures.USER_OPERATION.nonce), initCode: fixtures.USER_OPERATION.initCode, @@ -31,10 +32,11 @@ describe('SafeOperation', () => { }) it('should add and retrieve signatures', () => { - const safeOperation = new EthSafeOperation(fixtures.USER_OPERATION, { + const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, new Safe(), { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[0] + entryPoint: fixtures.ENTRYPOINTS[0], + sharedSigner: fixtures.SHARED_SIGNER }) safeOperation.addSignature(new EthSafeSignature('0xSigner', '0xSignature')) @@ -48,10 +50,11 @@ describe('SafeOperation', () => { }) it('should encode the signatures', () => { - const safeOperation = new EthSafeOperation(fixtures.USER_OPERATION, { + const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, new Safe(), { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[0] + entryPoint: fixtures.ENTRYPOINTS[0], + sharedSigner: fixtures.SHARED_SIGNER }) safeOperation.addSignature(new EthSafeSignature('0xSigner1', '0xSignature1')) @@ -61,10 +64,11 @@ describe('SafeOperation', () => { }) it('should add estimations', () => { - const safeOperation = new EthSafeOperation(fixtures.USER_OPERATION, { + const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, new Safe(), { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[0] + entryPoint: fixtures.ENTRYPOINTS[0], + sharedSigner: fixtures.SHARED_SIGNER }) safeOperation.addEstimations({ @@ -73,7 +77,7 @@ describe('SafeOperation', () => { preVerificationGas: BigInt(fixtures.GAS_ESTIMATION.preVerificationGas) }) - expect(safeOperation.data).toMatchObject({ + expect(safeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.USER_OPERATION.sender, nonce: BigInt(fixtures.USER_OPERATION.nonce), initCode: fixtures.USER_OPERATION.initCode, @@ -90,11 +94,12 @@ describe('SafeOperation', () => { }) }) - it('should convert to UserOperation', () => { - const safeOperation = new EthSafeOperation(fixtures.USER_OPERATION, { + it('should retrieve the UserOperation', () => { + const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, new Safe(), { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[0] + entryPoint: fixtures.ENTRYPOINTS[0], + sharedSigner: fixtures.SHARED_SIGNER }) safeOperation.addSignature( @@ -104,22 +109,22 @@ describe('SafeOperation', () => { ) ) - expect(safeOperation.toUserOperation()).toMatchObject({ - sender: safeOperation.data.safe, + expect(safeOperation.getUserOperation()).toMatchObject({ + sender: safeOperation.userOperation.sender, nonce: fixtures.USER_OPERATION.nonce, - initCode: safeOperation.data.initCode, - callData: safeOperation.data.callData, - callGasLimit: safeOperation.data.callGasLimit, - verificationGasLimit: safeOperation.data.verificationGasLimit, - preVerificationGas: safeOperation.data.preVerificationGas, - maxFeePerGas: safeOperation.data.maxFeePerGas, - maxPriorityFeePerGas: safeOperation.data.maxPriorityFeePerGas, - paymasterAndData: safeOperation.data.paymasterAndData, + initCode: safeOperation.userOperation.initCode, + callData: safeOperation.userOperation.callData, + callGasLimit: safeOperation.userOperation.callGasLimit, + verificationGasLimit: safeOperation.userOperation.verificationGasLimit, + preVerificationGas: safeOperation.userOperation.preVerificationGas, + maxFeePerGas: safeOperation.userOperation.maxFeePerGas, + maxPriorityFeePerGas: safeOperation.userOperation.maxPriorityFeePerGas, + paymasterAndData: safeOperation.userOperation.paymasterAndData, signature: encodePacked( ['uint48', 'uint48', 'bytes'], [ - safeOperation.data.validAfter, - safeOperation.data.validUntil, + safeOperation.options.validAfter || 0, + safeOperation.options.validUntil || 0, safeOperation.encodedSignatures() as Hex ] ) diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts index 2de4ec2b4..e53153171 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts @@ -1,15 +1,15 @@ import { PimlicoFeeEstimator } from './PimlicoFeeEstimator' import * as fixtures from '../../testing-utils/fixtures' -import * as constants from '../../constants' +import { PIMLICO_CUSTOM_RPC_4337_CALLS } from './types' -jest.mock('../utils', () => ({ - ...jest.requireActual('../utils'), - getEip4337BundlerProvider: () => ({ +jest.mock('../../utils', () => ({ + ...jest.requireActual('../../utils'), + createBundlerClient: () => ({ request: async ({ method }: { method: string }) => { switch (method) { - case constants.RPC_4337_CALLS.SPONSOR_USER_OPERATION: + case PIMLICO_CUSTOM_RPC_4337_CALLS.SPONSOR_USER_OPERATION: return fixtures.SPONSORED_GAS_ESTIMATION - case 'pimlico_getUserOperationGasPrice': + case PIMLICO_CUSTOM_RPC_4337_CALLS.GET_USER_OPERATION_GAS_PRICE: return fixtures.USER_OPERATION_GAS_PRICE default: return undefined @@ -52,7 +52,12 @@ describe('PimlicoFeeEstimator', () => { it('should get the paymaster estimation', async () => { const paymasterGasEstimation = await estimator.getPaymasterEstimation({ userOperation: fixtures.USER_OPERATION, - paymasterUrl: fixtures.PAYMASTER_URL, + bundlerUrl: fixtures.BUNDLER_URL, + paymasterOptions: { + paymasterUrl: fixtures.PAYMASTER_URL, + paymasterAddress: fixtures.PAYMASTER_ADDRESS, + paymasterTokenAddress: fixtures.PAYMASTER_TOKEN_ADDRESS + }, entryPoint: fixtures.ENTRYPOINTS[0] }) diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts index 86cbb93d8..7fd188ae6 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts @@ -56,6 +56,11 @@ export class PimlicoFeeEstimator implements IFeeEstimator { } } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async adjustEstimation(_: EstimateFeeFunctionProps): Promise { + return {} + } + async getPaymasterEstimation({ userOperation, entryPoint, diff --git a/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts b/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts index b2304d729..961f27cec 100644 --- a/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts +++ b/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts @@ -15,6 +15,7 @@ export const PAYMASTER_ADDRESS = '0x0000000000325602a77416A16136FDafd04b299f' export const PAYMASTER_TOKEN_ADDRESS = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' export const CHAIN_ID = '0xaa36a7' export const MODULE_ADDRESS = '0xa581c4A4DB7175302464fF3C06380BC3270b4037' +export const SHARED_SIGNER = '0x' export const RPC_URL = 'https://sepolia.gateway.tenderly.co' export const BUNDLER_URL = 'https://bundler.url' export const PAYMASTER_URL = 'https://paymaster.url' diff --git a/packages/relay-kit/src/packs/safe-4337/testing-utils/helpers.ts b/packages/relay-kit/src/packs/safe-4337/testing-utils/helpers.ts index 15f7d29f6..2b2094c84 100644 --- a/packages/relay-kit/src/packs/safe-4337/testing-utils/helpers.ts +++ b/packages/relay-kit/src/packs/safe-4337/testing-utils/helpers.ts @@ -27,6 +27,7 @@ export const createSafe4337Pack = async ( const safe4337Pack = await Safe4337Pack.init({ provider: fixtures.RPC_URL, signer: process.env.PRIVATE_KEY, + safeModulesVersion: '0.2.0', options: { safeAddress: '' }, diff --git a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts index 49882292d..a67939f18 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts @@ -1,5 +1,5 @@ -import { encodeFunctionData, getAddress, Hex, hexToBytes, sliceHex, toHex } from 'viem' import Safe from '@safe-global/protocol-kit' +import { encodeFunctionData, getAddress, Hex, hexToBytes, sliceHex, toHex } from 'viem' import { MetaTransactionData, OperationType, From 800d2a05ea1b97d287aba0820c09ae1268189167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Thu, 30 Jan 2025 12:36:03 +0100 Subject: [PATCH 27/55] Downgrade viem version breaking tests --- packages/relay-kit/package.json | 2 +- yarn.lock | 135 +++----------------------------- 2 files changed, 11 insertions(+), 126 deletions(-) diff --git a/packages/relay-kit/package.json b/packages/relay-kit/package.json index 820d1fd38..8edc57761 100644 --- a/packages/relay-kit/package.json +++ b/packages/relay-kit/package.json @@ -43,6 +43,6 @@ "@safe-global/protocol-kit": "^5.2.0", "@safe-global/safe-modules-deployments": "^2.2.4", "@safe-global/types-kit": "^1.0.1", - "viem": "^2.22.11" + "viem": "^2.21.8" } } diff --git a/yarn.lock b/yarn.lock index c208df5b4..0c8655ff1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,11 +22,6 @@ resolved "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.9.2.tgz" integrity sha512-0h+FrQDqe2Wn+IIGFkTCd4aAwTJ+7834Ek1COohCyV26AXhwQ7WQaz+4F/nLOeVl/3BtWHOHLPsq46V8YB46Eg== -"@adraffy/ens-normalize@^1.10.1": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" - integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== - "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz" @@ -1117,13 +1112,6 @@ dependencies: "@noble/hashes" "1.4.0" -"@noble/curves@1.7.0", "@noble/curves@~1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.7.0.tgz#0512360622439256df892f21d25b388f52505e45" - integrity sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw== - dependencies: - "@noble/hashes" "1.6.0" - "@noble/curves@^1.4.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.5.0.tgz#7a9b9b507065d516e6dce275a1e31db8d2a100dd" @@ -1145,13 +1133,6 @@ dependencies: "@noble/hashes" "1.4.0" -"@noble/curves@~1.8.1": - version "1.8.1" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.8.1.tgz#19bc3970e205c99e4bdb1c64a4785706bce497ff" - integrity sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ== - dependencies: - "@noble/hashes" "1.7.1" - "@noble/hashes@1.1.2": version "1.1.2" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz" @@ -1182,21 +1163,6 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== -"@noble/hashes@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.0.tgz#d4bfb516ad6e7b5111c216a5cc7075f4cf19e6c5" - integrity sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ== - -"@noble/hashes@1.6.1", "@noble/hashes@~1.6.0": - version "1.6.1" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.1.tgz#df6e5943edcea504bac61395926d6fd67869a0d5" - integrity sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w== - -"@noble/hashes@1.7.1", "@noble/hashes@^1.5.0", "@noble/hashes@~1.7.1": - version "1.7.1" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.7.1.tgz#5738f6d765710921e7a751e00c20ae091ed8db0f" - integrity sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ== - "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1": version "1.3.3" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" @@ -1777,11 +1743,6 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.8.tgz#8f23646c352f020c83bca750a82789e246d42b50" integrity sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg== -"@scure/base@~1.2.1", "@scure/base@~1.2.2", "@scure/base@~1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.4.tgz#002eb571a35d69bdb4c214d0995dff76a8dcd2a9" - integrity sha512-5Yy9czTO47mqz+/J8GM6GIId4umdCk1wc1q8rKERQulIoc8VP9pzDcghv10Tl2E7R96ZUx/PhND3ESYUQX8NuQ== - "@scure/bip32@1.1.5": version "1.1.5" resolved "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz" @@ -1809,24 +1770,6 @@ "@noble/hashes" "~1.4.0" "@scure/base" "~1.1.6" -"@scure/bip32@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.6.0.tgz#6dbc6b4af7c9101b351f41231a879d8da47e0891" - integrity sha512-82q1QfklrUUdXJzjuRU7iG7D7XiFx5PHYVS0+oeNKhyDLT7WPqs6pBcM2W5ZdwOwKCwoE1Vy1se+DHjcXwCYnA== - dependencies: - "@noble/curves" "~1.7.0" - "@noble/hashes" "~1.6.0" - "@scure/base" "~1.2.1" - -"@scure/bip32@^1.5.0": - version "1.6.2" - resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.6.2.tgz#093caa94961619927659ed0e711a6e4bf35bffd0" - integrity sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw== - dependencies: - "@noble/curves" "~1.8.1" - "@noble/hashes" "~1.7.1" - "@scure/base" "~1.2.2" - "@scure/bip39@1.1.1": version "1.1.1" resolved "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz" @@ -1851,22 +1794,6 @@ "@noble/hashes" "~1.5.0" "@scure/base" "~1.1.8" -"@scure/bip39@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.5.0.tgz#c8f9533dbd787641b047984356531d84485f19be" - integrity sha512-Dop+ASYhnrwm9+HA/HwXg7j2ZqM6yk2fyLWb5znexjctFY3+E+eU8cIWI0Pql0Qx4hPZCijlGq4OL71g+Uz30A== - dependencies: - "@noble/hashes" "~1.6.0" - "@scure/base" "~1.2.1" - -"@scure/bip39@^1.4.0": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.5.4.tgz#07fd920423aa671be4540d59bdd344cc1461db51" - integrity sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA== - dependencies: - "@noble/hashes" "~1.7.1" - "@scure/base" "~1.2.4" - "@sentry/core@5.30.0": version "5.30.0" resolved "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz" @@ -2402,21 +2329,11 @@ abitype@1.0.5, abitype@^1.0.2: resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.5.tgz#29d0daa3eea867ca90f7e4123144c1d1270774b6" integrity sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw== -abitype@1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.7.tgz#876a0005d211e1c9132825d45bcee7b46416b284" - integrity sha512-ZfYYSktDQUwc2eduYu8C4wOs+RDPmnRYMh7zNfzeMtGGgb0U+6tLGjixUic6mXf5xKKCcgT5Qp6cv39tOARVFw== - abitype@^0.9.8: version "0.9.10" resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.9.10.tgz#fa6fa30a6465da98736f98b6c601a02ed49f6eec" integrity sha512-FIS7U4n7qwAT58KibwYig5iFG4K61rbhAqaQh/UWj8v1Y8mjX3F8TC9gd8cz9yT1TYel9f8nS5NO5kZp2RW0jQ== -abitype@^1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.8.tgz#3554f28b2e9d6e9f35eb59878193eabd1b9f46ba" - integrity sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg== - acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" @@ -4050,16 +3967,16 @@ ethjs-util@0.1.6, ethjs-util@^0.1.6: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" -eventemitter3@5.0.1, eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - eventemitter3@^4.0.4: version "4.0.7" resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz" @@ -5236,11 +5153,6 @@ isows@1.0.4: resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.4.tgz#810cd0d90cc4995c26395d2aa4cfa4037ebdf061" integrity sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ== -isows@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7" - integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw== - istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" @@ -6922,19 +6834,6 @@ os-tmpdir@~1.0.2: resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== -ox@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/ox/-/ox-0.6.5.tgz#e6506a589bd6af9b5fecfcb2c641b63c9882edb6" - integrity sha512-vmnH8KvMDwFZDbNY1mq2CBRBWIgSliZB/dFV0xKp+DfF/dJkTENt6nmA+DzHSSAgL/GO2ydjkXWvlndJgSY4KQ== - dependencies: - "@adraffy/ens-normalize" "^1.10.1" - "@noble/curves" "^1.6.0" - "@noble/hashes" "^1.5.0" - "@scure/bip32" "^1.5.0" - "@scure/bip39" "^1.4.0" - abitype "^1.0.6" - eventemitter3 "5.0.1" - p-finally@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz" @@ -8594,20 +8493,6 @@ viem@^2.21.8: webauthn-p256 "0.0.5" ws "8.17.1" -viem@^2.22.11: - version "2.22.11" - resolved "https://registry.yarnpkg.com/viem/-/viem-2.22.11.tgz#e8e937980e11688a79559419882347754bb0d009" - integrity sha512-r86JkRcE8GVTRKBZADkT01EbmIAkjqJE3xcgeIk1AznKYE/KQfuaki8vZwaOoqQd5jqVZ7m5kGtFFsRe6LEWrg== - dependencies: - "@noble/curves" "1.7.0" - "@noble/hashes" "1.6.1" - "@scure/bip32" "1.6.0" - "@scure/bip39" "1.5.0" - abitype "1.0.7" - isows "1.0.6" - ox "0.6.5" - ws "8.18.0" - walk-up-path@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-3.0.1.tgz#c8d78d5375b4966c717eb17ada73dbd41490e886" @@ -9051,11 +8936,6 @@ ws@8.17.1: resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== -ws@8.18.0, ws@^8.17.1, ws@^8.5.0: - version "8.18.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" - integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== - ws@8.5.0: version "8.5.0" resolved "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz" @@ -9066,6 +8946,11 @@ ws@^7.4.6: resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== +ws@^8.17.1, ws@^8.5.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + xtend@~4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" From d0e1147d43a01d79dafe4b6f32fac9c1111883bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Thu, 30 Jan 2025 15:21:40 +0100 Subject: [PATCH 28/55] Fix tests --- .../src/packs/safe-4337/Safe4337Pack.test.ts | 4 ++-- .../src/packs/safe-4337/SafeOperationBase.ts | 5 ++--- .../estimators/pimlico/PimlicoFeeEstimator.test.ts | 13 +++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts index 3dccb68e0..32ef39879 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts @@ -764,7 +764,7 @@ describe('Safe4337Pack', () => { fixtures.OWNER_1.toLowerCase(), new protocolKit.EthSafeSignature( fixtures.OWNER_1, - '0xda808d1e84e6aac5eb50fda331469a108bfdce442fd41501fefaa5b5d648ade406d08a1ca2ca9a5f0ba1a079da001dbee6990189a2cdb054e6c388d5afbd2d9b20', + '0x316dda79337aa7c03f8bab2e752ffcb27904cd1be433f7c5eb1a9b70d13770ba4285fa0d0a61e4126770594cc7d9b91fc3cfb19927ad5b8d268aeec8c5473a231b', false ) ) @@ -784,7 +784,7 @@ describe('Safe4337Pack', () => { fixtures.OWNER_1.toLowerCase(), new protocolKit.EthSafeSignature( fixtures.OWNER_1, - '0x975c7ddab3dc06240918a7bde0f543d1b082a8cadeca19d4bc13c30430367fac46c7ef923d9d0051423d1d59d106e5d199a734cd6a472276d54bb04ec7b3796520', + '0xcb28e74375889e400a4d8aca46b8c59e1cf8825e373c26fa99c2fd7c078080e64fe30eaf1125257bdfe0b358b5caef68aa0420478145f52decc8e74c979d43ab1c', false ) ) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts index 20609286c..b9084d448 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts @@ -30,7 +30,7 @@ abstract class SafeOperationBase implements SafeOperation { abstract getSafeOperation(): SafeUserOperation - async sign(signingMethod: string = SigningMethod.ETH_SIGN_TYPED_DATA_V4) { + async sign(signingMethod: SigningMethod = SigningMethod.ETH_SIGN_TYPED_DATA_V4) { const safeProvider = this.protocolKit.getSafeProvider() const signerAddress = await safeProvider.getSignerAddress() const isPasskeySigner = await safeProvider.isPasskeySigner() @@ -59,12 +59,11 @@ abstract class SafeOperationBase implements SafeOperation { } } else { if ( - signingMethod in [ SigningMethod.ETH_SIGN_TYPED_DATA_V4, SigningMethod.ETH_SIGN_TYPED_DATA_V3, SigningMethod.ETH_SIGN_TYPED_DATA - ] + ].includes(signingMethod) ) { const signer = await safeProvider.getExternalSigner() diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts index e53153171..875b931cd 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts @@ -1,6 +1,7 @@ import { PimlicoFeeEstimator } from './PimlicoFeeEstimator' import * as fixtures from '../../testing-utils/fixtures' import { PIMLICO_CUSTOM_RPC_4337_CALLS } from './types' +import { RPC_4337_CALLS } from '../../constants' jest.mock('../../utils', () => ({ ...jest.requireActual('../../utils'), @@ -8,6 +9,7 @@ jest.mock('../../utils', () => ({ request: async ({ method }: { method: string }) => { switch (method) { case PIMLICO_CUSTOM_RPC_4337_CALLS.SPONSOR_USER_OPERATION: + case RPC_4337_CALLS.GET_PAYMASTER_DATA: return fixtures.SPONSORED_GAS_ESTIMATION case PIMLICO_CUSTOM_RPC_4337_CALLS.GET_USER_OPERATION_GAS_PRICE: return fixtures.USER_OPERATION_GAS_PRICE @@ -32,7 +34,10 @@ describe('PimlicoFeeEstimator', () => { entryPoint: fixtures.ENTRYPOINTS[0] }) - expect(sponsoredGasEstimation).toEqual({ maxFeePerGas: 100000n, maxPriorityFeePerGas: 200000n }) + expect(sponsoredGasEstimation).toEqual({ + maxFeePerGas: '0x186A0', + maxPriorityFeePerGas: '0x30D40' + }) }) it('should enable to adjust the gas estimation', async () => { @@ -42,11 +47,7 @@ describe('PimlicoFeeEstimator', () => { entryPoint: fixtures.ENTRYPOINTS[0] }) - expect(sponsoredGasEstimation).toEqual({ - callGasLimit: 181_176n, - verificationGasLimit: 332_224n, - preVerificationGas: 50_996n - }) + expect(sponsoredGasEstimation).toEqual({}) }) it('should get the paymaster estimation', async () => { From 069e4f5174c7c4b2312ae5ac23bbc8682ed3dfdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Thu, 30 Jan 2025 16:37:05 +0100 Subject: [PATCH 29/55] Fix nonce --- packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts | 2 +- packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts | 2 +- packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts | 2 +- .../relay-kit/src/packs/safe-4337/utils/userOperations.ts | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts index b9084d448..755c7f377 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts @@ -81,7 +81,7 @@ abstract class SafeOperationBase implements SafeOperation { types: this.getEIP712Type(), message: { ...safeOperation, - nonce: toHex(safeOperation.nonce), + nonce: BigInt(safeOperation.nonce), validAfter: toHex(safeOperation.validAfter), validUntil: toHex(safeOperation.validUntil), maxFeePerGas: toHex(safeOperation.maxFeePerGas), diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts index 2ed41651d..b8f669114 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts @@ -38,7 +38,7 @@ class SafeOperationV06 extends SafeOperationBase { getSafeOperation(): SafeUserOperation { return { safe: this.userOperation.sender, - nonce: BigInt(this.userOperation.nonce), + nonce: this.userOperation.nonce, initCode: this.userOperation.initCode, callData: this.userOperation.callData, callGasLimit: this.userOperation.callGasLimit, diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts index 67e25f555..fa1f0bb9f 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts @@ -66,7 +66,7 @@ class SafeOperationV07 extends SafeOperationBase { return { safe: this.userOperation.sender, - nonce: BigInt(this.userOperation.nonce), + nonce: this.userOperation.nonce, initCode, callData: this.userOperation.callData, callGasLimit: this.userOperation.callGasLimit, diff --git a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts index a67939f18..fa6c10176 100644 --- a/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts +++ b/packages/relay-kit/src/packs/safe-4337/utils/userOperations.ts @@ -135,7 +135,7 @@ export async function createUserOperation( if (isEntryPointV6(entryPoint)) { return { sender: safeAddress, - nonce: toHex(nonce), + nonce: nonce.toString(), initCode, callData, callGasLimit: 1n, @@ -150,7 +150,7 @@ export async function createUserOperation( return { sender: safeAddress, - nonce: toHex(nonce), + nonce: nonce.toString(), ...unpackInitCode(initCode), callData, callGasLimit: 1n, From 6b5f5632c66876219ab2f2ddbd8ac32318151dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Thu, 30 Jan 2025 16:42:47 +0100 Subject: [PATCH 30/55] Fix starter-kit --- .../relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts | 10 +++++----- .../safe-operations/SafeOperationClient.test.ts | 8 +++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts index 533d4bcfc..70d0ed517 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts @@ -10,7 +10,7 @@ import { } from '@safe-global/safe-modules-deployments' import { MetaTransactionData, OperationType } from '@safe-global/types-kit' import { Safe4337Pack } from './Safe4337Pack' -import EthSafeOperation from './SafeOperationBase' +import SafeOperationBase from './SafeOperationBase' import * as constants from './constants' import * as fixtures from './testing-utils/fixtures' import { createSafe4337Pack, generateTransferCallData } from './testing-utils/helpers' @@ -362,7 +362,7 @@ describe('Safe4337Pack', () => { transactions }) - expect(safeOperation).toBeInstanceOf(EthSafeOperation) + expect(safeOperation).toBeInstanceOf(SafeOperationBase) expect(safeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1, entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', @@ -398,7 +398,7 @@ describe('Safe4337Pack', () => { transactions: [transferUSDC] }) - expect(safeOperation).toBeInstanceOf(EthSafeOperation) + expect(safeOperation).toBeInstanceOf(SafeOperationBase) expect(safeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1, entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', @@ -460,7 +460,7 @@ describe('Safe4337Pack', () => { transactions: [transferUSDC] }) - expect(sponsoredSafeOperation).toBeInstanceOf(EthSafeOperation) + expect(sponsoredSafeOperation).toBeInstanceOf(SafeOperationBase) expect(sponsoredSafeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1, entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', @@ -521,7 +521,7 @@ describe('Safe4337Pack', () => { const batch = [transferUSDC, approveTransaction] - expect(sponsoredSafeOperation).toBeInstanceOf(EthSafeOperation) + expect(sponsoredSafeOperation).toBeInstanceOf(SafeOperationBase) expect(sponsoredSafeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1, entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', diff --git a/packages/sdk-starter-kit/src/extensions/safe-operations/SafeOperationClient.test.ts b/packages/sdk-starter-kit/src/extensions/safe-operations/SafeOperationClient.test.ts index 02a7045a0..5b1c652e1 100644 --- a/packages/sdk-starter-kit/src/extensions/safe-operations/SafeOperationClient.test.ts +++ b/packages/sdk-starter-kit/src/extensions/safe-operations/SafeOperationClient.test.ts @@ -1,6 +1,6 @@ import Safe, * as protocolKitModule from '@safe-global/protocol-kit' import SafeApiKit from '@safe-global/api-kit' -import { Safe4337Pack, EthSafeOperation } from '@safe-global/relay-kit' +import { Safe4337Pack, SafeOperationV06 } from '@safe-global/relay-kit' import { SafeOperationClient } from './SafeOperationClient' import { MESSAGES, SafeClientTxStatus } from '../../constants' @@ -33,7 +33,7 @@ const SAFE_OPERATION_RESPONSE = { } ] } -const SAFE_OPERATION = new EthSafeOperation( +const SAFE_OPERATION = new SafeOperationV06( { sender: '0xSenderAddress', nonce: '0', @@ -47,10 +47,12 @@ const SAFE_OPERATION = new EthSafeOperation( paymasterAndData: '0xPaymasterAndData', signature: '0xSignature' }, + new Safe(), { chainId: 1n, entryPoint: '0xEntryPoint', - moduleAddress: '0xModuleAddress' + moduleAddress: '0xModuleAddress', + sharedSigner: '0xSharedSigner' } ) From f57bab975b9ca1703947eebb07456b76af608759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Thu, 30 Jan 2025 16:47:20 +0100 Subject: [PATCH 31/55] Fix api-kit tests --- packages/api-kit/tests/e2e/addSafeOperation.test.ts | 4 ++-- packages/api-kit/tests/e2e/confirmSafeOperation.test.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/api-kit/tests/e2e/addSafeOperation.test.ts b/packages/api-kit/tests/e2e/addSafeOperation.test.ts index 43c3f9188..509c717e5 100644 --- a/packages/api-kit/tests/e2e/addSafeOperation.test.ts +++ b/packages/api-kit/tests/e2e/addSafeOperation.test.ts @@ -11,7 +11,7 @@ import { ENTRYPOINT_ADDRESS_V06, RPC_4337_CALLS } from '@safe-global/relay-kit/packs/safe-4337/constants' -// Needs to be imported from dist folder in order to mock the getEip4337BundlerProvider function +// Needs to be imported from dist folder in order to mock the createBundlerClient function import * as safe4337Utils from '@safe-global/relay-kit/dist/src/packs/safe-4337/utils' import { getKits } from '../utils/setupKits' @@ -39,7 +39,7 @@ describe('addSafeOperation', () => { const requestStub = sinon.stub() // Setup mocks for the bundler client before(async () => { - sinon.stub(safe4337Utils, 'getEip4337BundlerProvider').returns({ + sinon.stub(safe4337Utils, 'createBundlerClient').returns({ request: requestStub, readContract: sinon .stub() diff --git a/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts b/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts index 1976e0c87..c87fa708d 100644 --- a/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts +++ b/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts @@ -6,7 +6,7 @@ import { generateTransferCallData } from '@safe-global/relay-kit/packs/safe-4337 import SafeApiKit from '@safe-global/api-kit/index' import { getAddSafeOperationProps } from '@safe-global/api-kit/utils/safeOperation' import { SafeOperation } from '@safe-global/types-kit' -// Needs to be imported from dist folder in order to mock the getEip4337BundlerProvider function +// Needs to be imported from dist folder in order to mock the createBundlerClient function import * as safe4337Utils from '@safe-global/relay-kit/dist/src/packs/safe-4337/utils' import { getApiKit, getEip1193Provider } from '../utils/setupKits' import { @@ -71,7 +71,7 @@ describe('confirmSafeOperation', () => { const requestStub = sinon.stub() before(async () => { - sinon.stub(safe4337Utils, 'getEip4337BundlerProvider').returns({ + sinon.stub(safe4337Utils, 'createBundlerClient').returns({ request: requestStub } as unknown as BundlerClient) From 752963514ca54b86a87fc4d5097db0d1f76cc53f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Fri, 31 Jan 2025 08:53:28 +0100 Subject: [PATCH 32/55] Fix some tests --- packages/api-kit/tests/e2e/addMessageSignature.test.ts | 3 +-- packages/api-kit/tests/e2e/addSafeOperation.test.ts | 1 + packages/api-kit/tests/e2e/confirmSafeOperation.test.ts | 3 ++- packages/api-kit/tests/e2e/confirmTransaction.test.ts | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/api-kit/tests/e2e/addMessageSignature.test.ts b/packages/api-kit/tests/e2e/addMessageSignature.test.ts index 86ee5239f..4427b1783 100644 --- a/packages/api-kit/tests/e2e/addMessageSignature.test.ts +++ b/packages/api-kit/tests/e2e/addMessageSignature.test.ts @@ -2,10 +2,9 @@ import Safe, { EthSafeSignature, buildSignatureBytes, hashSafeMessage, - SigningMethod, buildContractSignature } from '@safe-global/protocol-kit' -import { SafeMessage } from '@safe-global/types-kit' +import { SafeMessage, SigningMethod } from '@safe-global/types-kit' import SafeApiKit from '@safe-global/api-kit/index' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' diff --git a/packages/api-kit/tests/e2e/addSafeOperation.test.ts b/packages/api-kit/tests/e2e/addSafeOperation.test.ts index 509c717e5..169397a35 100644 --- a/packages/api-kit/tests/e2e/addSafeOperation.test.ts +++ b/packages/api-kit/tests/e2e/addSafeOperation.test.ts @@ -77,6 +77,7 @@ describe('addSafeOperation', () => { signer: protocolKit.getSafeProvider().signer, options: { safeAddress: SAFE_ADDRESS }, bundlerUrl: BUNDLER_URL, + safeModulesVersion: '0.2.0', paymasterOptions: { paymasterTokenAddress: PAYMASTER_TOKEN_ADDRESS, paymasterAddress: PAYMASTER_ADDRESS diff --git a/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts b/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts index c87fa708d..d3485fb88 100644 --- a/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts +++ b/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts @@ -41,7 +41,8 @@ describe('confirmSafeOperation', () => { provider: options.provider || getEip1193Provider(), signer: options.signer || PRIVATE_KEY_1, options: { safeAddress: SAFE_ADDRESS }, - bundlerUrl: BUNDLER_URL + bundlerUrl: BUNDLER_URL, + safeModulesVersion: '0.2.0' }) const createSignature = async (safeOperation: SafeOperation, signer: string) => { diff --git a/packages/api-kit/tests/e2e/confirmTransaction.test.ts b/packages/api-kit/tests/e2e/confirmTransaction.test.ts index 090a8a342..130e6564a 100644 --- a/packages/api-kit/tests/e2e/confirmTransaction.test.ts +++ b/packages/api-kit/tests/e2e/confirmTransaction.test.ts @@ -1,12 +1,12 @@ import Safe, { EthSafeSignature, buildSignatureBytes, - SigningMethod, buildContractSignature } from '@safe-global/protocol-kit' import { SafeMultisigConfirmationResponse, - SafeTransactionDataPartial + SafeTransactionDataPartial, + SigningMethod } from '@safe-global/types-kit' import SafeApiKit from '@safe-global/api-kit/index' import chai from 'chai' From 705be625312060364a307bd45658ec984380b5c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Fri, 31 Jan 2025 09:31:37 +0100 Subject: [PATCH 33/55] Fix api kit tests --- packages/api-kit/tests/e2e/addSafeOperation.test.ts | 1 + packages/types-kit/src/types.ts | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/api-kit/tests/e2e/addSafeOperation.test.ts b/packages/api-kit/tests/e2e/addSafeOperation.test.ts index 169397a35..56b3681da 100644 --- a/packages/api-kit/tests/e2e/addSafeOperation.test.ts +++ b/packages/api-kit/tests/e2e/addSafeOperation.test.ts @@ -79,6 +79,7 @@ describe('addSafeOperation', () => { bundlerUrl: BUNDLER_URL, safeModulesVersion: '0.2.0', paymasterOptions: { + paymasterUrl: 'https://paymaster.url', paymasterTokenAddress: PAYMASTER_TOKEN_ADDRESS, paymasterAddress: PAYMASTER_ADDRESS } diff --git a/packages/types-kit/src/types.ts b/packages/types-kit/src/types.ts index ba9cfe7c4..05dd9e493 100644 --- a/packages/types-kit/src/types.ts +++ b/packages/types-kit/src/types.ts @@ -374,7 +374,12 @@ export interface SafeOperation { export const isSafeOperation = (response: unknown): response is SafeOperation => { const safeOperation = response as SafeOperation - return 'data' in safeOperation && 'signatures' in safeOperation + return ( + 'userOperation' in safeOperation && + 'options' in safeOperation && + 'signatures' in safeOperation && + 'protocolKit' in safeOperation + ) } export type SafeOperationConfirmation = { From a8d7ba0d95eb071449ce77757fa66e86d97f61cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Fri, 31 Jan 2025 17:23:38 +0100 Subject: [PATCH 34/55] protocol-kit shouldn't be a dep of SafeOperation --- .../tests/e2e/addSafeOperation.test.ts | 3 +- .../tests/e2e/confirmSafeOperation.test.ts | 15 ++- .../src/packs/safe-4337/Safe4337Pack.ts | 96 ++++++++++++++++--- .../src/packs/safe-4337/SafeOperation.test.ts | 12 +-- .../src/packs/safe-4337/SafeOperationBase.ts | 82 +--------------- .../packs/safe-4337/SafeOperationFactory.ts | 6 +- .../src/packs/safe-4337/SafeOperationV06.ts | 5 +- .../src/packs/safe-4337/SafeOperationV07.ts | 5 +- packages/types-kit/src/types.ts | 6 +- 9 files changed, 111 insertions(+), 119 deletions(-) diff --git a/packages/api-kit/tests/e2e/addSafeOperation.test.ts b/packages/api-kit/tests/e2e/addSafeOperation.test.ts index 56b3681da..45c97a081 100644 --- a/packages/api-kit/tests/e2e/addSafeOperation.test.ts +++ b/packages/api-kit/tests/e2e/addSafeOperation.test.ts @@ -180,12 +180,11 @@ describe('addSafeOperation', () => { chai.expect(safeOperationsAfter.count).to.equal(initialNumSafeOperations + 1) }) - it('should add a new SafeOperation using a SafeOperation object from the relay-kit', async () => { + it.only('should add a new SafeOperation using a SafeOperation object from the relay-kit', async () => { const safeOperation = await safe4337Pack.createTransaction({ transactions: [transferUSDC, transferUSDC] }) const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) - // Get the number of SafeOperations before adding a new one const safeOperationsBefore = await safeApiKit.getSafeOperationsByAddress({ safeAddress: SAFE_ADDRESS diff --git a/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts b/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts index d3485fb88..e61229e2c 100644 --- a/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts +++ b/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts @@ -1,7 +1,12 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import sinon from 'sinon' -import { BundlerClient, Safe4337InitOptions, Safe4337Pack } from '@safe-global/relay-kit' +import { + BundlerClient, + Safe4337InitOptions, + Safe4337Pack, + SafeOperationBase +} from '@safe-global/relay-kit' import { generateTransferCallData } from '@safe-global/relay-kit/packs/safe-4337/testing-utils/helpers' import SafeApiKit from '@safe-global/api-kit/index' import { getAddSafeOperationProps } from '@safe-global/api-kit/utils/safeOperation' @@ -16,8 +21,8 @@ import { chai.use(chaiAsPromised) -const PRIVATE_KEY_1 = '0x83a415ca62e11f5fa5567e98450d0f82ae19ff36ef876c10a8d448c788a53676' -const PRIVATE_KEY_2 = '0xb88ad5789871315d0dab6fc5961d6714f24f35a6393f13a6f426dfecfc00ab44' +const PRIVATE_KEY_1 = '0x83a415ca62e11f5fa5567e98450d0f82ae19ff36ef876c10a8d448c788a53676' // 0x56e2C102c664De6DfD7315d12c0178b61D16F171 +const PRIVATE_KEY_2 = '0xb88ad5789871315d0dab6fc5961d6714f24f35a6393f13a6f426dfecfc00ab44' // 0x9cCBDE03eDd71074ea9c49e413FA9CDfF16D263B const SAFE_ADDRESS = '0x60C4Ab82D06Fd7dFE9517e17736C2Dcc77443EF0' // 4337 enabled 1/2 Safe (v1.4.1) owned by PRIVATE_KEY_1 + PRIVATE_KEY_2 const TX_SERVICE_URL = 'https://safe-transaction-sepolia.staging.5afe.dev/api' const BUNDLER_URL = `https://bundler.url` @@ -47,7 +52,9 @@ describe('confirmSafeOperation', () => { const createSignature = async (safeOperation: SafeOperation, signer: string) => { const safe4337Pack = await getSafe4337Pack({ signer }) - const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) + const signedSafeOperation = await safe4337Pack.signSafeOperation( + safeOperation as SafeOperationBase + ) const signerAddress = await safe4337Pack.protocolKit.getSafeProvider().getSignerAddress() return signedSafeOperation.getSignature(signerAddress!) } diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index 23a2b95e8..66bc584d5 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -1,4 +1,5 @@ import semverSatisfies from 'semver/functions/satisfies' +import { toHex } from 'viem' import Safe, { EthSafeSignature, encodeMultiSendData, @@ -13,6 +14,7 @@ import { OperationType, SafeOperationConfirmation, SafeOperationResponse, + SafeSignature, SigningMethod } from '@safe-global/types-kit' import { @@ -483,18 +485,14 @@ export class Safe4337Pack extends RelayKitBasePack<{ userOperation.callData += this.#onchainIdentifier } - const safeOperation = SafeOperationFactory.createSafeOperation( - userOperation, - this.protocolKit, - { - chainId: this.#chainId, - moduleAddress: this.#SAFE_4337_MODULE_ADDRESS, - entryPoint: this.#ENTRYPOINT_ADDRESS, - sharedSigner: this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, - validUntil, - validAfter - } - ) + const safeOperation = SafeOperationFactory.createSafeOperation(userOperation, { + chainId: this.#chainId, + moduleAddress: this.#SAFE_4337_MODULE_ADDRESS, + entryPoint: this.#ENTRYPOINT_ADDRESS, + sharedSigner: this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, + validUntil, + validAfter + }) return await this.getEstimateFee({ safeOperation, @@ -527,7 +525,6 @@ export class Safe4337Pack extends RelayKitBasePack<{ paymasterAndData: concat([paymaster, paymasterData]), signature: safeOperationResponse.preparedSignature || '0x' }, - this.protocolKit, { chainId: this.#chainId, moduleAddress: this.#SAFE_4337_MODULE_ADDRESS, @@ -577,7 +574,78 @@ export class Safe4337Pack extends RelayKitBasePack<{ safeOp = safeOperation } - await safeOp.sign(signingMethod) + const safeProvider = this.protocolKit.getSafeProvider() + const signerAddress = await safeProvider.getSignerAddress() + const isPasskeySigner = await safeProvider.isPasskeySigner() + + if (!signerAddress) { + throw new Error('There is no signer address available to sign the SafeOperation') + } + + const isOwner = await this.protocolKit.isOwner(signerAddress) + const isSafeDeployed = await this.protocolKit.isSafeDeployed() + + if ((!isOwner && isSafeDeployed) || (!isSafeDeployed && !isPasskeySigner && !isOwner)) { + throw new Error('UserOperations can only be signed by Safe owners') + } + + let safeSignature: SafeSignature + + if (isPasskeySigner) { + const safeOpHash = safeOp.getHash() + + if (!isSafeDeployed) { + const passkeySignature = await this.protocolKit.signHash(safeOpHash) + safeSignature = new EthSafeSignature( + this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, + passkeySignature.data, + true + ) + } else { + safeSignature = await this.protocolKit.signHash(safeOpHash) + } + } else { + if ( + [ + SigningMethod.ETH_SIGN_TYPED_DATA_V4, + SigningMethod.ETH_SIGN_TYPED_DATA_V3, + SigningMethod.ETH_SIGN_TYPED_DATA + ].includes(signingMethod) + ) { + const signer = await safeProvider.getExternalSigner() + + if (!signer) { + throw new Error('No signer found') + } + + const signerAddress = signer.account.address + const safeOperation = safeOp.getSafeOperation() + const signature = await signer.signTypedData({ + domain: { + chainId: Number(this.#chainId), + verifyingContract: this.#SAFE_4337_MODULE_ADDRESS + }, + types: safeOp.getEIP712Type(), + message: { + ...safeOperation, + nonce: BigInt(safeOperation.nonce), + validAfter: toHex(safeOperation.validAfter), + validUntil: toHex(safeOperation.validUntil), + maxFeePerGas: toHex(safeOperation.maxFeePerGas), + maxPriorityFeePerGas: toHex(safeOperation.maxPriorityFeePerGas) + }, + primaryType: 'SafeOp' + }) + + safeSignature = new EthSafeSignature(signerAddress, signature) + } else { + const safeOpHash = safeOp.getHash() + + safeSignature = await this.protocolKit.signHash(safeOpHash) + } + } + + safeOp.addSignature(safeSignature) return safeOp } diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts index 87680118f..353988c1d 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts @@ -1,11 +1,11 @@ import { Hex, encodePacked } from 'viem' -import Safe, { EthSafeSignature } from '@safe-global/protocol-kit' +import { EthSafeSignature } from '@safe-global/protocol-kit' import SafeOperationV06 from './SafeOperationV06' import * as fixtures from './testing-utils/fixtures' describe('SafeOperation', () => { it('should create a SafeOperation from an UserOperation', () => { - const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, new Safe(), { + const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, entryPoint: fixtures.ENTRYPOINTS[0], @@ -32,7 +32,7 @@ describe('SafeOperation', () => { }) it('should add and retrieve signatures', () => { - const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, new Safe(), { + const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, entryPoint: fixtures.ENTRYPOINTS[0], @@ -50,7 +50,7 @@ describe('SafeOperation', () => { }) it('should encode the signatures', () => { - const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, new Safe(), { + const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, entryPoint: fixtures.ENTRYPOINTS[0], @@ -64,7 +64,7 @@ describe('SafeOperation', () => { }) it('should add estimations', () => { - const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, new Safe(), { + const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, entryPoint: fixtures.ENTRYPOINTS[0], @@ -95,7 +95,7 @@ describe('SafeOperation', () => { }) it('should retrieve the UserOperation', () => { - const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, new Safe(), { + const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, entryPoint: fixtures.ENTRYPOINTS[0], diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts index 755c7f377..89ce5d484 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts @@ -1,14 +1,13 @@ -import { Hex, encodePacked, hashTypedData, toHex } from 'viem' +import { Hex, encodePacked, hashTypedData } from 'viem' import { EstimateGasData, SafeOperation, SafeOperationOptions, SafeSignature, SafeUserOperation, - UserOperation, - SigningMethod + UserOperation } from '@safe-global/types-kit' -import Safe, { buildSignatureBytes, EthSafeSignature } from '@safe-global/protocol-kit' +import { buildSignatureBytes } from '@safe-global/protocol-kit' import { EIP712_SAFE_OPERATION_TYPE_V06, EIP712_SAFE_OPERATION_TYPE_V07 @@ -16,13 +15,11 @@ import { abstract class SafeOperationBase implements SafeOperation { userOperation: UserOperation - protocolKit: Safe options: SafeOperationOptions signatures: Map = new Map() - constructor(userOperation: UserOperation, protocolKit: Safe, options: SafeOperationOptions) { + constructor(userOperation: UserOperation, options: SafeOperationOptions) { this.userOperation = userOperation - this.protocolKit = protocolKit this.options = options } @@ -30,77 +27,6 @@ abstract class SafeOperationBase implements SafeOperation { abstract getSafeOperation(): SafeUserOperation - async sign(signingMethod: SigningMethod = SigningMethod.ETH_SIGN_TYPED_DATA_V4) { - const safeProvider = this.protocolKit.getSafeProvider() - const signerAddress = await safeProvider.getSignerAddress() - const isPasskeySigner = await safeProvider.isPasskeySigner() - - if (!signerAddress) { - throw new Error('There is no signer address available to sign the SafeOperation') - } - - const isOwner = await this.protocolKit.isOwner(signerAddress) - const isSafeDeployed = await this.protocolKit.isSafeDeployed() - - if ((!isOwner && isSafeDeployed) || (!isSafeDeployed && !isPasskeySigner && !isOwner)) { - throw new Error('UserOperations can only be signed by Safe owners') - } - - let safeSignature: SafeSignature - - if (isPasskeySigner) { - const safeOpHash = this.getHash() - - if (!isSafeDeployed) { - const passkeySignature = await this.protocolKit.signHash(safeOpHash) - safeSignature = new EthSafeSignature(this.options.sharedSigner, passkeySignature.data, true) - } else { - safeSignature = await this.protocolKit.signHash(safeOpHash) - } - } else { - if ( - [ - SigningMethod.ETH_SIGN_TYPED_DATA_V4, - SigningMethod.ETH_SIGN_TYPED_DATA_V3, - SigningMethod.ETH_SIGN_TYPED_DATA - ].includes(signingMethod) - ) { - const signer = await safeProvider.getExternalSigner() - - if (!signer) { - throw new Error('No signer found') - } - - const signerAddress = signer.account.address - const safeOperation = this.getSafeOperation() - const signature = await signer.signTypedData({ - domain: { - chainId: Number(this.options.chainId), - verifyingContract: this.options.moduleAddress - }, - types: this.getEIP712Type(), - message: { - ...safeOperation, - nonce: BigInt(safeOperation.nonce), - validAfter: toHex(safeOperation.validAfter), - validUntil: toHex(safeOperation.validUntil), - maxFeePerGas: toHex(safeOperation.maxFeePerGas), - maxPriorityFeePerGas: toHex(safeOperation.maxPriorityFeePerGas) - }, - primaryType: 'SafeOp' - }) - - safeSignature = new EthSafeSignature(signerAddress, signature) - } else { - const safeOpHash = this.getHash() - - safeSignature = await this.protocolKit.signHash(safeOpHash) - } - } - - this.addSignature(safeSignature) - } - getSignature(signer: string): SafeSignature | undefined { return this.signatures.get(signer.toLowerCase()) } diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts index a4c034017..83ea2b6e7 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts @@ -1,4 +1,3 @@ -import Safe from '@safe-global/protocol-kit' import { UserOperation, UserOperationV06, @@ -13,13 +12,12 @@ import { isEntryPointV7 } from '@safe-global/relay-kit/packs/safe-4337/utils' class SafeOperationFactory { static createSafeOperation( userOperation: UserOperation, - protocolKit: Safe, options: SafeOperationOptions ): SafeOperationBase { if (isEntryPointV7(options.entryPoint)) { - return new SafeOperationV07(userOperation as UserOperationV07, protocolKit, options) + return new SafeOperationV07(userOperation as UserOperationV07, options) } else { - return new SafeOperationV06(userOperation as UserOperationV06, protocolKit, options) + return new SafeOperationV06(userOperation as UserOperationV06, options) } } } diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts index b8f669114..8a2cdcc95 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts @@ -1,4 +1,3 @@ -import Safe from '@safe-global/protocol-kit' import { UserOperationV06, EstimateGasData, @@ -11,8 +10,8 @@ import { EIP712_SAFE_OPERATION_TYPE_V06 } from '@safe-global/relay-kit/packs/saf class SafeOperationV06 extends SafeOperationBase { userOperation!: UserOperationV06 - constructor(userOperation: UserOperationV06, protocolKit: Safe, options: SafeOperationOptions) { - super(userOperation, protocolKit, options) + constructor(userOperation: UserOperationV06, options: SafeOperationOptions) { + super(userOperation, options) } addEstimations(estimations: EstimateGasData): void { diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts index fa1f0bb9f..3f5f73cb5 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts @@ -7,13 +7,12 @@ import { import { concat, Hex, isAddress, pad, toHex } from 'viem' import SafeOperationBase from '@safe-global/relay-kit/packs/safe-4337/SafeOperationBase' import { EIP712_SAFE_OPERATION_TYPE_V07 } from '@safe-global/relay-kit/packs/safe-4337/constants' -import Safe from '@safe-global/protocol-kit' class SafeOperationV07 extends SafeOperationBase { userOperation!: UserOperationV07 - constructor(userOperation: UserOperationV07, protocolKit: Safe, options: SafeOperationOptions) { - super(userOperation, protocolKit, options) + constructor(userOperation: UserOperationV07, options: SafeOperationOptions) { + super(userOperation, options) } addEstimations(estimations: EstimateGasData): void { diff --git a/packages/types-kit/src/types.ts b/packages/types-kit/src/types.ts index 05dd9e493..7cab75d48 100644 --- a/packages/types-kit/src/types.ts +++ b/packages/types-kit/src/types.ts @@ -366,7 +366,6 @@ export interface SafeOperation { encodedSignatures(): string addEstimations(estimations: EstimateGasData): void getSafeOperation(): SafeUserOperation - sign(signingMethod: SigningMethod): void getUserOperation(): UserOperation getHash(): string } @@ -375,10 +374,7 @@ export const isSafeOperation = (response: unknown): response is SafeOperation => const safeOperation = response as SafeOperation return ( - 'userOperation' in safeOperation && - 'options' in safeOperation && - 'signatures' in safeOperation && - 'protocolKit' in safeOperation + 'userOperation' in safeOperation && 'options' in safeOperation && 'signatures' in safeOperation ) } From 4a21b2cf47702a424703562018b46da9bf127f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Fri, 31 Jan 2025 17:40:07 +0100 Subject: [PATCH 35/55] Fix protocol-kit tests --- .../tests/e2e/eip1271-contract-signatures.test.ts | 3 +-- packages/protocol-kit/tests/e2e/eip1271.test.ts | 3 +-- packages/protocol-kit/tests/e2e/execution.test.ts | 4 ++-- packages/protocol-kit/tests/e2e/offChainSignatures.test.ts | 4 ++-- packages/protocol-kit/tests/e2e/utilsSignatures.test.ts | 2 +- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/protocol-kit/tests/e2e/eip1271-contract-signatures.test.ts b/packages/protocol-kit/tests/e2e/eip1271-contract-signatures.test.ts index 28e2a5a86..bcc54b404 100644 --- a/packages/protocol-kit/tests/e2e/eip1271-contract-signatures.test.ts +++ b/packages/protocol-kit/tests/e2e/eip1271-contract-signatures.test.ts @@ -5,8 +5,7 @@ import { getSafeWithOwners, itif } from '@safe-global/testing-kit' -import { SafeTransactionDataPartial } from '@safe-global/types-kit' -import { SigningMethod } from '@safe-global/protocol-kit/types' +import { SafeTransactionDataPartial, SigningMethod } from '@safe-global/types-kit' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import { getEip1193Provider } from './utils/setupProvider' diff --git a/packages/protocol-kit/tests/e2e/eip1271.test.ts b/packages/protocol-kit/tests/e2e/eip1271.test.ts index 47db45be4..6f9ecc7e0 100644 --- a/packages/protocol-kit/tests/e2e/eip1271.test.ts +++ b/packages/protocol-kit/tests/e2e/eip1271.test.ts @@ -14,8 +14,7 @@ import { itif } from '@safe-global/testing-kit' import SafeMessage from '@safe-global/protocol-kit/utils/messages/SafeMessage' -import { OperationType, SafeTransactionDataPartial } from '@safe-global/types-kit' -import { SigningMethod } from '@safe-global/protocol-kit/types' +import { OperationType, SafeTransactionDataPartial, SigningMethod } from '@safe-global/types-kit' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import { getEip1193Provider } from './utils/setupProvider' diff --git a/packages/protocol-kit/tests/e2e/execution.test.ts b/packages/protocol-kit/tests/e2e/execution.test.ts index 6eafa6418..05c4bffb5 100644 --- a/packages/protocol-kit/tests/e2e/execution.test.ts +++ b/packages/protocol-kit/tests/e2e/execution.test.ts @@ -1,6 +1,6 @@ import { getERC20Mintable, safeVersionDeployed, setupTests, itif } from '@safe-global/testing-kit' -import Safe, { SigningMethod } from '@safe-global/protocol-kit/index' -import { TransactionOptions, MetaTransactionData } from '@safe-global/types-kit' +import Safe from '@safe-global/protocol-kit/index' +import { TransactionOptions, MetaTransactionData, SigningMethod } from '@safe-global/types-kit' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import { waitSafeTxReceipt } from './utils/transactions' diff --git a/packages/protocol-kit/tests/e2e/offChainSignatures.test.ts b/packages/protocol-kit/tests/e2e/offChainSignatures.test.ts index c1f02b86d..cccceaab6 100644 --- a/packages/protocol-kit/tests/e2e/offChainSignatures.test.ts +++ b/packages/protocol-kit/tests/e2e/offChainSignatures.test.ts @@ -1,6 +1,6 @@ import { safeVersionDeployed, setupTests, itif } from '@safe-global/testing-kit' -import Safe, { SigningMethod } from '@safe-global/protocol-kit/index' -import { SafeMultisigTransactionResponse } from '@safe-global/types-kit' +import Safe from '@safe-global/protocol-kit/index' +import { SafeMultisigTransactionResponse, SigningMethod } from '@safe-global/types-kit' import chai from 'chai' import chaiAsPromised from 'chai-as-promised' import { getEip1193Provider } from './utils/setupProvider' diff --git a/packages/protocol-kit/tests/e2e/utilsSignatures.test.ts b/packages/protocol-kit/tests/e2e/utilsSignatures.test.ts index 66a166162..6c623298a 100644 --- a/packages/protocol-kit/tests/e2e/utilsSignatures.test.ts +++ b/packages/protocol-kit/tests/e2e/utilsSignatures.test.ts @@ -3,7 +3,7 @@ import { adjustVInSignature, isTxHashSignedWithPrefix } from '@safe-global/protocol-kit/utils/signatures' -import { SigningMethod } from '@safe-global/protocol-kit/index' +import { SigningMethod } from '@safe-global/types-kit' const safeTxHash = '0x4de27e660bd23052b71c854b0188ef1c5b325b10075c70f27afe2343e5c287f5' const signerAddress = '0xbc2BB26a6d821e69A38016f3858561a1D80d4182' From 0a3842ffee388ac9955399f055f5ccdb6f4c7183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Fri, 31 Jan 2025 17:44:42 +0100 Subject: [PATCH 36/55] Update test --- .../src/extensions/safe-operations/SafeOperationClient.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/sdk-starter-kit/src/extensions/safe-operations/SafeOperationClient.test.ts b/packages/sdk-starter-kit/src/extensions/safe-operations/SafeOperationClient.test.ts index 5b1c652e1..46c074fed 100644 --- a/packages/sdk-starter-kit/src/extensions/safe-operations/SafeOperationClient.test.ts +++ b/packages/sdk-starter-kit/src/extensions/safe-operations/SafeOperationClient.test.ts @@ -47,7 +47,6 @@ const SAFE_OPERATION = new SafeOperationV06( paymasterAndData: '0xPaymasterAndData', signature: '0xSignature' }, - new Safe(), { chainId: 1n, entryPoint: '0xEntryPoint', From bd317561ceb4d1ed7e6d547bb3a407d055abaa72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Fri, 31 Jan 2025 17:47:11 +0100 Subject: [PATCH 37/55] Not neccesary sharedSigner --- .../relay-kit/src/packs/safe-4337/Safe4337Pack.ts | 2 -- .../src/packs/safe-4337/SafeOperation.test.ts | 15 +++++---------- .../safe-operations/SafeOperationClient.test.ts | 3 +-- packages/types-kit/src/types.ts | 1 - 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index 66bc584d5..ea6354054 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -489,7 +489,6 @@ export class Safe4337Pack extends RelayKitBasePack<{ chainId: this.#chainId, moduleAddress: this.#SAFE_4337_MODULE_ADDRESS, entryPoint: this.#ENTRYPOINT_ADDRESS, - sharedSigner: this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, validUntil, validAfter }) @@ -528,7 +527,6 @@ export class Safe4337Pack extends RelayKitBasePack<{ { chainId: this.#chainId, moduleAddress: this.#SAFE_4337_MODULE_ADDRESS, - sharedSigner: this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, entryPoint: userOperation?.entryPoint || this.#ENTRYPOINT_ADDRESS, validAfter: this.#timestamp(validAfter), validUntil: this.#timestamp(validUntil) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts index 353988c1d..652fc3ab9 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts @@ -8,8 +8,7 @@ describe('SafeOperation', () => { const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[0], - sharedSigner: fixtures.SHARED_SIGNER + entryPoint: fixtures.ENTRYPOINTS[0] }) expect(safeOperation.getSafeOperation()).toMatchObject({ @@ -35,8 +34,7 @@ describe('SafeOperation', () => { const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[0], - sharedSigner: fixtures.SHARED_SIGNER + entryPoint: fixtures.ENTRYPOINTS[0] }) safeOperation.addSignature(new EthSafeSignature('0xSigner', '0xSignature')) @@ -53,8 +51,7 @@ describe('SafeOperation', () => { const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[0], - sharedSigner: fixtures.SHARED_SIGNER + entryPoint: fixtures.ENTRYPOINTS[0] }) safeOperation.addSignature(new EthSafeSignature('0xSigner1', '0xSignature1')) @@ -67,8 +64,7 @@ describe('SafeOperation', () => { const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[0], - sharedSigner: fixtures.SHARED_SIGNER + entryPoint: fixtures.ENTRYPOINTS[0] }) safeOperation.addEstimations({ @@ -98,8 +94,7 @@ describe('SafeOperation', () => { const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[0], - sharedSigner: fixtures.SHARED_SIGNER + entryPoint: fixtures.ENTRYPOINTS[0] }) safeOperation.addSignature( diff --git a/packages/sdk-starter-kit/src/extensions/safe-operations/SafeOperationClient.test.ts b/packages/sdk-starter-kit/src/extensions/safe-operations/SafeOperationClient.test.ts index 46c074fed..456119ae6 100644 --- a/packages/sdk-starter-kit/src/extensions/safe-operations/SafeOperationClient.test.ts +++ b/packages/sdk-starter-kit/src/extensions/safe-operations/SafeOperationClient.test.ts @@ -50,8 +50,7 @@ const SAFE_OPERATION = new SafeOperationV06( { chainId: 1n, entryPoint: '0xEntryPoint', - moduleAddress: '0xModuleAddress', - sharedSigner: '0xSharedSigner' + moduleAddress: '0xModuleAddress' } ) diff --git a/packages/types-kit/src/types.ts b/packages/types-kit/src/types.ts index d40e28e2c..bac1bcf2d 100644 --- a/packages/types-kit/src/types.ts +++ b/packages/types-kit/src/types.ts @@ -351,7 +351,6 @@ export type SafeOperationOptions = { moduleAddress: string entryPoint: string chainId: bigint - sharedSigner: string validAfter?: number validUntil?: number } From 5159d691f81fb3c8d755f85567324fea5e8da5cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Mon, 3 Feb 2025 10:52:32 +0100 Subject: [PATCH 38/55] Add SafeOperationV07 tests --- .../packs/safe-4337/SafeOperationBase.test.ts | 35 ++++++ ...ation.test.ts => SafeOperationV06.test.ts} | 74 ++++-------- .../packs/safe-4337/SafeOperationV07.test.ts | 106 ++++++++++++++++++ .../pimlico/PimlicoFeeEstimator.test.ts | 12 +- .../packs/safe-4337/testing-utils/fixtures.ts | 23 +++- 5 files changed, 191 insertions(+), 59 deletions(-) create mode 100644 packages/relay-kit/src/packs/safe-4337/SafeOperationBase.test.ts rename packages/relay-kit/src/packs/safe-4337/{SafeOperation.test.ts => SafeOperationV06.test.ts} (58%) create mode 100644 packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.test.ts new file mode 100644 index 000000000..31ee7fe8e --- /dev/null +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.test.ts @@ -0,0 +1,35 @@ +import { EthSafeSignature } from '@safe-global/protocol-kit' +import SafeOperationV07 from './SafeOperationV07' +import * as fixtures from './testing-utils/fixtures' + +describe('SafeOperationBase', () => { + it('should add and retrieve signatures', () => { + const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V07, { + chainId: BigInt(fixtures.CHAIN_ID), + moduleAddress: fixtures.MODULE_ADDRESS, + entryPoint: fixtures.ENTRYPOINTS[1] + }) + + safeOperation.addSignature(new EthSafeSignature('0xSigner', '0xSignature')) + + expect(safeOperation.signatures.size).toBe(1) + expect(safeOperation.getSignature('0xSigner')).toMatchObject({ + signer: '0xSigner', + data: '0xSignature', + isContractSignature: false + }) + }) + + it('should encode the signatures', () => { + const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V07, { + chainId: BigInt(fixtures.CHAIN_ID), + moduleAddress: fixtures.MODULE_ADDRESS, + entryPoint: fixtures.ENTRYPOINTS[1] + }) + + safeOperation.addSignature(new EthSafeSignature('0xSigner1', '0xSignature1')) + safeOperation.addSignature(new EthSafeSignature('0xSigner2', '0xSignature2')) + + expect(safeOperation.encodedSignatures()).toBe('0xSignature1Signature2') + }) +}) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts similarity index 58% rename from packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts rename to packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts index 652fc3ab9..8dd9e0c28 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts @@ -3,25 +3,25 @@ import { EthSafeSignature } from '@safe-global/protocol-kit' import SafeOperationV06 from './SafeOperationV06' import * as fixtures from './testing-utils/fixtures' -describe('SafeOperation', () => { +describe('SafeOperationV06', () => { it('should create a SafeOperation from an UserOperation', () => { - const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, { + const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION_V06, { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, entryPoint: fixtures.ENTRYPOINTS[0] }) expect(safeOperation.getSafeOperation()).toMatchObject({ - safe: fixtures.USER_OPERATION.sender, - nonce: fixtures.USER_OPERATION.nonce, - initCode: fixtures.USER_OPERATION.initCode, - callData: fixtures.USER_OPERATION.callData, - callGasLimit: fixtures.USER_OPERATION.callGasLimit, - verificationGasLimit: fixtures.USER_OPERATION.verificationGasLimit, - preVerificationGas: fixtures.USER_OPERATION.preVerificationGas, - maxFeePerGas: fixtures.USER_OPERATION.maxFeePerGas, - maxPriorityFeePerGas: fixtures.USER_OPERATION.maxPriorityFeePerGas, - paymasterAndData: fixtures.USER_OPERATION.paymasterAndData, + safe: fixtures.USER_OPERATION_V06.sender, + nonce: fixtures.USER_OPERATION_V06.nonce, + initCode: fixtures.USER_OPERATION_V06.initCode, + callData: fixtures.USER_OPERATION_V06.callData, + callGasLimit: fixtures.USER_OPERATION_V06.callGasLimit, + verificationGasLimit: fixtures.USER_OPERATION_V06.verificationGasLimit, + preVerificationGas: fixtures.USER_OPERATION_V06.preVerificationGas, + maxFeePerGas: fixtures.USER_OPERATION_V06.maxFeePerGas, + maxPriorityFeePerGas: fixtures.USER_OPERATION_V06.maxPriorityFeePerGas, + paymasterAndData: fixtures.USER_OPERATION_V06.paymasterAndData, validAfter: 0, validUntil: 0, entryPoint: fixtures.ENTRYPOINTS[0] @@ -30,38 +30,8 @@ describe('SafeOperation', () => { expect(safeOperation.signatures.size).toBe(0) }) - it('should add and retrieve signatures', () => { - const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, { - chainId: BigInt(fixtures.CHAIN_ID), - moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[0] - }) - - safeOperation.addSignature(new EthSafeSignature('0xSigner', '0xSignature')) - - expect(safeOperation.signatures.size).toBe(1) - expect(safeOperation.getSignature('0xSigner')).toMatchObject({ - signer: '0xSigner', - data: '0xSignature', - isContractSignature: false - }) - }) - - it('should encode the signatures', () => { - const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, { - chainId: BigInt(fixtures.CHAIN_ID), - moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[0] - }) - - safeOperation.addSignature(new EthSafeSignature('0xSigner1', '0xSignature1')) - safeOperation.addSignature(new EthSafeSignature('0xSigner2', '0xSignature2')) - - expect(safeOperation.encodedSignatures()).toBe('0xSignature1Signature2') - }) - it('should add estimations', () => { - const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, { + const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION_V06, { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, entryPoint: fixtures.ENTRYPOINTS[0] @@ -74,16 +44,16 @@ describe('SafeOperation', () => { }) expect(safeOperation.getSafeOperation()).toMatchObject({ - safe: fixtures.USER_OPERATION.sender, - nonce: fixtures.USER_OPERATION.nonce, - initCode: fixtures.USER_OPERATION.initCode, - callData: fixtures.USER_OPERATION.callData, + safe: fixtures.USER_OPERATION_V06.sender, + nonce: fixtures.USER_OPERATION_V06.nonce, + initCode: fixtures.USER_OPERATION_V06.initCode, + callData: fixtures.USER_OPERATION_V06.callData, callGasLimit: BigInt(fixtures.GAS_ESTIMATION.callGasLimit), verificationGasLimit: BigInt(fixtures.GAS_ESTIMATION.verificationGasLimit), preVerificationGas: BigInt(fixtures.GAS_ESTIMATION.preVerificationGas), - maxFeePerGas: fixtures.USER_OPERATION.maxFeePerGas, - maxPriorityFeePerGas: fixtures.USER_OPERATION.maxPriorityFeePerGas, - paymasterAndData: fixtures.USER_OPERATION.paymasterAndData, + maxFeePerGas: fixtures.USER_OPERATION_V06.maxFeePerGas, + maxPriorityFeePerGas: fixtures.USER_OPERATION_V06.maxPriorityFeePerGas, + paymasterAndData: fixtures.USER_OPERATION_V06.paymasterAndData, validAfter: 0, validUntil: 0, entryPoint: fixtures.ENTRYPOINTS[0] @@ -91,7 +61,7 @@ describe('SafeOperation', () => { }) it('should retrieve the UserOperation', () => { - const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION, { + const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION_V06, { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.MODULE_ADDRESS, entryPoint: fixtures.ENTRYPOINTS[0] @@ -106,7 +76,7 @@ describe('SafeOperation', () => { expect(safeOperation.getUserOperation()).toMatchObject({ sender: safeOperation.userOperation.sender, - nonce: fixtures.USER_OPERATION.nonce, + nonce: fixtures.USER_OPERATION_V06.nonce, initCode: safeOperation.userOperation.initCode, callData: safeOperation.userOperation.callData, callGasLimit: safeOperation.userOperation.callGasLimit, diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts new file mode 100644 index 000000000..32676ce80 --- /dev/null +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts @@ -0,0 +1,106 @@ +import { Hex, concat, encodePacked } from 'viem' +import { EthSafeSignature } from '@safe-global/protocol-kit' +import SafeOperationV07 from './SafeOperationV07' +import * as fixtures from './testing-utils/fixtures' + +describe('SafeOperationV07', () => { + it('should create a SafeOperation from an UserOperation', () => { + const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V07, { + chainId: BigInt(fixtures.CHAIN_ID), + moduleAddress: fixtures.MODULE_ADDRESS, + entryPoint: fixtures.ENTRYPOINTS[1] + }) + + expect(safeOperation.getSafeOperation()).toMatchObject({ + safe: fixtures.USER_OPERATION_V07.sender, + nonce: fixtures.USER_OPERATION_V07.nonce, + initCode: concat([ + safeOperation.userOperation.factory as Hex, + (safeOperation.userOperation.factoryData as Hex) || ('0x' as Hex) + ]), + callData: fixtures.USER_OPERATION_V07.callData, + callGasLimit: fixtures.USER_OPERATION_V07.callGasLimit, + verificationGasLimit: fixtures.USER_OPERATION_V07.verificationGasLimit, + preVerificationGas: fixtures.USER_OPERATION_V07.preVerificationGas, + maxFeePerGas: fixtures.USER_OPERATION_V07.maxFeePerGas, + maxPriorityFeePerGas: fixtures.USER_OPERATION_V07.maxPriorityFeePerGas, + paymasterAndData: '0x', + validAfter: 0, + validUntil: 0, + entryPoint: fixtures.ENTRYPOINTS[1] + }) + + expect(safeOperation.signatures.size).toBe(0) + }) + + it('should add estimations', () => { + const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V07, { + chainId: BigInt(fixtures.CHAIN_ID), + moduleAddress: fixtures.MODULE_ADDRESS, + entryPoint: fixtures.ENTRYPOINTS[1] + }) + + safeOperation.addEstimations({ + callGasLimit: BigInt(fixtures.GAS_ESTIMATION.callGasLimit), + verificationGasLimit: BigInt(fixtures.GAS_ESTIMATION.verificationGasLimit), + preVerificationGas: BigInt(fixtures.GAS_ESTIMATION.preVerificationGas) + }) + + expect(safeOperation.getSafeOperation()).toMatchObject({ + safe: fixtures.USER_OPERATION_V07.sender, + nonce: fixtures.USER_OPERATION_V07.nonce, + initCode: concat([ + safeOperation.userOperation.factory as Hex, + (safeOperation.userOperation.factoryData as Hex) || ('0x' as Hex) + ]), + callData: fixtures.USER_OPERATION_V07.callData, + callGasLimit: BigInt(fixtures.GAS_ESTIMATION.callGasLimit), + verificationGasLimit: BigInt(fixtures.GAS_ESTIMATION.verificationGasLimit), + preVerificationGas: BigInt(fixtures.GAS_ESTIMATION.preVerificationGas), + maxFeePerGas: fixtures.USER_OPERATION_V07.maxFeePerGas, + maxPriorityFeePerGas: fixtures.USER_OPERATION_V07.maxPriorityFeePerGas, + paymasterAndData: '0x', + validAfter: 0, + validUntil: 0, + entryPoint: fixtures.ENTRYPOINTS[1] + }) + }) + + it('should retrieve the UserOperation', () => { + const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V07, { + chainId: BigInt(fixtures.CHAIN_ID), + moduleAddress: fixtures.MODULE_ADDRESS, + entryPoint: fixtures.ENTRYPOINTS[1] + }) + + safeOperation.addSignature( + new EthSafeSignature( + '0xSigner', + '0x000000000000000000000000a397ca32ee7fb5282256ee3465da0843485930b803d747516aac76e152f834051ac18fd2b3c0565590f9d65085538993c85c9bb189c940d15c15402c7c2885821b' + ) + ) + + expect(safeOperation.getUserOperation()).toMatchObject({ + sender: safeOperation.userOperation.sender, + nonce: fixtures.USER_OPERATION_V07.nonce, + factory: fixtures.USER_OPERATION_V07.factory, + factoryData: fixtures.USER_OPERATION_V07.factoryData, + callData: safeOperation.userOperation.callData, + callGasLimit: safeOperation.userOperation.callGasLimit, + verificationGasLimit: safeOperation.userOperation.verificationGasLimit, + preVerificationGas: safeOperation.userOperation.preVerificationGas, + maxFeePerGas: safeOperation.userOperation.maxFeePerGas, + maxPriorityFeePerGas: safeOperation.userOperation.maxPriorityFeePerGas, + paymaster: safeOperation.userOperation.paymaster, + paymasterData: safeOperation.userOperation.paymasterData, + signature: encodePacked( + ['uint48', 'uint48', 'bytes'], + [ + safeOperation.options.validAfter || 0, + safeOperation.options.validUntil || 0, + safeOperation.encodedSignatures() as Hex + ] + ) + }) + }) +}) diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts index 875b931cd..2690e6204 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts @@ -30,8 +30,8 @@ describe('PimlicoFeeEstimator', () => { it('should enable to setup the gas estimation', async () => { const sponsoredGasEstimation = await estimator.setupEstimation({ bundlerUrl: fixtures.BUNDLER_URL, - userOperation: fixtures.USER_OPERATION, - entryPoint: fixtures.ENTRYPOINTS[0] + userOperation: fixtures.USER_OPERATION_V07, + entryPoint: fixtures.ENTRYPOINTS[1] }) expect(sponsoredGasEstimation).toEqual({ @@ -43,8 +43,8 @@ describe('PimlicoFeeEstimator', () => { it('should enable to adjust the gas estimation', async () => { const sponsoredGasEstimation = await estimator.adjustEstimation({ bundlerUrl: fixtures.BUNDLER_URL, - userOperation: fixtures.USER_OPERATION, - entryPoint: fixtures.ENTRYPOINTS[0] + userOperation: fixtures.USER_OPERATION_V07, + entryPoint: fixtures.ENTRYPOINTS[1] }) expect(sponsoredGasEstimation).toEqual({}) @@ -52,14 +52,14 @@ describe('PimlicoFeeEstimator', () => { it('should get the paymaster estimation', async () => { const paymasterGasEstimation = await estimator.getPaymasterEstimation({ - userOperation: fixtures.USER_OPERATION, + userOperation: fixtures.USER_OPERATION_V07, bundlerUrl: fixtures.BUNDLER_URL, paymasterOptions: { paymasterUrl: fixtures.PAYMASTER_URL, paymasterAddress: fixtures.PAYMASTER_ADDRESS, paymasterTokenAddress: fixtures.PAYMASTER_TOKEN_ADDRESS }, - entryPoint: fixtures.ENTRYPOINTS[0] + entryPoint: fixtures.ENTRYPOINTS[1] }) expect(paymasterGasEstimation).toEqual(fixtures.SPONSORED_GAS_ESTIMATION) diff --git a/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts b/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts index 8648ddda9..cc2152efb 100644 --- a/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts +++ b/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts @@ -51,7 +51,7 @@ export const USER_OPERATION_RECEIPT = { } } -export const USER_OPERATION = { +export const USER_OPERATION_V06 = { sender: '0x1405B3659a11a16459fc27Fa1925b60388C38Ce1', nonce: '1', initCode: '0x', @@ -67,6 +67,27 @@ export const USER_OPERATION = { '0x000000000000000000000000a397ca32ee7fb5282256ee3465da0843485930b803d747516aac76e152f834051ac18fd2b3c0565590f9d65085538993c85c9bb189c940d15c15402c7c2885821b' } +export const USER_OPERATION_V07 = { + sender: '0x26874a65eA7B6B6655e4582c8D215e1De05dd39b', + nonce: '0x0', + factory: '0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67', + factoryData: + '0x1688f0b900000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c7620000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000009a15e37d88ba5900000000000000000000000000000000000000000000000000000000000001e4b63e800d000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000002dd68b007b46fbe91b9a7c3eda5a7a1063cb5b47000000000000000000000000000000000000000000000000000000000000014000000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c2260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bc16a6fbc93f62187a137f30c92e3f90bbbaa49200000000000000000000000000000000000000000000000000000000000000648d0dc49f0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c2260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + callData: + '0x7bb3742800000000000000000000000038869bf66a61cf6bdb996a6ae40d5853fd43b52600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001848d80ff0a0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000013200fc3e86566895fb007c6a0d3809eb2827df94f75100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000bc16a6fbc93f62187a137f30c92e3f90bbbaa49200000000000000000000000000000000000000000000000000000000000186a000fc3e86566895fb007c6a0d3809eb2827df94f75100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000bc16a6fbc93f62187a137f30c92e3f90bbbaa49200000000000000000000000000000000000000000000000000000000000186a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + callGasLimit: 120_784n, + verificationGasLimit: 83_056n, + preVerificationGas: 48_568n, + maxFeePerGas: 193_584_757_388n, + maxPriorityFeePerGas: 1_380_000_000n, + paymaster: undefined, + paymasterVerificationGasLimit: undefined, + paymasterPostOpGasLimit: undefined, + paymasterData: undefined, + signature: + '0x0000679fa3ac000067a1786c8c012f3bef75848690703f17ab0519669bc38bc2629bd8b3118f6280936933fa675bc52dde81cc71c3e0c4587e17ddecf21f845a7a34862b586776501845b1511c' +} + export const USER_OPERATION_HEX_VALUES = { sender: '0x1405B3659a11a16459fc27Fa1925b60388C38Ce1', nonce: '0x1', From 2e9e13681e565302912ab78718991d7cc2eb467e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Mon, 3 Feb 2025 15:38:45 +0100 Subject: [PATCH 39/55] Fix tests --- .../tests/e2e/addSafeOperation.test.ts | 2 +- .../src/packs/safe-4337/Safe4337Pack.test.ts | 124 ++++++++++-------- .../packs/safe-4337/SafeOperationBase.test.ts | 10 +- .../packs/safe-4337/SafeOperationV06.test.ts | 16 +-- .../packs/safe-4337/SafeOperationV07.test.ts | 16 +-- .../pimlico/PimlicoFeeEstimator.test.ts | 6 +- .../packs/safe-4337/testing-utils/fixtures.ts | 12 +- .../packs/safe-4337/testing-utils/helpers.ts | 2 +- 8 files changed, 103 insertions(+), 85 deletions(-) diff --git a/packages/api-kit/tests/e2e/addSafeOperation.test.ts b/packages/api-kit/tests/e2e/addSafeOperation.test.ts index 45c97a081..632725a4d 100644 --- a/packages/api-kit/tests/e2e/addSafeOperation.test.ts +++ b/packages/api-kit/tests/e2e/addSafeOperation.test.ts @@ -180,7 +180,7 @@ describe('addSafeOperation', () => { chai.expect(safeOperationsAfter.count).to.equal(initialNumSafeOperations + 1) }) - it.only('should add a new SafeOperation using a SafeOperation object from the relay-kit', async () => { + it('should add a new SafeOperation using a SafeOperation object from the relay-kit', async () => { const safeOperation = await safe4337Pack.createTransaction({ transactions: [transferUSDC, transferUSDC] }) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts index 70d0ed517..7d9b78810 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts @@ -19,7 +19,10 @@ import * as utils from './utils' dotenv.config() const requestResponseMap = { - [constants.RPC_4337_CALLS.SUPPORTED_ENTRY_POINTS]: fixtures.ENTRYPOINTS, + [constants.RPC_4337_CALLS.SUPPORTED_ENTRY_POINTS]: [ + fixtures.ENTRYPOINT_ADDRESS_V06, + fixtures.ENTRYPOINT_ADDRESS_V07 + ], [constants.RPC_4337_CALLS.CHAIN_ID]: fixtures.CHAIN_ID, [constants.RPC_4337_CALLS.SEND_USER_OPERATION]: fixtures.USER_OPERATION_HASH, [constants.RPC_4337_CALLS.ESTIMATE_USER_OPERATION_GAS]: fixtures.GAS_ESTIMATION, @@ -45,12 +48,12 @@ describe('Safe4337Pack', () => { const network = parseInt(fixtures.CHAIN_ID).toString() safe4337ModuleAddress = getSafe4337ModuleDeployment({ released: true, - version: '0.2.0', + version: '0.3.0', network })?.networkAddresses[network] as viem.Hash safeModulesSetupAddress = getSafeModuleSetupDeployment({ released: true, - version: '0.2.0', + version: '0.3.0', network })?.networkAddresses[network] as string }) @@ -71,20 +74,22 @@ describe('Safe4337Pack', () => { it('should throw an error if the 4337 Module is not enabled in the Safe account', async () => { await expect( createSafe4337Pack({ - options: { safeAddress: fixtures.SAFE_ADDRESS_4337_MODULE_NOT_ENABLED } + options: { safeAddress: fixtures.SAFE_ADDRESS_4337_MODULE_NOT_ENABLED }, + safeModulesVersion: '0.2.0' }) ).rejects.toThrow( - 'Incompatibility detected: The EIP-4337 module is not enabled in the provided Safe Account. Enable this module (address: 0xa581c4A4DB7175302464fF3C06380BC3270b4037) to add compatibility.' + `Incompatibility detected: The EIP-4337 module is not enabled in the provided Safe Account. Enable this module (address: ${fixtures.SAFE_4337_MODULE_ADDRESS_V0_2_0}) to add compatibility.` ) }) it('should throw an error if the 4337 fallbackhandler is not attached to the Safe account', async () => { await expect( createSafe4337Pack({ - options: { safeAddress: fixtures.SAFE_ADDRESS_4337_FALLBACKHANDLER_NOT_ENABLED } + options: { safeAddress: fixtures.SAFE_ADDRESS_4337_FALLBACKHANDLER_NOT_ENABLED }, + safeModulesVersion: '0.2.0' }) ).rejects.toThrow( - 'Incompatibility detected: The EIP-4337 fallbackhandler is not attached to the Safe Account. Attach this fallbackhandler (address: 0xa581c4A4DB7175302464fF3C06380BC3270b4037) to ensure compatibility.' + `Incompatibility detected: The EIP-4337 fallbackhandler is not attached to the Safe Account. Attach this fallbackhandler (address: ${fixtures.SAFE_4337_MODULE_ADDRESS_V0_2_0}) to ensure compatibility.` ) }) }) @@ -93,17 +98,17 @@ describe('Safe4337Pack', () => { it('should throw an error if the version of the entrypoint used is incompatible', async () => { await expect( createSafe4337Pack({ - options: { safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 }, - customContracts: { entryPointAddress: fixtures.ENTRYPOINTS[1] } + options: { safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE }, + customContracts: { entryPointAddress: fixtures.ENTRYPOINT_ADDRESS_V06 } }) ).rejects.toThrow( - `The selected entrypoint ${fixtures.ENTRYPOINTS[1]} is not compatible with version 0.2.0 of Safe modules` + `The selected entrypoint ${fixtures.ENTRYPOINT_ADDRESS_V06} is not compatible with version 0.3.0 of Safe modules` ) }) it('should throw an error if no supported entrypoints are available', async () => { const overridenMap = Object.assign({}, requestResponseMap, { - [constants.RPC_4337_CALLS.SUPPORTED_ENTRY_POINTS]: [fixtures.ENTRYPOINTS[1]] + [constants.RPC_4337_CALLS.SUPPORTED_ENTRY_POINTS]: [fixtures.ENTRYPOINT_ADDRESS_V06] }) const mockedUtils = jest.requireMock('./utils') @@ -115,30 +120,32 @@ describe('Safe4337Pack', () => { await expect( createSafe4337Pack({ - options: { safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 } + options: { safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE } }) ).rejects.toThrow( - `Incompatibility detected: None of the entrypoints provided by the bundler is compatible with the Safe modules version 0.2.0` + `Incompatibility detected: None of the entrypoints provided by the bundler is compatible with the Safe modules version 0.3.0` ) }) it('should be able to instantiate the pack using a existing Safe', async () => { const safe4337Pack = await createSafe4337Pack({ - options: { safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 } + options: { safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE } }) expect(safe4337Pack).toBeInstanceOf(Safe4337Pack) expect(safe4337Pack.protocolKit).toBeInstanceOf(Safe) - expect(await safe4337Pack.protocolKit.getAddress()).toBe(fixtures.SAFE_ADDRESS_v1_4_1) + expect(await safe4337Pack.protocolKit.getAddress()).toBe( + fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE + ) expect(await safe4337Pack.getChainId()).toBe(fixtures.CHAIN_ID) }) it('should have the 4337 module enabled', async () => { const safe4337Pack = await createSafe4337Pack({ - options: { safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 } + options: { safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE } }) - expect(await safe4337Pack.protocolKit.getModules()).toEqual([safe4337ModuleAddress]) + expect(await safe4337Pack.protocolKit.getModules()).toContain(safe4337ModuleAddress) }) it('should detect if a custom 4337 module is not enabled in the Safe', async () => { @@ -148,7 +155,7 @@ describe('Safe4337Pack', () => { safe4337ModuleAddress: '0xCustomModule' }, options: { - safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 + safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE } }) ).rejects.toThrow( @@ -159,7 +166,7 @@ describe('Safe4337Pack', () => { it('should use the 4337 module as the fallback handler', async () => { const safe4337Pack = await createSafe4337Pack({ options: { - safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 + safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE } }) @@ -186,10 +193,10 @@ describe('Safe4337Pack', () => { owners: [fixtures.OWNER_1], threshold: 1 }, - customContracts: { entryPointAddress: fixtures.ENTRYPOINTS[1] } + customContracts: { entryPointAddress: fixtures.ENTRYPOINT_ADDRESS_V06 } }) ).rejects.toThrow( - `The selected entrypoint ${fixtures.ENTRYPOINTS[1]} is not compatible with version 0.2.0 of Safe modules` + `The selected entrypoint ${fixtures.ENTRYPOINT_ADDRESS_V06} is not compatible with version 0.3.0 of Safe modules` ) }) @@ -343,13 +350,13 @@ describe('Safe4337Pack', () => { beforeAll(async () => { safe4337Pack = await createSafe4337Pack({ options: { - safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 + safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE } }) transferUSDC = { to: fixtures.PAYMASTER_TOKEN_ADDRESS, - data: generateTransferCallData(fixtures.SAFE_ADDRESS_v1_4_1, 100_000n), + data: generateTransferCallData(fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, 100_000n), value: '0', operation: 0 } @@ -364,8 +371,8 @@ describe('Safe4337Pack', () => { expect(safeOperation).toBeInstanceOf(SafeOperationBase) expect(safeOperation.getSafeOperation()).toMatchObject({ - safe: fixtures.SAFE_ADDRESS_v1_4_1, - entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', + safe: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07, initCode: '0x', paymasterAndData: '0x', callData: viem.encodeFunctionData({ @@ -400,8 +407,8 @@ describe('Safe4337Pack', () => { expect(safeOperation).toBeInstanceOf(SafeOperationBase) expect(safeOperation.getSafeOperation()).toMatchObject({ - safe: fixtures.SAFE_ADDRESS_v1_4_1, - entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', + safe: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07, initCode: '0x', paymasterAndData: '0x', callData: viem.encodeFunctionData({ @@ -441,14 +448,14 @@ describe('Safe4337Pack', () => { expect(getInitCodeSpy).toHaveBeenCalled() expect(safeOperation.getSafeOperation().initCode).toBe( - '0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec671688f0b900000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c7620000000000000000000000000000000000000000000000000000000000000060ad27de2a410652abce96ea0fdfc30c2f0fd35952b78f554667111999a28ff33800000000000000000000000000000000000000000000000000000000000001e4b63e800d000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000008ecd4ec46d4d2a6b64fe960b3d64e8b94b2234eb0000000000000000000000000000000000000000000000000000000000000140000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b40370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000ffac5578be8ac1b2b9d13b34caf4a074b96b8a1b00000000000000000000000000000000000000000000000000000000000000648d0dc49f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000a581c4a4db7175302464ff3c06380bc3270b40370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + '0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec671688f0b900000000000000000000000029fcb43b46531bca003ddc8fcb67ffe91900c7620000000000000000000000000000000000000000000000000000000000000060ad27de2a410652abce96ea0fdfc30c2f0fd35952b78f554667111999a28ff33800000000000000000000000000000000000000000000000000000000000001e4b63e800d000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000002dd68b007b46fbe91b9a7c3eda5a7a1063cb5b47000000000000000000000000000000000000000000000000000000000000014000000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c2260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000ffac5578be8ac1b2b9d13b34caf4a074b96b8a1b00000000000000000000000000000000000000000000000000000000000000648d0dc49f0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000075cf11467937ce3f2f357ce24ffc3dbf8fd5c2260000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' ) }) it('should allow to create a sponsored transaction', async () => { const safe4337Pack = await createSafe4337Pack({ options: { - safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 + safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE }, paymasterOptions: { isSponsored: true, @@ -462,8 +469,8 @@ describe('Safe4337Pack', () => { expect(sponsoredSafeOperation).toBeInstanceOf(SafeOperationBase) expect(sponsoredSafeOperation.getSafeOperation()).toMatchObject({ - safe: fixtures.SAFE_ADDRESS_v1_4_1, - entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', + safe: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07, initCode: '0x', paymasterAndData: '0x', callData: viem.encodeFunctionData({ @@ -490,7 +497,7 @@ describe('Safe4337Pack', () => { it('should add the approve transaction to the batch when amountToApprove is provided', async () => { const safe4337Pack = await createSafe4337Pack({ options: { - safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 + safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE }, paymasterOptions: { paymasterUrl: fixtures.PAYMASTER_URL, @@ -523,10 +530,11 @@ describe('Safe4337Pack', () => { expect(sponsoredSafeOperation).toBeInstanceOf(SafeOperationBase) expect(sponsoredSafeOperation.getSafeOperation()).toMatchObject({ - safe: fixtures.SAFE_ADDRESS_v1_4_1, - entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', + safe: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07, initCode: '0x', - paymasterAndData: '0x0000000000325602a77416A16136FDafd04b299f', + paymasterAndData: + '0x0000000000325602a77416A16136FDafd04b299f0000000000000000000000000000000000000000000000000000000000000000', callData: viem.encodeFunctionData({ abi: constants.ABI, functionName: 'executeUserOp', @@ -719,6 +727,7 @@ describe('Safe4337Pack', () => { const safe4337Pack = await createSafe4337Pack({ signer: passkey, + safeModulesVersion: '0.2.0', options: { safeAddress: fixtures.SAFE_ADDRESS_4337_PASSKEY } @@ -734,8 +743,11 @@ describe('Safe4337Pack', () => { expect(requestMock).toHaveBeenCalledWith({ method: constants.RPC_4337_CALLS.SEND_USER_OPERATION, params: [ - utils.userOperationToHexValues(safeOperation.getUserOperation(), fixtures.ENTRYPOINTS[0]), - fixtures.ENTRYPOINTS[0] + utils.userOperationToHexValues( + safeOperation.getUserOperation(), + fixtures.ENTRYPOINT_ADDRESS_V06 + ), + fixtures.ENTRYPOINT_ADDRESS_V06 ] }) }) @@ -744,14 +756,14 @@ describe('Safe4337Pack', () => { it('should allow to sign a SafeOperation', async () => { const transferUSDC = { to: fixtures.PAYMASTER_TOKEN_ADDRESS, - data: generateTransferCallData(fixtures.SAFE_ADDRESS_v1_4_1, 100_000n), + data: generateTransferCallData(fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, 100_000n), value: '0', operation: 0 } const safe4337Pack = await createSafe4337Pack({ options: { - safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 + safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE } }) @@ -764,7 +776,7 @@ describe('Safe4337Pack', () => { fixtures.OWNER_1.toLowerCase(), new protocolKit.EthSafeSignature( fixtures.OWNER_1, - '0x316dda79337aa7c03f8bab2e752ffcb27904cd1be433f7c5eb1a9b70d13770ba4285fa0d0a61e4126770594cc7d9b91fc3cfb19927ad5b8d268aeec8c5473a231b', + '0x341b48cbc73a74905d3e52f96329cd994043b8cc261d5f2d2fc87875c6a0e987241e09f0ceb7a061e6c058e65fd3e2f9d3b47f56cad00c4e02cf62fed012a8bb1c', false ) ) @@ -774,7 +786,7 @@ describe('Safe4337Pack', () => { it('should allow to sign a SafeOperation using a SafeOperationResponse object from the api to add a signature', async () => { const safe4337Pack = await createSafe4337Pack({ options: { - safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 + safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE } }) @@ -784,7 +796,7 @@ describe('Safe4337Pack', () => { fixtures.OWNER_1.toLowerCase(), new protocolKit.EthSafeSignature( fixtures.OWNER_1, - '0xcb28e74375889e400a4d8aca46b8c59e1cf8825e373c26fa99c2fd7c078080e64fe30eaf1125257bdfe0b358b5caef68aa0420478145f52decc8e74c979d43ab1c', + '0x6fa024afd110bee3832dd9507b5ce2bf1bb097363ba63b887b1a44f5a7b89e3b5d32ff9dbb5fee63f0bf44df1b427d7a7e69451b3c05d25fb49f77fe2fd044141b', false ) ) @@ -802,14 +814,14 @@ describe('Safe4337Pack', () => { it('should allow to send an UserOperation to a bundler', async () => { const transferUSDC = { to: fixtures.PAYMASTER_TOKEN_ADDRESS, - data: generateTransferCallData(fixtures.SAFE_ADDRESS_v1_4_1, 100_000n), + data: generateTransferCallData(fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, 100_000n), value: '0', operation: 0 } const safe4337Pack = await createSafe4337Pack({ options: { - safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 + safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE } }) const readContractSpy = jest.spyOn(safe4337Pack.protocolKit.getSafeProvider(), 'readContract') @@ -818,10 +830,10 @@ describe('Safe4337Pack', () => { transactions: [transferUSDC] }) expect(readContractSpy).toHaveBeenCalledWith({ - address: constants.ENTRYPOINT_ADDRESS_V06, + address: constants.ENTRYPOINT_ADDRESS_V07, abi: constants.ENTRYPOINT_ABI, functionName: 'getNonce', - args: [fixtures.SAFE_ADDRESS_v1_4_1, 0n] + args: [fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, 0n] }) safeOperation = await safe4337Pack.signSafeOperation(safeOperation) @@ -831,16 +843,20 @@ describe('Safe4337Pack', () => { expect(requestMock).toHaveBeenCalledWith({ method: constants.RPC_4337_CALLS.SEND_USER_OPERATION, params: [ - utils.userOperationToHexValues(safeOperation.getUserOperation(), fixtures.ENTRYPOINTS[0]), - fixtures.ENTRYPOINTS[0] + utils.userOperationToHexValues( + safeOperation.getUserOperation(), + fixtures.ENTRYPOINT_ADDRESS_V07 + ), + fixtures.ENTRYPOINT_ADDRESS_V07 ] }) }) it('should allow to send a UserOperation to the bundler using a SafeOperationResponse object from the api', async () => { const safe4337Pack = await createSafe4337Pack({ + safeModulesVersion: '0.2.0', options: { - safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 + safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_2_0_MODULE } }) @@ -865,9 +881,9 @@ describe('Safe4337Pack', () => { signature: '0x000000000000000000000000cb28e74375889e400a4d8aca46b8c59e1cf8825e373c26fa99c2fd7c078080e64fe30eaf1125257bdfe0b358b5caef68aa0420478145f52decc8e74c979d43ab1d' }, - fixtures.ENTRYPOINTS[0] + fixtures.ENTRYPOINT_ADDRESS_V06 ), - fixtures.ENTRYPOINTS[0] + fixtures.ENTRYPOINT_ADDRESS_V06 ] }) }) @@ -875,7 +891,7 @@ describe('Safe4337Pack', () => { it('should return a UserOperation based on a userOpHash', async () => { const safe4337Pack = await createSafe4337Pack({ options: { - safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 + safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE } }) @@ -911,7 +927,7 @@ describe('Safe4337Pack', () => { it('should return a UserOperation receipt based on a userOpHash', async () => { const safe4337Pack = await createSafe4337Pack({ options: { - safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 + safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE } }) @@ -949,7 +965,7 @@ describe('Safe4337Pack', () => { it('should return an array of the entryPoint addresses supported by the client', async () => { const safe4337Pack = await createSafe4337Pack({ options: { - safeAddress: fixtures.SAFE_ADDRESS_v1_4_1 + safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE } }) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.test.ts index 31ee7fe8e..5af4b9ef0 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.test.ts @@ -3,11 +3,11 @@ import SafeOperationV07 from './SafeOperationV07' import * as fixtures from './testing-utils/fixtures' describe('SafeOperationBase', () => { - it('should add and retrieve signatures', () => { + it('should add and get signatures', () => { const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V07, { chainId: BigInt(fixtures.CHAIN_ID), - moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[1] + moduleAddress: fixtures.SAFE_4337_MODULE_ADDRESS_V0_3_0, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 }) safeOperation.addSignature(new EthSafeSignature('0xSigner', '0xSignature')) @@ -23,8 +23,8 @@ describe('SafeOperationBase', () => { it('should encode the signatures', () => { const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V07, { chainId: BigInt(fixtures.CHAIN_ID), - moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[1] + moduleAddress: fixtures.SAFE_4337_MODULE_ADDRESS_V0_3_0, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 }) safeOperation.addSignature(new EthSafeSignature('0xSigner1', '0xSignature1')) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts index 8dd9e0c28..3beb813f0 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts @@ -7,8 +7,8 @@ describe('SafeOperationV06', () => { it('should create a SafeOperation from an UserOperation', () => { const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION_V06, { chainId: BigInt(fixtures.CHAIN_ID), - moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[0] + moduleAddress: fixtures.SAFE_4337_MODULE_ADDRESS_V0_2_0, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V06 }) expect(safeOperation.getSafeOperation()).toMatchObject({ @@ -24,7 +24,7 @@ describe('SafeOperationV06', () => { paymasterAndData: fixtures.USER_OPERATION_V06.paymasterAndData, validAfter: 0, validUntil: 0, - entryPoint: fixtures.ENTRYPOINTS[0] + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V06 }) expect(safeOperation.signatures.size).toBe(0) @@ -33,8 +33,8 @@ describe('SafeOperationV06', () => { it('should add estimations', () => { const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION_V06, { chainId: BigInt(fixtures.CHAIN_ID), - moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[0] + moduleAddress: fixtures.SAFE_4337_MODULE_ADDRESS_V0_2_0, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V06 }) safeOperation.addEstimations({ @@ -56,15 +56,15 @@ describe('SafeOperationV06', () => { paymasterAndData: fixtures.USER_OPERATION_V06.paymasterAndData, validAfter: 0, validUntil: 0, - entryPoint: fixtures.ENTRYPOINTS[0] + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V06 }) }) it('should retrieve the UserOperation', () => { const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION_V06, { chainId: BigInt(fixtures.CHAIN_ID), - moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[0] + moduleAddress: fixtures.SAFE_4337_MODULE_ADDRESS_V0_2_0, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V06 }) safeOperation.addSignature( diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts index 32676ce80..159019c6b 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts @@ -7,8 +7,8 @@ describe('SafeOperationV07', () => { it('should create a SafeOperation from an UserOperation', () => { const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V07, { chainId: BigInt(fixtures.CHAIN_ID), - moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[1] + moduleAddress: fixtures.SAFE_4337_MODULE_ADDRESS_V0_3_0, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 }) expect(safeOperation.getSafeOperation()).toMatchObject({ @@ -27,7 +27,7 @@ describe('SafeOperationV07', () => { paymasterAndData: '0x', validAfter: 0, validUntil: 0, - entryPoint: fixtures.ENTRYPOINTS[1] + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 }) expect(safeOperation.signatures.size).toBe(0) @@ -36,8 +36,8 @@ describe('SafeOperationV07', () => { it('should add estimations', () => { const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V07, { chainId: BigInt(fixtures.CHAIN_ID), - moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[1] + moduleAddress: fixtures.SAFE_4337_MODULE_ADDRESS_V0_3_0, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 }) safeOperation.addEstimations({ @@ -62,15 +62,15 @@ describe('SafeOperationV07', () => { paymasterAndData: '0x', validAfter: 0, validUntil: 0, - entryPoint: fixtures.ENTRYPOINTS[1] + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 }) }) it('should retrieve the UserOperation', () => { const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V07, { chainId: BigInt(fixtures.CHAIN_ID), - moduleAddress: fixtures.MODULE_ADDRESS, - entryPoint: fixtures.ENTRYPOINTS[1] + moduleAddress: fixtures.SAFE_4337_MODULE_ADDRESS_V0_3_0, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 }) safeOperation.addSignature( diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts index 2690e6204..21baae454 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts @@ -31,7 +31,7 @@ describe('PimlicoFeeEstimator', () => { const sponsoredGasEstimation = await estimator.setupEstimation({ bundlerUrl: fixtures.BUNDLER_URL, userOperation: fixtures.USER_OPERATION_V07, - entryPoint: fixtures.ENTRYPOINTS[1] + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 }) expect(sponsoredGasEstimation).toEqual({ @@ -44,7 +44,7 @@ describe('PimlicoFeeEstimator', () => { const sponsoredGasEstimation = await estimator.adjustEstimation({ bundlerUrl: fixtures.BUNDLER_URL, userOperation: fixtures.USER_OPERATION_V07, - entryPoint: fixtures.ENTRYPOINTS[1] + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 }) expect(sponsoredGasEstimation).toEqual({}) @@ -59,7 +59,7 @@ describe('PimlicoFeeEstimator', () => { paymasterAddress: fixtures.PAYMASTER_ADDRESS, paymasterTokenAddress: fixtures.PAYMASTER_TOKEN_ADDRESS }, - entryPoint: fixtures.ENTRYPOINTS[1] + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 }) expect(paymasterGasEstimation).toEqual(fixtures.SPONSORED_GAS_ESTIMATION) diff --git a/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts b/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts index cc2152efb..825ec441c 100644 --- a/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts +++ b/packages/relay-kit/src/packs/safe-4337/testing-utils/fixtures.ts @@ -1,10 +1,10 @@ import { SignatureTypes } from '@safe-global/types-kit' -import { ENTRYPOINT_ADDRESS_V06, ENTRYPOINT_ADDRESS_V07 } from '../constants' export const OWNER_1 = '0xFfAC5578BE8AC1B2B9D13b34cAf4A074B96B8A1b' export const OWNER_2 = '0x3059EfD1BCe33be41eeEfd5fb6D520d7fEd54E43' -export const PREDICTED_SAFE_ADDRESS = '0x65e0d294F2d17CB9fB0f65111E9Ac8a00C4049dA' -export const SAFE_ADDRESS_v1_4_1 = '0x717f4BB83D8DF2e5a3Cc603Ee27263ac9EFB6c12' +export const PREDICTED_SAFE_ADDRESS = '0xB71d0a777A692870163FFfd777094217a52DD9e4' +export const SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE = '0x5f92e52CD555539a0D30c81FcF6703c04E11dA48' +export const SAFE_ADDRESS_v1_4_1_WITH_0_2_0_MODULE = '0x717f4BB83D8DF2e5a3Cc603Ee27263ac9EFB6c12' export const SAFE_ADDRESS_v1_3_0 = '0x8C35a08Af278518B59D04ddDe3F1b370aD766D22' export const SAFE_ADDRESS_4337_MODULE_NOT_ENABLED = '0xfC82a1e4A045a44527e8b45FC70332C8F66fc32B' export const SAFE_ADDRESS_4337_FALLBACKHANDLER_NOT_ENABLED = @@ -14,7 +14,8 @@ export const SAFE_MODULES_V0_3_0 = '0.3.0' export const PAYMASTER_ADDRESS = '0x0000000000325602a77416A16136FDafd04b299f' export const PAYMASTER_TOKEN_ADDRESS = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' export const CHAIN_ID = '0xaa36a7' -export const MODULE_ADDRESS = '0xa581c4A4DB7175302464fF3C06380BC3270b4037' +export const SAFE_4337_MODULE_ADDRESS_V0_2_0 = '0xa581c4A4DB7175302464fF3C06380BC3270b4037' +export const SAFE_4337_MODULE_ADDRESS_V0_3_0 = '0x75cf11467937ce3F2f357CE24ffc3DBF8fD5c226' export const SHARED_SIGNER = '0x' export const RPC_URL = 'https://sepolia.gateway.tenderly.co' export const BUNDLER_URL = 'https://bundler.url' @@ -23,7 +24,8 @@ export const PAYMASTER_URL = 'https://paymaster.url' export const USER_OPERATION_HASH = '0x3cb881d1969036174f38d636d22108d1d032145518b53104fc0b1e1296d2cc9c' -export const ENTRYPOINTS = [ENTRYPOINT_ADDRESS_V06, ENTRYPOINT_ADDRESS_V07] +export const ENTRYPOINT_ADDRESS_V06 = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789' +export const ENTRYPOINT_ADDRESS_V07 = '0x0000000071727De22E5E9d8BAf0edAc6f37da032' export const USER_OPERATION_RECEIPT = { userOpHash: '0x3cb881d1969036174f38d636d22108d1d032145518b53104fc0b1e1296d2cc9c', diff --git a/packages/relay-kit/src/packs/safe-4337/testing-utils/helpers.ts b/packages/relay-kit/src/packs/safe-4337/testing-utils/helpers.ts index 2b2094c84..d95e71d55 100644 --- a/packages/relay-kit/src/packs/safe-4337/testing-utils/helpers.ts +++ b/packages/relay-kit/src/packs/safe-4337/testing-utils/helpers.ts @@ -27,7 +27,7 @@ export const createSafe4337Pack = async ( const safe4337Pack = await Safe4337Pack.init({ provider: fixtures.RPC_URL, signer: process.env.PRIVATE_KEY, - safeModulesVersion: '0.2.0', + safeModulesVersion: initOptions.safeModulesVersion, options: { safeAddress: '' }, From f7ce30b1054bff9972ad75d6339b839cd791a09a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Tue, 4 Feb 2025 11:32:37 +0100 Subject: [PATCH 40/55] Improve gas estimation --- .../src/packs/safe-4337/Safe4337Pack.ts | 34 ++++++------------- .../pimlico/PimlicoFeeEstimator.test.ts | 18 +++------- .../estimators/pimlico/PimlicoFeeEstimator.ts | 23 ++++--------- .../relay-kit/src/packs/safe-4337/types.ts | 5 ++- 4 files changed, 24 insertions(+), 56 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index e97ab147c..55f7ccd49 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -407,15 +407,15 @@ export class Safe4337Pack extends RelayKitBasePack<{ feeEstimator = new PimlicoFeeEstimator() }: EstimateFeeProps): Promise { const threshold = await this.protocolKit.getThreshold() - const setupEstimationData = await feeEstimator?.setupEstimation?.({ + const preEstimationData = await feeEstimator?.preEstimateUserOperationGas?.({ bundlerUrl: this.#BUNDLER_URL, entryPoint: this.#ENTRYPOINT_ADDRESS, userOperation: safeOperation.getUserOperation(), paymasterOptions: this.#paymasterOptions }) - if (setupEstimationData) { - safeOperation.addEstimations(setupEstimationData) + if (preEstimationData) { + safeOperation.addEstimations(preEstimationData) } const estimateUserOperationGas = await this.#bundlerClient.request({ @@ -433,30 +433,18 @@ export class Safe4337Pack extends RelayKitBasePack<{ safeOperation.addEstimations(estimateUserOperationGas) } - const adjustEstimationData = await feeEstimator?.adjustEstimation?.({ + const postEstimationData = await feeEstimator?.postEstimateUserOperationGas?.({ bundlerUrl: this.#BUNDLER_URL, entryPoint: this.#ENTRYPOINT_ADDRESS, - userOperation: safeOperation.getUserOperation() + userOperation: { + ...safeOperation.getUserOperation(), + signature: getDummySignature(this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, threshold) + }, + paymasterOptions: this.#paymasterOptions }) - if (adjustEstimationData) { - safeOperation.addEstimations(adjustEstimationData) - } - - if (this.#paymasterOptions) { - const paymasterEstimation = await feeEstimator?.getPaymasterEstimation?.({ - userOperation: { - ...safeOperation.getUserOperation(), - signature: getDummySignature(this.#SAFE_WEBAUTHN_SHARED_SIGNER_ADDRESS, threshold) - }, - bundlerUrl: this.#BUNDLER_URL, - entryPoint: this.#ENTRYPOINT_ADDRESS, - paymasterOptions: this.#paymasterOptions - }) - - if (paymasterEstimation) { - safeOperation.addEstimations(paymasterEstimation) - } + if (postEstimationData) { + safeOperation.addEstimations(postEstimationData) } return safeOperation diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts index 941515c9d..f6680a3c4 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.test.ts @@ -27,8 +27,8 @@ describe('PimlicoFeeEstimator', () => { estimator = new PimlicoFeeEstimator() }) - it('should enable to setup the gas estimation', async () => { - const sponsoredGasEstimation = await estimator.setupEstimation({ + it('should enable to setup the user operation for gas estimation before calling eth_estimateUserOperationGas', async () => { + const sponsoredGasEstimation = await estimator.preEstimateUserOperationGas({ bundlerUrl: fixtures.BUNDLER_URL, userOperation: fixtures.USER_OPERATION_V07, entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 @@ -40,18 +40,8 @@ describe('PimlicoFeeEstimator', () => { }) }) - it('should enable to adjust the gas estimation', async () => { - const sponsoredGasEstimation = await estimator.adjustEstimation({ - bundlerUrl: fixtures.BUNDLER_URL, - userOperation: fixtures.USER_OPERATION_V07, - entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 - }) - - expect(sponsoredGasEstimation).toEqual({}) - }) - - it('should get the paymaster estimation', async () => { - const paymasterGasEstimation = await estimator.getPaymasterEstimation({ + it('should enable to adjust the gas estimation after calling eth_estimateUserOperationGas', async () => { + const paymasterGasEstimation = await estimator.postEstimateUserOperationGas({ userOperation: fixtures.USER_OPERATION_V07, bundlerUrl: fixtures.BUNDLER_URL, paymasterOptions: { diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts index 7fd188ae6..0c3df4ef0 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts @@ -16,12 +16,11 @@ import { PIMLICO_CUSTOM_RPC_4337_CALLS, PimlicoCustomRpcSchema } from './types' /** * PimlicoFeeEstimator is a class that implements the IFeeEstimator interface. You can implement three optional methods that will be called during the estimation process: - * - setupEstimation: Setup the userOperation before calling the eth_estimateUserOperation gas method. - * - adjustEstimation: Adjust the userOperation values returned after calling the eth_adjustUserOperation method. - * - getPaymasterEstimation: Obtain the paymaster data and the paymaster gas values. + * - preEstimateUserOperationGas: Setup the userOperation before calling the eth_estimateUserOperation gas method. + * - postEstimateUserOperationGas: Adjust the userOperation values returned after calling the eth_estimateUserOperation method. */ export class PimlicoFeeEstimator implements IFeeEstimator { - async setupEstimation({ + async preEstimateUserOperationGas({ bundlerUrl, userOperation, entryPoint, @@ -56,20 +55,12 @@ export class PimlicoFeeEstimator implements IFeeEstimator { } } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async adjustEstimation(_: EstimateFeeFunctionProps): Promise { - return {} - } - - async getPaymasterEstimation({ + async postEstimateUserOperationGas({ userOperation, entryPoint, paymasterOptions }: EstimateFeeFunctionProps): Promise { - if (!paymasterOptions) - throw new Error( - "Paymaster options can't be empty when trying to get the paymaster data and gas estimation" - ) + if (!paymasterOptions) return {} const paymasterClient = createBundlerClient( paymasterOptions.paymasterUrl @@ -111,9 +102,9 @@ export class PimlicoFeeEstimator implements IFeeEstimator { } async #getUserOperationGasPrices( - bundlerClient: BundlerClient + client: BundlerClient ): Promise> { - const feeData = await bundlerClient.request({ + const feeData = await client.request({ method: PIMLICO_CUSTOM_RPC_4337_CALLS.GET_USER_OPERATION_GAS_PRICE }) diff --git a/packages/relay-kit/src/packs/safe-4337/types.ts b/packages/relay-kit/src/packs/safe-4337/types.ts index 5bcf29319..0732c2bce 100644 --- a/packages/relay-kit/src/packs/safe-4337/types.ts +++ b/packages/relay-kit/src/packs/safe-4337/types.ts @@ -153,9 +153,8 @@ export type EstimateFeeFunction = ({ }: EstimateFeeFunctionProps) => Promise export interface IFeeEstimator { - setupEstimation?: EstimateFeeFunction - adjustEstimation?: EstimateFeeFunction - getPaymasterEstimation?: EstimateFeeFunction + preEstimateUserOperationGas?: EstimateFeeFunction + postEstimateUserOperationGas?: EstimateFeeFunction } export type EstimateFeeProps = { From 9601e11c65bef36ebaca9f3e3e940050b7bef901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Tue, 4 Feb 2025 12:34:26 +0100 Subject: [PATCH 41/55] Improve SafeOperationFactory --- .../src/packs/safe-4337/SafeOperationFactory.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts index 83ea2b6e7..9929a7013 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts @@ -7,18 +7,24 @@ import { import SafeOperationV06 from '@safe-global/relay-kit/packs/safe-4337/SafeOperationV06' import SafeOperationV07 from '@safe-global/relay-kit/packs/safe-4337/SafeOperationV07' import SafeOperationBase from '@safe-global/relay-kit/packs/safe-4337/SafeOperationBase' -import { isEntryPointV7 } from '@safe-global/relay-kit/packs/safe-4337/utils' +import { isEntryPointV6 } from '@safe-global/relay-kit/packs/safe-4337/utils' class SafeOperationFactory { + /** + * Creates a new SafeOperation with proper validation + * @param userOperation - The base user operation + * @param options - Configuration options + * @returns Validated SafeOperation instance + */ static createSafeOperation( userOperation: UserOperation, options: SafeOperationOptions ): SafeOperationBase { - if (isEntryPointV7(options.entryPoint)) { - return new SafeOperationV07(userOperation as UserOperationV07, options) - } else { + if (isEntryPointV6(options.entryPoint)) { return new SafeOperationV06(userOperation as UserOperationV06, options) } + + return new SafeOperationV07(userOperation as UserOperationV07, options) } } From 479fd130666cc11b3799461b46d60cf466bae629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Tue, 4 Feb 2025 12:59:13 +0100 Subject: [PATCH 42/55] Add SPONSORSHIP_POLICY_ID env var --- playground/relay-kit/.env-sample | 1 + 1 file changed, 1 insertion(+) diff --git a/playground/relay-kit/.env-sample b/playground/relay-kit/.env-sample index ee89742b4..1b279fb5b 100644 --- a/playground/relay-kit/.env-sample +++ b/playground/relay-kit/.env-sample @@ -9,3 +9,4 @@ CHAIN_ID=11155111 # You can get Bundler and Paymaster URL's from your provider's dashboard BUNDLER_URL= PAYMASTER_URL= +SPONSORSHIP_POLICY_ID= \ No newline at end of file From 1b1b163f8644684f506707e3cadaabc9770bc460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Tue, 4 Feb 2025 14:43:45 +0100 Subject: [PATCH 43/55] Improve estimator --- .../estimators/pimlico/PimlicoFeeEstimator.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts index 0c3df4ef0..31130904c 100644 --- a/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts +++ b/packages/relay-kit/src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts @@ -1,10 +1,8 @@ import { EstimateGasData } from '@safe-global/types-kit' import { BundlerClient, - ERC20PaymasterOption, EstimateFeeFunctionProps, IFeeEstimator, - SponsoredPaymasterOption, UserOperationStringValues } from '@safe-global/relay-kit/packs/safe-4337/types' import { @@ -36,16 +34,15 @@ export class PimlicoFeeEstimator implements IFeeEstimator { const paymasterClient = createBundlerClient( paymasterOptions.paymasterUrl ) - const context = (paymasterOptions as ERC20PaymasterOption).paymasterTokenAddress - ? { - token: (paymasterOptions as ERC20PaymasterOption).paymasterTokenAddress - } - : undefined + const context = + 'paymasterTokenAddress' in paymasterOptions + ? { + token: paymasterOptions.paymasterTokenAddress + } + : undefined paymasterStubData = await paymasterClient.request({ method: RPC_4337_CALLS.GET_PAYMASTER_STUB_DATA, - params: (paymasterOptions as SponsoredPaymasterOption).sponsorshipPolicyId - ? [userOperationToHexValues(userOperation, entryPoint), entryPoint, chainId, context] - : [userOperationToHexValues(userOperation, entryPoint), entryPoint, chainId, context] + params: [userOperationToHexValues(userOperation, entryPoint), entryPoint, chainId, context] }) } From a9f2c35ff88f328ae5b57b37a619416608b5d277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Tue, 4 Feb 2025 14:55:07 +0100 Subject: [PATCH 44/55] Add tests --- .../src/packs/safe-4337/Safe4337Pack.test.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts index dab6507d2..359a0b116 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts @@ -975,4 +975,49 @@ describe('Safe4337Pack', () => { expect(supportedEntryPoints).toContain('0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789') }) + + describe('When using the onChainAnalytics feature', () => { + it("should enable to generate on chain analytics data for a Safe's transactions", async () => { + const safe4337Pack = await createSafe4337Pack({ + onchainAnalytics: { + project: 'Test Relay kit', + platform: 'Web' + }, + options: { + safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE + } + }) + + expect(safe4337Pack.getOnchainIdentifier()).toBe( + '5afe006137303238633936636562316132623939353333646561393063346135' + ) + }) + + it('should include th onchain identifier at the end of the callData property', async () => { + const safe4337Pack = await createSafe4337Pack({ + onchainAnalytics: { + project: 'Test Relay kit', + platform: 'Web' + }, + options: { + safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE + } + }) + + const transferUSDC = { + to: fixtures.PAYMASTER_TOKEN_ADDRESS, + data: generateTransferCallData(fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, 100_000n), + value: '0', + operation: 0 + } + + const safeOperation = await safe4337Pack.createTransaction({ + transactions: [transferUSDC] + }) + + expect(safeOperation.userOperation.callData).toContain( + '5afe006137303238633936636562316132623939353333646561393063346135' + ) + }) + }) }) From e0ace1ddd6e275309f6b4d5552a9581c88bc15f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Tue, 4 Feb 2025 15:07:53 +0100 Subject: [PATCH 45/55] Add test --- .../src/packs/safe-4337/Safe4337Pack.test.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts index 359a0b116..2d25db120 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts @@ -94,6 +94,17 @@ describe('Safe4337Pack', () => { `Incompatibility detected: The EIP-4337 fallbackhandler is not attached to the Safe Account. Attach this fallbackhandler (address: ${fixtures.SAFE_4337_MODULE_ADDRESS_V0_2_0}) to ensure compatibility.` ) }) + + it('should throw an error if the 4337 Module is not enabled in the Safe account', async () => { + await expect( + createSafe4337Pack({ + options: { safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE }, + safeModulesVersion: '9.9.9' + }) + ).rejects.toThrow( + 'Safe4337Module and/or SafeModuleSetup not available for chain 11155111 and modules version 9.9.9' + ) + }) }) describe('When using existing Safe Accounts with version 1.4.1 or greater', () => { @@ -755,6 +766,18 @@ describe('Safe4337Pack', () => { }) }) + it('should use the default module version when safeModuleVersion is not provided', async () => { + const safe4337Pack = await createSafe4337Pack({ + options: { + safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE + } + }) + + expect(await safe4337Pack.protocolKit.getFallbackHandler()).toBe( + fixtures.SAFE_4337_MODULE_ADDRESS_V0_3_0 + ) + }) + it('should allow to sign a SafeOperation', async () => { const transferUSDC = { to: fixtures.PAYMASTER_TOKEN_ADDRESS, From bfe5591cf4ae8b3fbbf13437a424b140aa31f8da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Wed, 5 Feb 2025 10:48:01 +0100 Subject: [PATCH 46/55] Fix checksum shared signer --- packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index 55f7ccd49..793f7e004 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -1,4 +1,4 @@ -import { toHex } from 'viem' +import { getAddress, toHex } from 'viem' import semverSatisfies from 'semver/functions/satisfies.js' import Safe, { EthSafeSignature, @@ -286,8 +286,11 @@ export class Safe4337Pack extends RelayKitBasePack<{ const passkeySigner = (await safeProvider.getExternalSigner()) as PasskeyClient - if (!options.owners.includes(safeWebAuthnSharedSignerAddress)) { - options.owners.push(safeWebAuthnSharedSignerAddress) + const checkSummedOwners = options.owners.map((owner) => getAddress(owner)) + const checkSummedSignerAddress = getAddress(safeWebAuthnSharedSignerAddress) + + if (!checkSummedOwners.includes(checkSummedSignerAddress)) { + options.owners.push(checkSummedSignerAddress) } const sharedSignerTransaction = { From e145ac48429b141f259986261b6e5093aba57248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Wed, 5 Feb 2025 11:02:08 +0100 Subject: [PATCH 47/55] Update api-kit-interoperability --- playground/config/run.ts | 6 ++--- ...nsaction.ts => gelato-paid-transaction.ts} | 0 ...ion.ts => gelato-sponsored-transaction.ts} | 0 ....ts => userop-api-kit-interoperability.ts} | 22 ++++++++++--------- 4 files changed, 15 insertions(+), 13 deletions(-) rename playground/relay-kit/{paid-transaction.ts => gelato-paid-transaction.ts} (100%) rename playground/relay-kit/{sponsored-transaction.ts => gelato-sponsored-transaction.ts} (100%) rename playground/relay-kit/{api-kit-interoperability.ts => userop-api-kit-interoperability.ts} (81%) diff --git a/playground/config/run.ts b/playground/config/run.ts index 1fe6dca49..33680553d 100644 --- a/playground/config/run.ts +++ b/playground/config/run.ts @@ -17,9 +17,9 @@ const playgroundApiKitPaths = { 'execute-transaction': 'api-kit/execute-transaction' } const playgroundRelayKitPaths = { - 'api-kit-interoperability': 'relay-kit/api-kit-interoperability', - 'relay-paid-transaction': 'relay-kit/paid-transaction', - 'relay-sponsored-transaction': 'relay-kit/sponsored-transaction', + 'gelato-paid-transaction': 'relay-kit/gelato-paid-transaction', + 'gelato-sponsored-transaction': 'relay-kit/gelato-sponsored-transaction', + 'userop-api-kit-interoperability': 'relay-kit/userop-api-kit-interoperability', userop: 'relay-kit/userop', 'userop-counterfactual': 'relay-kit/userop-counterfactual', 'userop-erc20-paymaster': 'relay-kit/userop-erc20-paymaster', diff --git a/playground/relay-kit/paid-transaction.ts b/playground/relay-kit/gelato-paid-transaction.ts similarity index 100% rename from playground/relay-kit/paid-transaction.ts rename to playground/relay-kit/gelato-paid-transaction.ts diff --git a/playground/relay-kit/sponsored-transaction.ts b/playground/relay-kit/gelato-sponsored-transaction.ts similarity index 100% rename from playground/relay-kit/sponsored-transaction.ts rename to playground/relay-kit/gelato-sponsored-transaction.ts diff --git a/playground/relay-kit/api-kit-interoperability.ts b/playground/relay-kit/userop-api-kit-interoperability.ts similarity index 81% rename from playground/relay-kit/api-kit-interoperability.ts rename to playground/relay-kit/userop-api-kit-interoperability.ts index 18f3350ab..6fd42a055 100644 --- a/playground/relay-kit/api-kit-interoperability.ts +++ b/playground/relay-kit/userop-api-kit-interoperability.ts @@ -1,5 +1,4 @@ import { privateKeyToAddress } from 'viem/accounts' -import { sepolia } from 'viem/chains' import SafeApiKit from '@safe-global/api-kit' import { Safe4337Pack } from '@safe-global/relay-kit' import { waitForOperationToFinish } from '../utils' @@ -7,16 +6,14 @@ import { waitForOperationToFinish } from '../utils' // Variables const OWNER_1_PRIVATE_KEY = '0x' const OWNER_2_PRIVATE_KEY = '0x' -const PIMLICO_API_KEY = '' -const SAFE_ADDRESS = '' // Safe 2/N +const SAFE_ADDRESS = '0x' // Safe 2/N -const CHAIN_NAME = 'sepolia' -const CHAIN_ID = sepolia.id -const RPC_URL = sepolia.rpcUrls.default.http[0] +const CHAIN_ID = '11155111' +const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com' // Constants -const BUNDLER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` -const PAYMASTER_URL = `https://api.pimlico.io/v2/${CHAIN_NAME}/rpc?apikey=${PIMLICO_API_KEY}` +const BUNDLER_URL = 'https://...' +const PAYMASTER_URL = 'https://...' async function main() { const apiKit = new SafeApiKit({ chainId: BigInt(CHAIN_ID) }) @@ -25,8 +22,12 @@ async function main() { provider: RPC_URL, signer: OWNER_1_PRIVATE_KEY, bundlerUrl: BUNDLER_URL, + safeModulesVersion: '0.2.0', + paymasterOptions: { + isSponsored: true, + paymasterUrl: PAYMASTER_URL + }, options: { - owners: [OWNER_1_PRIVATE_KEY, OWNER_2_PRIVATE_KEY], safeAddress: SAFE_ADDRESS } }) @@ -60,6 +61,7 @@ async function main() { provider: RPC_URL, signer: OWNER_2_PRIVATE_KEY, bundlerUrl: BUNDLER_URL, + safeModulesVersion: '0.2.0', paymasterOptions: { isSponsored: true, paymasterUrl: PAYMASTER_URL @@ -87,7 +89,7 @@ async function main() { }) console.log('Executing the SafeOperation...') - await waitForOperationToFinish(userOperationHash, CHAIN_NAME, safe4337Pack) + await waitForOperationToFinish(userOperationHash, CHAIN_ID, safe4337Pack) } main() From 6dc64ad8d0c87f90bfeb6c3dc31676589dc80a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Thu, 6 Feb 2025 13:17:50 +0100 Subject: [PATCH 48/55] Update playground/utils.ts Co-authored-by: Daniel <25051234+dasanra@users.noreply.github.com> --- playground/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/utils.ts b/playground/utils.ts index 2c6659c3a..26cfe04cd 100644 --- a/playground/utils.ts +++ b/playground/utils.ts @@ -9,7 +9,7 @@ import { import { Safe4337Pack } from '@safe-global/relay-kit' import { ExternalSigner } from '@safe-global/protocol-kit' import { getBlock, waitForTransactionReceipt } from 'viem/actions' -import { MetaTransactionData } from 'packages/types-kit/dist/src' +import { MetaTransactionData } from '@safe-global/types-kit' import { sepolia } from 'viem/chains' export const generateTransferCallData = (to: string, value: bigint) => { From 3182c565012a5a0ef957aab1fa8a17dc7db65eb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Thu, 6 Feb 2025 13:17:59 +0100 Subject: [PATCH 49/55] Update packages/protocol-kit/src/utils/signatures/utils.ts Co-authored-by: Daniel <25051234+dasanra@users.noreply.github.com> --- packages/protocol-kit/src/utils/signatures/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol-kit/src/utils/signatures/utils.ts b/packages/protocol-kit/src/utils/signatures/utils.ts index ca6dc28f7..5a0e92662 100644 --- a/packages/protocol-kit/src/utils/signatures/utils.ts +++ b/packages/protocol-kit/src/utils/signatures/utils.ts @@ -6,7 +6,7 @@ import { SafeTransactionData, SigningMethod } from '@safe-global/types-kit' -import semverSatisfies from 'semver/functions/satisfies' +import semverSatisfies from 'semver/functions/satisfies.js' import { sameString } from '../address' import { EthSafeSignature } from './SafeSignature' import { getEip712MessageTypes, getEip712TxTypes } from '../eip-712' From 36f2f1684f925b45a4c361e75797d0c330047f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Thu, 6 Feb 2025 13:18:37 +0100 Subject: [PATCH 50/55] Update packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts Co-authored-by: Daniel <25051234+dasanra@users.noreply.github.com> --- packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts index 2d25db120..b3d1837a8 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts @@ -95,7 +95,7 @@ describe('Safe4337Pack', () => { ) }) - it('should throw an error if the 4337 Module is not enabled in the Safe account', async () => { + it('should throw an error if the Safe Modules do not match the supported version', async () => { await expect( createSafe4337Pack({ options: { safeAddress: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE }, From 6130264dbca5525dd4cbc1bcac2198798ff2be3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Thu, 6 Feb 2025 13:30:40 +0100 Subject: [PATCH 51/55] Fix using viem sepolia chain in tests --- playground/utils.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/playground/utils.ts b/playground/utils.ts index 2c6659c3a..a9201d6bb 100644 --- a/playground/utils.ts +++ b/playground/utils.ts @@ -10,7 +10,7 @@ import { Safe4337Pack } from '@safe-global/relay-kit' import { ExternalSigner } from '@safe-global/protocol-kit' import { getBlock, waitForTransactionReceipt } from 'viem/actions' import { MetaTransactionData } from 'packages/types-kit/dist/src' -import { sepolia } from 'viem/chains' +import * as chains from 'viem/chains' export const generateTransferCallData = (to: string, value: bigint) => { const functionAbi = parseAbi(['function transfer(address _to, uint256 _value) returns (bool)']) @@ -67,6 +67,18 @@ export async function transfer( return await publicClient.waitForTransactionReceipt({ hash }) } +function getChain(chainId: number): any { + for (const chain of Object.values(chains)) { + if ('id' in chain) { + if (chain.id === chainId) { + return chain + } + } + } + + throw new Error(`Chain with id ${chainId} not found`) +} + export async function setup4337Playground( safe4337Pack: Safe4337Pack, { @@ -83,10 +95,11 @@ export async function setup4337Playground( } ): Promise<{ transactions: MetaTransactionData[]; timestamp: bigint }> { const senderAddress = await safe4337Pack.protocolKit.getAddress() + const chainId = await safe4337Pack.getChainId() // Log supported entry points and Safe state console.log('Supported Entry Points', await safe4337Pack.getSupportedEntryPoints()) - console.log('Chain id', await safe4337Pack.getChainId()) + console.log('Chain id', chainId) console.log('Safe Address: ', senderAddress) console.log('Safe Owners:', await safe4337Pack.protocolKit.getOwners()) console.log('is Safe Account deployed: ', await safe4337Pack.protocolKit.isSafeDeployed()) @@ -106,7 +119,7 @@ export async function setup4337Playground( const hash = await externalSigner?.sendTransaction({ to: senderAddress, value: nativeTokenAmount, - chain: sepolia + chain: getChain(Number(chainId)) }) await waitForTransactionReceipt(externalProvider, { hash }) From e49b3e87e04de3b134daa5da2ebf5f92dbdd37a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Thu, 6 Feb 2025 17:57:01 +0100 Subject: [PATCH 52/55] Remove SafeOperation Interface --- packages/api-kit/src/SafeApiKit.ts | 7 ++--- packages/api-kit/src/utils/safeOperation.ts | 4 +-- .../tests/e2e/confirmSafeOperation.test.ts | 11 +++----- .../src/packs/safe-4337/Safe4337Pack.ts | 13 ++++----- .../src/packs/safe-4337/SafeOperationBase.ts | 3 +- .../packs/safe-4337/SafeOperationV06.test.ts | 14 +++++++++- .../packs/safe-4337/SafeOperationV07.test.ts | 14 +++++++++- packages/types-kit/src/types.ts | 28 ------------------- 8 files changed, 42 insertions(+), 52 deletions(-) diff --git a/packages/api-kit/src/SafeApiKit.ts b/packages/api-kit/src/SafeApiKit.ts index c58537a8d..2f52f0340 100644 --- a/packages/api-kit/src/SafeApiKit.ts +++ b/packages/api-kit/src/SafeApiKit.ts @@ -34,12 +34,11 @@ import { import { HttpMethod, sendRequest } from '@safe-global/api-kit/utils/httpRequests' import { signDelegate } from '@safe-global/api-kit/utils/signDelegate' import { validateEip3770Address, validateEthereumAddress } from '@safe-global/protocol-kit' +import { SafeOperationBase } from '@safe-global/relay-kit' import { Eip3770Address, - isSafeOperation, SafeMultisigConfirmationListResponse, SafeMultisigTransactionResponse, - SafeOperation, SafeOperationConfirmationListResponse, SafeOperationResponse, UserOperationV06 @@ -875,11 +874,11 @@ class SafeApiKit { * @throws "Invalid module address {moduleAddress}" * @throws "Signature must not be empty" */ - async addSafeOperation(safeOperation: AddSafeOperationProps | SafeOperation): Promise { + async addSafeOperation(safeOperation: AddSafeOperationProps | SafeOperationBase): Promise { let safeAddress: string, moduleAddress: string let addSafeOperationProps: AddSafeOperationProps - if (isSafeOperation(safeOperation)) { + if (safeOperation instanceof SafeOperationBase) { addSafeOperationProps = await getAddSafeOperationProps(safeOperation) } else { addSafeOperationProps = safeOperation diff --git a/packages/api-kit/src/utils/safeOperation.ts b/packages/api-kit/src/utils/safeOperation.ts index b19650e4e..c1ebe000e 100644 --- a/packages/api-kit/src/utils/safeOperation.ts +++ b/packages/api-kit/src/utils/safeOperation.ts @@ -1,6 +1,6 @@ -import { SafeOperation } from '@safe-global/types-kit' +import { SafeOperationBase } from '@safe-global/relay-kit' -export const getAddSafeOperationProps = async (safeOperation: SafeOperation) => { +export const getAddSafeOperationProps = async (safeOperation: SafeOperationBase) => { const userOperation = safeOperation.getUserOperation() userOperation.signature = safeOperation.encodedSignatures() // Without validity dates diff --git a/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts b/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts index 2dbddf316..d44c58831 100644 --- a/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts +++ b/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts @@ -3,7 +3,6 @@ import chaiAsPromised from 'chai-as-promised' import { Safe4337InitOptions, Safe4337Pack, SafeOperationBase } from '@safe-global/relay-kit' import SafeApiKit from '@safe-global/api-kit/index' import { getAddSafeOperationProps } from '@safe-global/api-kit/utils/safeOperation' -import { SafeOperation } from '@safe-global/types-kit' import { generateTransferCallData } from '@safe-global/relay-kit/test-utils' import { getApiKit, getEip1193Provider } from '../utils/setupKits' @@ -18,7 +17,7 @@ const PAYMASTER_TOKEN_ADDRESS = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' let safeApiKit: SafeApiKit let safe4337Pack: Safe4337Pack -let safeOperation: SafeOperation +let safeOperation: SafeOperationBase let safeOpHash: string describe('confirmSafeOperation', () => { @@ -38,11 +37,9 @@ describe('confirmSafeOperation', () => { safeModulesVersion: '0.2.0' }) - const createSignature = async (safeOperation: SafeOperation, signer: string) => { + const createSignature = async (safeOperation: SafeOperationBase, signer: string) => { const safe4337Pack = await getSafe4337Pack({ signer }) - const signedSafeOperation = await safe4337Pack.signSafeOperation( - safeOperation as SafeOperationBase - ) + const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) const signerAddress = await safe4337Pack.protocolKit.getSafeProvider().getSignerAddress() return signedSafeOperation.getSignature(signerAddress!) } @@ -51,7 +48,7 @@ describe('confirmSafeOperation', () => { * Add a new Safe operation to the transaction service. * @returns Resolves with the signed Safe operation */ - const addSafeOperation = async (): Promise => { + const addSafeOperation = async (): Promise => { const safeOperation = await safe4337Pack.createTransaction({ transactions: [transferUSDC] }) diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index 793f7e004..5b2f2d432 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -10,7 +10,6 @@ import Safe, { } from '@safe-global/protocol-kit' import { RelayKitBasePack } from '@safe-global/relay-kit/RelayKitBasePack' import { - isSafeOperationResponse, OperationType, SafeOperationConfirmation, SafeOperationResponse, @@ -557,10 +556,10 @@ export class Safe4337Pack extends RelayKitBasePack<{ ): Promise { let safeOp: SafeOperationBase - if (isSafeOperationResponse(safeOperation)) { - safeOp = this.#toSafeOperation(safeOperation) - } else { + if (safeOperation instanceof SafeOperationBase) { safeOp = safeOperation + } else { + safeOp = this.#toSafeOperation(safeOperation) } const safeProvider = this.protocolKit.getSafeProvider() @@ -651,10 +650,10 @@ export class Safe4337Pack extends RelayKitBasePack<{ async executeTransaction({ executable }: Safe4337ExecutableProps): Promise { let safeOperation: SafeOperationBase - if (isSafeOperationResponse(executable)) { - safeOperation = this.#toSafeOperation(executable) - } else { + if (executable instanceof SafeOperationBase) { safeOperation = executable + } else { + safeOperation = this.#toSafeOperation(executable) } return this.#bundlerClient.request({ diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts index 89ce5d484..8d62600b8 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts @@ -1,7 +1,6 @@ import { Hex, encodePacked, hashTypedData } from 'viem' import { EstimateGasData, - SafeOperation, SafeOperationOptions, SafeSignature, SafeUserOperation, @@ -13,7 +12,7 @@ import { EIP712_SAFE_OPERATION_TYPE_V07 } from '@safe-global/relay-kit/packs/safe-4337/constants' -abstract class SafeOperationBase implements SafeOperation { +abstract class SafeOperationBase { userOperation: UserOperation options: SafeOperationOptions signatures: Map = new Map() diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts index 9d487a226..ca3a37e5e 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts @@ -1,9 +1,21 @@ import { Hex, encodePacked } from 'viem' import { EthSafeSignature } from '@safe-global/protocol-kit' -import SafeOperationV06 from './SafeOperationV06' import { fixtures } from '@safe-global/relay-kit/test-utils' +import SafeOperationV06 from './SafeOperationV06' +import SafeOperationBase from './SafeOperationBase' describe('SafeOperationV06', () => { + it('should be an instance of SafeOperationBase', () => { + const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION_V06, { + chainId: BigInt(fixtures.CHAIN_ID), + moduleAddress: fixtures.SAFE_4337_MODULE_ADDRESS_V0_2_0, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V06 + }) + + expect(safeOperation).toBeInstanceOf(SafeOperationBase) + expect(safeOperation).toBeInstanceOf(SafeOperationV06) + }) + it('should create a SafeOperation from an UserOperation', () => { const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION_V06, { chainId: BigInt(fixtures.CHAIN_ID), diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts index 2e1f3b509..e1833d675 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts @@ -1,9 +1,21 @@ import { Hex, concat, encodePacked } from 'viem' import { EthSafeSignature } from '@safe-global/protocol-kit' -import SafeOperationV07 from './SafeOperationV07' import { fixtures } from '@safe-global/relay-kit/test-utils' +import SafeOperationV07 from './SafeOperationV07' +import SafeOperationBase from './SafeOperationBase' describe('SafeOperationV07', () => { + it('should be an instance of SafeOperationBase', () => { + const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V06, { + chainId: BigInt(fixtures.CHAIN_ID), + moduleAddress: fixtures.SAFE_4337_MODULE_ADDRESS_V0_3_0, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 + }) + + expect(safeOperation).toBeInstanceOf(SafeOperationBase) + expect(safeOperation).toBeInstanceOf(SafeOperationV07) + }) + it('should create a SafeOperation from an UserOperation', () => { const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V07, { chainId: BigInt(fixtures.CHAIN_ID), diff --git a/packages/types-kit/src/types.ts b/packages/types-kit/src/types.ts index bac1bcf2d..b2dfff96e 100644 --- a/packages/types-kit/src/types.ts +++ b/packages/types-kit/src/types.ts @@ -355,28 +355,6 @@ export type SafeOperationOptions = { validUntil?: number } -export interface SafeOperation { - userOperation: UserOperation - options: SafeOperationOptions - - readonly signatures: Map - getSignature(signer: string): SafeSignature | undefined - addSignature(signature: SafeSignature): void - encodedSignatures(): string - addEstimations(estimations: EstimateGasData): void - getSafeOperation(): SafeUserOperation - getUserOperation(): UserOperation - getHash(): string -} - -export const isSafeOperation = (response: unknown): response is SafeOperation => { - const safeOperation = response as SafeOperation - - return ( - 'userOperation' in safeOperation && 'options' in safeOperation && 'signatures' in safeOperation - ) -} - export type SafeOperationConfirmation = { readonly created: string readonly modified: string @@ -415,10 +393,4 @@ export type SafeOperationResponse = { readonly userOperation?: UserOperationResponse } -export const isSafeOperationResponse = (response: unknown): response is SafeOperationResponse => { - const safeOperationResponse = response as SafeOperationResponse - - return 'userOperation' in safeOperationResponse && 'safeOperationHash' in safeOperationResponse -} - export type SafeOperationConfirmationListResponse = ListResponse From 609aa1b5aea3ecc08c3a2e0069741107c497fc2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Thu, 6 Feb 2025 18:08:06 +0100 Subject: [PATCH 53/55] Renamed SafeOperationBase -> SafeOperation --- packages/api-kit/src/SafeApiKit.ts | 6 +-- packages/api-kit/src/utils/safeOperation.ts | 4 +- .../tests/e2e/confirmSafeOperation.test.ts | 8 ++-- packages/relay-kit/src/index.ts | 2 +- .../src/packs/safe-4337/Safe4337Pack.test.ts | 10 ++-- .../src/packs/safe-4337/Safe4337Pack.ts | 46 +++++++++---------- ...tionBase.test.ts => SafeOperation.test.ts} | 2 +- ...{SafeOperationBase.ts => SafeOperation.ts} | 4 +- .../packs/safe-4337/SafeOperationFactory.ts | 4 +- .../packs/safe-4337/SafeOperationV06.test.ts | 6 +-- .../src/packs/safe-4337/SafeOperationV06.ts | 4 +- .../packs/safe-4337/SafeOperationV07.test.ts | 6 +-- .../src/packs/safe-4337/SafeOperationV07.ts | 4 +- .../relay-kit/src/packs/safe-4337/types.ts | 6 +-- 14 files changed, 56 insertions(+), 56 deletions(-) rename packages/relay-kit/src/packs/safe-4337/{SafeOperationBase.test.ts => SafeOperation.test.ts} (97%) rename packages/relay-kit/src/packs/safe-4337/{SafeOperationBase.ts => SafeOperation.ts} (96%) diff --git a/packages/api-kit/src/SafeApiKit.ts b/packages/api-kit/src/SafeApiKit.ts index 2f52f0340..8c88a426b 100644 --- a/packages/api-kit/src/SafeApiKit.ts +++ b/packages/api-kit/src/SafeApiKit.ts @@ -34,7 +34,7 @@ import { import { HttpMethod, sendRequest } from '@safe-global/api-kit/utils/httpRequests' import { signDelegate } from '@safe-global/api-kit/utils/signDelegate' import { validateEip3770Address, validateEthereumAddress } from '@safe-global/protocol-kit' -import { SafeOperationBase } from '@safe-global/relay-kit' +import { SafeOperation } from '@safe-global/relay-kit' import { Eip3770Address, SafeMultisigConfirmationListResponse, @@ -874,11 +874,11 @@ class SafeApiKit { * @throws "Invalid module address {moduleAddress}" * @throws "Signature must not be empty" */ - async addSafeOperation(safeOperation: AddSafeOperationProps | SafeOperationBase): Promise { + async addSafeOperation(safeOperation: AddSafeOperationProps | SafeOperation): Promise { let safeAddress: string, moduleAddress: string let addSafeOperationProps: AddSafeOperationProps - if (safeOperation instanceof SafeOperationBase) { + if (safeOperation instanceof SafeOperation) { addSafeOperationProps = await getAddSafeOperationProps(safeOperation) } else { addSafeOperationProps = safeOperation diff --git a/packages/api-kit/src/utils/safeOperation.ts b/packages/api-kit/src/utils/safeOperation.ts index c1ebe000e..869e2e04f 100644 --- a/packages/api-kit/src/utils/safeOperation.ts +++ b/packages/api-kit/src/utils/safeOperation.ts @@ -1,6 +1,6 @@ -import { SafeOperationBase } from '@safe-global/relay-kit' +import { SafeOperation } from '@safe-global/relay-kit' -export const getAddSafeOperationProps = async (safeOperation: SafeOperationBase) => { +export const getAddSafeOperationProps = async (safeOperation: SafeOperation) => { const userOperation = safeOperation.getUserOperation() userOperation.signature = safeOperation.encodedSignatures() // Without validity dates diff --git a/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts b/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts index d44c58831..6e6dcc542 100644 --- a/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts +++ b/packages/api-kit/tests/e2e/confirmSafeOperation.test.ts @@ -1,6 +1,6 @@ import chai from 'chai' import chaiAsPromised from 'chai-as-promised' -import { Safe4337InitOptions, Safe4337Pack, SafeOperationBase } from '@safe-global/relay-kit' +import { Safe4337InitOptions, Safe4337Pack, SafeOperation } from '@safe-global/relay-kit' import SafeApiKit from '@safe-global/api-kit/index' import { getAddSafeOperationProps } from '@safe-global/api-kit/utils/safeOperation' import { generateTransferCallData } from '@safe-global/relay-kit/test-utils' @@ -17,7 +17,7 @@ const PAYMASTER_TOKEN_ADDRESS = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' let safeApiKit: SafeApiKit let safe4337Pack: Safe4337Pack -let safeOperation: SafeOperationBase +let safeOperation: SafeOperation let safeOpHash: string describe('confirmSafeOperation', () => { @@ -37,7 +37,7 @@ describe('confirmSafeOperation', () => { safeModulesVersion: '0.2.0' }) - const createSignature = async (safeOperation: SafeOperationBase, signer: string) => { + const createSignature = async (safeOperation: SafeOperation, signer: string) => { const safe4337Pack = await getSafe4337Pack({ signer }) const signedSafeOperation = await safe4337Pack.signSafeOperation(safeOperation) const signerAddress = await safe4337Pack.protocolKit.getSafeProvider().getSignerAddress() @@ -48,7 +48,7 @@ describe('confirmSafeOperation', () => { * Add a new Safe operation to the transaction service. * @returns Resolves with the signed Safe operation */ - const addSafeOperation = async (): Promise => { + const addSafeOperation = async (): Promise => { const safeOperation = await safe4337Pack.createTransaction({ transactions: [transferUSDC] }) diff --git a/packages/relay-kit/src/index.ts b/packages/relay-kit/src/index.ts index d902f92f5..19c757cec 100644 --- a/packages/relay-kit/src/index.ts +++ b/packages/relay-kit/src/index.ts @@ -4,7 +4,7 @@ export * from './packs/gelato/GelatoRelayPack' export * from './packs/gelato/types' export * from './packs/safe-4337/Safe4337Pack' -export { default as SafeOperationBase } from './packs/safe-4337/SafeOperationBase' +export { default as SafeOperation } from './packs/safe-4337/SafeOperation' export { default as SafeOperationV07 } from './packs/safe-4337/SafeOperationV07' export { default as SafeOperationV06 } from './packs/safe-4337/SafeOperationV06' export { default as SafeOperationFactory } from './packs/safe-4337/SafeOperationFactory' diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts index b3d1837a8..cbd70996e 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts @@ -9,7 +9,7 @@ import { } from '@safe-global/safe-modules-deployments' import { MetaTransactionData, OperationType } from '@safe-global/types-kit' import { Safe4337Pack } from './Safe4337Pack' -import SafeOperationBase from './SafeOperationBase' +import SafeOperation from './SafeOperation' import * as constants from './constants' import * as utils from './utils' import { @@ -382,7 +382,7 @@ describe('Safe4337Pack', () => { transactions }) - expect(safeOperation).toBeInstanceOf(SafeOperationBase) + expect(safeOperation).toBeInstanceOf(SafeOperation) expect(safeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07, @@ -418,7 +418,7 @@ describe('Safe4337Pack', () => { transactions: [transferUSDC] }) - expect(safeOperation).toBeInstanceOf(SafeOperationBase) + expect(safeOperation).toBeInstanceOf(SafeOperation) expect(safeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07, @@ -480,7 +480,7 @@ describe('Safe4337Pack', () => { transactions: [transferUSDC] }) - expect(sponsoredSafeOperation).toBeInstanceOf(SafeOperationBase) + expect(sponsoredSafeOperation).toBeInstanceOf(SafeOperation) expect(sponsoredSafeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07, @@ -541,7 +541,7 @@ describe('Safe4337Pack', () => { const batch = [transferUSDC, approveTransaction] - expect(sponsoredSafeOperation).toBeInstanceOf(SafeOperationBase) + expect(sponsoredSafeOperation).toBeInstanceOf(SafeOperation) expect(sponsoredSafeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07, diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index 5b2f2d432..45695e8cd 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -22,7 +22,7 @@ import { getSafeWebAuthnShareSignerDeployment } from '@safe-global/safe-modules-deployments' import { Hash, encodeFunctionData, zeroAddress, Hex, concat } from 'viem' -import SafeOperationBase from '@safe-global/relay-kit/packs/safe-4337/SafeOperationBase' +import SafeOperation from '@safe-global/relay-kit/packs/safe-4337/SafeOperation' import SafeOperationFactory from '@safe-global/relay-kit/packs/safe-4337/SafeOperationFactory' import { EstimateFeeProps, @@ -67,9 +67,9 @@ const EQ_OR_GT_1_4_1 = '>=1.4.1' */ export class Safe4337Pack extends RelayKitBasePack<{ EstimateFeeProps: EstimateFeeProps - EstimateFeeResult: SafeOperationBase + EstimateFeeResult: SafeOperation CreateTransactionProps: Safe4337CreateTransactionProps - CreateTransactionResult: SafeOperationBase + CreateTransactionResult: SafeOperation ExecuteTransactionProps: Safe4337ExecutableProps ExecuteTransactionResult: string }> { @@ -399,15 +399,15 @@ export class Safe4337Pack extends RelayKitBasePack<{ * Estimates gas for the SafeOperation. * * @param {EstimateFeeProps} props - The parameters for the gas estimation. - * @param {SafeOperationBase} props.safeOperation - The SafeOperation to estimate the gas. + * @param {SafeOperation} props.safeOperation - The SafeOperation to estimate the gas. * @param {IFeeEstimator} props.feeEstimator - The function to estimate the gas. - * @return {Promise} The Promise object that will be resolved into the gas estimation. + * @return {Promise} The Promise object that will be resolved into the gas estimation. */ async getEstimateFee({ safeOperation, feeEstimator = new PimlicoFeeEstimator() - }: EstimateFeeProps): Promise { + }: EstimateFeeProps): Promise { const threshold = await this.protocolKit.getThreshold() const preEstimationData = await feeEstimator?.preEstimateUserOperationGas?.({ bundlerUrl: this.#BUNDLER_URL, @@ -457,12 +457,12 @@ export class Safe4337Pack extends RelayKitBasePack<{ * * @param {MetaTransactionData[]} transactions - The transactions to batch in a SafeOperation. * @param options - Optional configuration options for the transaction creation. - * @return {Promise} The Promise object will resolve a SafeOperation. + * @return {Promise} The Promise object will resolve a SafeOperation. */ async createTransaction({ transactions, options = {} - }: Safe4337CreateTransactionProps): Promise { + }: Safe4337CreateTransactionProps): Promise { const { amountToApprove, validUntil, validAfter, feeEstimator } = options const userOperation = await createUserOperation(this.protocolKit, transactions, { @@ -490,12 +490,12 @@ export class Safe4337Pack extends RelayKitBasePack<{ } /** - * Converts a SafeOperationResponse to an SafeOperationBase. + * Converts a SafeOperationResponse to an SafeOperation. * - * @param {SafeOperationResponse} safeOperationResponse - The SafeOperationResponse to convert to SafeOperationBase - * @returns {SafeOperationBase} - The SafeOperationBase object + * @param {SafeOperationResponse} safeOperationResponse - The SafeOperationResponse to convert to SafeOperation + * @returns {SafeOperation} - The SafeOperation object */ - #toSafeOperation(safeOperationResponse: SafeOperationResponse): SafeOperationBase { + #toSafeOperation(safeOperationResponse: SafeOperationResponse): SafeOperation { const { validUntil, validAfter, userOperation } = safeOperationResponse const paymaster = (userOperation?.paymaster as Hex) || '0x' @@ -544,19 +544,19 @@ export class Safe4337Pack extends RelayKitBasePack<{ /** * Signs a safe operation. * - * @param {SafeOperationBase | SafeOperationResponse} safeOperation - The SafeOperation to sign. It can be: + * @param {SafeOperation | SafeOperationResponse} safeOperation - The SafeOperation to sign. It can be: * - A response from the API (Tx Service) - * - An instance of SafeOperationBase + * - An instance of SafeOperation * @param {SigningMethod} signingMethod - The signing method to use. - * @return {Promise} The Promise object will resolve to the signed SafeOperation. + * @return {Promise} The Promise object will resolve to the signed SafeOperation. */ async signSafeOperation( - safeOperation: SafeOperationBase | SafeOperationResponse, + safeOperation: SafeOperation | SafeOperationResponse, signingMethod: SigningMethod = SigningMethod.ETH_SIGN_TYPED_DATA_V4 - ): Promise { - let safeOp: SafeOperationBase + ): Promise { + let safeOp: SafeOperation - if (safeOperation instanceof SafeOperationBase) { + if (safeOperation instanceof SafeOperation) { safeOp = safeOperation } else { safeOp = this.#toSafeOperation(safeOperation) @@ -642,15 +642,15 @@ export class Safe4337Pack extends RelayKitBasePack<{ * Executes the relay transaction. * * @param {Safe4337ExecutableProps} props - The parameters for the transaction execution. - * @param {SafeOperationBase | SafeOperationResponse} props.executable - The SafeOperation to execute. It can be: + * @param {SafeOperation | SafeOperationResponse} props.executable - The SafeOperation to execute. It can be: * - A response from the API (Tx Service) - * - An instance of SafeOperationBase + * - An instance of SafeOperation * @return {Promise} The user operation hash. */ async executeTransaction({ executable }: Safe4337ExecutableProps): Promise { - let safeOperation: SafeOperationBase + let safeOperation: SafeOperation - if (executable instanceof SafeOperationBase) { + if (executable instanceof SafeOperation) { safeOperation = executable } else { safeOperation = this.#toSafeOperation(executable) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts similarity index 97% rename from packages/relay-kit/src/packs/safe-4337/SafeOperationBase.test.ts rename to packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts index a9305e425..ae382bfd8 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts @@ -2,7 +2,7 @@ import { EthSafeSignature } from '@safe-global/protocol-kit' import SafeOperationV07 from './SafeOperationV07' import { fixtures } from '@safe-global/relay-kit/test-utils' -describe('SafeOperationBase', () => { +describe('SafeOperation', () => { it('should add and get signatures', () => { const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V07, { chainId: BigInt(fixtures.CHAIN_ID), diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts similarity index 96% rename from packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts rename to packages/relay-kit/src/packs/safe-4337/SafeOperation.ts index 8d62600b8..d16acd35a 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationBase.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts @@ -12,7 +12,7 @@ import { EIP712_SAFE_OPERATION_TYPE_V07 } from '@safe-global/relay-kit/packs/safe-4337/constants' -abstract class SafeOperationBase { +abstract class SafeOperation { userOperation: UserOperation options: SafeOperationOptions signatures: Map = new Map() @@ -69,4 +69,4 @@ abstract class SafeOperationBase { | typeof EIP712_SAFE_OPERATION_TYPE_V07 } -export default SafeOperationBase +export default SafeOperation diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts index 9929a7013..1d9dd0150 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts @@ -6,7 +6,7 @@ import { } from '@safe-global/types-kit' import SafeOperationV06 from '@safe-global/relay-kit/packs/safe-4337/SafeOperationV06' import SafeOperationV07 from '@safe-global/relay-kit/packs/safe-4337/SafeOperationV07' -import SafeOperationBase from '@safe-global/relay-kit/packs/safe-4337/SafeOperationBase' +import SafeOperation from '@safe-global/relay-kit/packs/safe-4337/SafeOperation' import { isEntryPointV6 } from '@safe-global/relay-kit/packs/safe-4337/utils' class SafeOperationFactory { @@ -19,7 +19,7 @@ class SafeOperationFactory { static createSafeOperation( userOperation: UserOperation, options: SafeOperationOptions - ): SafeOperationBase { + ): SafeOperation { if (isEntryPointV6(options.entryPoint)) { return new SafeOperationV06(userOperation as UserOperationV06, options) } diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts index ca3a37e5e..2dc71701b 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts @@ -2,17 +2,17 @@ import { Hex, encodePacked } from 'viem' import { EthSafeSignature } from '@safe-global/protocol-kit' import { fixtures } from '@safe-global/relay-kit/test-utils' import SafeOperationV06 from './SafeOperationV06' -import SafeOperationBase from './SafeOperationBase' +import SafeOperation from './SafeOperation' describe('SafeOperationV06', () => { - it('should be an instance of SafeOperationBase', () => { + it('should be an instance of SafeOperation', () => { const safeOperation = new SafeOperationV06(fixtures.USER_OPERATION_V06, { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.SAFE_4337_MODULE_ADDRESS_V0_2_0, entryPoint: fixtures.ENTRYPOINT_ADDRESS_V06 }) - expect(safeOperation).toBeInstanceOf(SafeOperationBase) + expect(safeOperation).toBeInstanceOf(SafeOperation) expect(safeOperation).toBeInstanceOf(SafeOperationV06) }) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts index 8a2cdcc95..bd8ef4ce4 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts @@ -4,10 +4,10 @@ import { SafeUserOperation, SafeOperationOptions } from '@safe-global/types-kit' -import SafeOperationBase from '@safe-global/relay-kit/packs/safe-4337/SafeOperationBase' +import SafeOperation from '@safe-global/relay-kit/packs/safe-4337/SafeOperation' import { EIP712_SAFE_OPERATION_TYPE_V06 } from '@safe-global/relay-kit/packs/safe-4337/constants' -class SafeOperationV06 extends SafeOperationBase { +class SafeOperationV06 extends SafeOperation { userOperation!: UserOperationV06 constructor(userOperation: UserOperationV06, options: SafeOperationOptions) { diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts index e1833d675..878040ea7 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts @@ -2,17 +2,17 @@ import { Hex, concat, encodePacked } from 'viem' import { EthSafeSignature } from '@safe-global/protocol-kit' import { fixtures } from '@safe-global/relay-kit/test-utils' import SafeOperationV07 from './SafeOperationV07' -import SafeOperationBase from './SafeOperationBase' +import SafeOperation from './SafeOperation' describe('SafeOperationV07', () => { - it('should be an instance of SafeOperationBase', () => { + it('should be an instance of SafeOperation', () => { const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V06, { chainId: BigInt(fixtures.CHAIN_ID), moduleAddress: fixtures.SAFE_4337_MODULE_ADDRESS_V0_3_0, entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 }) - expect(safeOperation).toBeInstanceOf(SafeOperationBase) + expect(safeOperation).toBeInstanceOf(SafeOperation) expect(safeOperation).toBeInstanceOf(SafeOperationV07) }) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts index 3f5f73cb5..a68850806 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts @@ -5,10 +5,10 @@ import { SafeOperationOptions } from '@safe-global/types-kit' import { concat, Hex, isAddress, pad, toHex } from 'viem' -import SafeOperationBase from '@safe-global/relay-kit/packs/safe-4337/SafeOperationBase' +import SafeOperation from '@safe-global/relay-kit/packs/safe-4337/SafeOperation' import { EIP712_SAFE_OPERATION_TYPE_V07 } from '@safe-global/relay-kit/packs/safe-4337/constants' -class SafeOperationV07 extends SafeOperationBase { +class SafeOperationV07 extends SafeOperation { userOperation!: UserOperationV07 constructor(userOperation: UserOperationV07, options: SafeOperationOptions) { diff --git a/packages/relay-kit/src/packs/safe-4337/types.ts b/packages/relay-kit/src/packs/safe-4337/types.ts index 0732c2bce..07e21792c 100644 --- a/packages/relay-kit/src/packs/safe-4337/types.ts +++ b/packages/relay-kit/src/packs/safe-4337/types.ts @@ -11,7 +11,7 @@ import { SafeVersion, UserOperation } from '@safe-global/types-kit' -import SafeOperationBase from '@safe-global/relay-kit/packs/safe-4337/SafeOperationBase' +import SafeOperation from '@safe-global/relay-kit/packs/safe-4337/SafeOperation' import { RPC_4337_CALLS } from '@safe-global/relay-kit/packs/safe-4337/constants' type ExistingSafeOptions = { @@ -81,7 +81,7 @@ export type Safe4337CreateTransactionProps = { } export type Safe4337ExecutableProps = { - executable: SafeOperationBase | SafeOperationResponse + executable: SafeOperation | SafeOperationResponse } export type EstimateSponsoredGasData = ( @@ -158,7 +158,7 @@ export interface IFeeEstimator { } export type EstimateFeeProps = { - safeOperation: SafeOperationBase + safeOperation: SafeOperation feeEstimator?: IFeeEstimator } From 028c58230977298aaf09b52cad5e51e8f570ca2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Thu, 6 Feb 2025 18:45:23 +0100 Subject: [PATCH 54/55] Add test --- .../src/packs/safe-4337/SafeOperation.test.ts | 30 +++++++++++++++++++ packages/relay-kit/test-utils/fixtures.ts | 3 ++ 2 files changed, 33 insertions(+) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts index ae382bfd8..6bae576c4 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts @@ -32,4 +32,34 @@ describe('SafeOperation', () => { expect(safeOperation.encodedSignatures()).toBe('0xSignature1Signature2') }) + + it('should allow to retrieve the SafeOperation hash', () => { + const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V07, { + chainId: BigInt(fixtures.CHAIN_ID), + moduleAddress: fixtures.SAFE_4337_MODULE_ADDRESS_V0_3_0, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 + }) + + expect(safeOperation.getHash()).toBe(fixtures.USER_OPERATION_V07_HASH) + }) + + it('should allow to retrieve the UserOperation', () => { + const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V07, { + chainId: BigInt(fixtures.CHAIN_ID), + moduleAddress: fixtures.SAFE_4337_MODULE_ADDRESS_V0_3_0, + entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07, + validAfter: 60_000, + validUntil: 60_000 + }) + + safeOperation.addSignature(new EthSafeSignature('0xSigner1', '0xSignature1')) + safeOperation.addSignature(new EthSafeSignature('0xSigner2', '0xSignature2')) + + const userOperation = safeOperation.getUserOperation() + + expect(userOperation).toMatchObject({ + ...fixtures.USER_OPERATION_V07, + signature: '0x00000000ea6000000000ea60Signature1Signature2' + }) + }) }) diff --git a/packages/relay-kit/test-utils/fixtures.ts b/packages/relay-kit/test-utils/fixtures.ts index 825ec441c..539276a99 100644 --- a/packages/relay-kit/test-utils/fixtures.ts +++ b/packages/relay-kit/test-utils/fixtures.ts @@ -90,6 +90,9 @@ export const USER_OPERATION_V07 = { '0x0000679fa3ac000067a1786c8c012f3bef75848690703f17ab0519669bc38bc2629bd8b3118f6280936933fa675bc52dde81cc71c3e0c4587e17ddecf21f845a7a34862b586776501845b1511c' } +export const USER_OPERATION_V07_HASH = + '0xea46190691c27950a9c4246be1e4550fa1bd85bcf1ad9fe7329b51666722b285' + export const USER_OPERATION_HEX_VALUES = { sender: '0x1405B3659a11a16459fc27Fa1925b60388C38Ce1', nonce: '0x1', From 480f84a924aaea0a617a5be456d99d6e0b4dc134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yago=20P=C3=A9rez=20V=C3=A1zquez?= Date: Fri, 7 Feb 2025 13:02:01 +0100 Subject: [PATCH 55/55] restore SafeOperation interface and rename the class to BaseSafeOperation --- packages/api-kit/src/SafeApiKit.ts | 6 +-- packages/api-kit/src/utils/safeOperation.ts | 9 ++++- packages/relay-kit/src/index.ts | 2 +- ...tion.test.ts => BaseSafeOperation.test.ts} | 2 +- ...{SafeOperation.ts => BaseSafeOperation.ts} | 5 ++- .../src/packs/safe-4337/Safe4337Pack.test.ts | 10 ++--- .../src/packs/safe-4337/Safe4337Pack.ts | 38 +++++++++---------- .../packs/safe-4337/SafeOperationFactory.ts | 4 +- .../packs/safe-4337/SafeOperationV06.test.ts | 4 +- .../src/packs/safe-4337/SafeOperationV06.ts | 4 +- .../packs/safe-4337/SafeOperationV07.test.ts | 4 +- .../src/packs/safe-4337/SafeOperationV07.ts | 4 +- .../relay-kit/src/packs/safe-4337/types.ts | 6 +-- packages/types-kit/src/types.ts | 15 ++++++++ 14 files changed, 68 insertions(+), 45 deletions(-) rename packages/relay-kit/src/packs/safe-4337/{SafeOperation.test.ts => BaseSafeOperation.test.ts} (98%) rename packages/relay-kit/src/packs/safe-4337/{SafeOperation.ts => BaseSafeOperation.ts} (94%) diff --git a/packages/api-kit/src/SafeApiKit.ts b/packages/api-kit/src/SafeApiKit.ts index 3caab822d..02a9bcae5 100644 --- a/packages/api-kit/src/SafeApiKit.ts +++ b/packages/api-kit/src/SafeApiKit.ts @@ -34,18 +34,18 @@ import { import { HttpMethod, sendRequest } from '@safe-global/api-kit/utils/httpRequests' import { signDelegate } from '@safe-global/api-kit/utils/signDelegate' import { validateEip3770Address, validateEthereumAddress } from '@safe-global/protocol-kit' -import { SafeOperation } from '@safe-global/relay-kit' import { Eip3770Address, SafeMultisigConfirmationListResponse, SafeMultisigTransactionResponse, + SafeOperation, SafeOperationConfirmationListResponse, SafeOperationResponse, UserOperationV06 } from '@safe-global/types-kit' import { TRANSACTION_SERVICE_URLS } from './utils/config' import { isEmptyData } from './utils' -import { getAddSafeOperationProps } from './utils/safeOperation' +import { getAddSafeOperationProps, isSafeOperation } from './utils/safeOperation' export interface SafeApiKitConfig { /** chainId - The chainId */ @@ -904,7 +904,7 @@ class SafeApiKit { let safeAddress: string, moduleAddress: string let addSafeOperationProps: AddSafeOperationProps - if (safeOperation instanceof SafeOperation) { + if (isSafeOperation(safeOperation)) { addSafeOperationProps = await getAddSafeOperationProps(safeOperation) } else { addSafeOperationProps = safeOperation diff --git a/packages/api-kit/src/utils/safeOperation.ts b/packages/api-kit/src/utils/safeOperation.ts index 869e2e04f..a87ecbaad 100644 --- a/packages/api-kit/src/utils/safeOperation.ts +++ b/packages/api-kit/src/utils/safeOperation.ts @@ -1,4 +1,5 @@ -import { SafeOperation } from '@safe-global/relay-kit' +import { SafeOperation } from '@safe-global/types-kit' +import { AddSafeOperationProps } from '../types/safeTransactionServiceTypes' export const getAddSafeOperationProps = async (safeOperation: SafeOperation) => { const userOperation = safeOperation.getUserOperation() @@ -15,3 +16,9 @@ export const getAddSafeOperationProps = async (safeOperation: SafeOperation) => } } } + +export const isSafeOperation = ( + obj: AddSafeOperationProps | SafeOperation +): obj is SafeOperation => { + return 'signatures' in obj && 'getUserOperation' in obj && 'getHash' in obj +} diff --git a/packages/relay-kit/src/index.ts b/packages/relay-kit/src/index.ts index 19c757cec..b96853506 100644 --- a/packages/relay-kit/src/index.ts +++ b/packages/relay-kit/src/index.ts @@ -4,7 +4,7 @@ export * from './packs/gelato/GelatoRelayPack' export * from './packs/gelato/types' export * from './packs/safe-4337/Safe4337Pack' -export { default as SafeOperation } from './packs/safe-4337/SafeOperation' +export { default as BaseSafeOperation } from './packs/safe-4337/BaseSafeOperation' export { default as SafeOperationV07 } from './packs/safe-4337/SafeOperationV07' export { default as SafeOperationV06 } from './packs/safe-4337/SafeOperationV06' export { default as SafeOperationFactory } from './packs/safe-4337/SafeOperationFactory' diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts b/packages/relay-kit/src/packs/safe-4337/BaseSafeOperation.test.ts similarity index 98% rename from packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts rename to packages/relay-kit/src/packs/safe-4337/BaseSafeOperation.test.ts index 6bae576c4..af8a8dcda 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/BaseSafeOperation.test.ts @@ -2,7 +2,7 @@ import { EthSafeSignature } from '@safe-global/protocol-kit' import SafeOperationV07 from './SafeOperationV07' import { fixtures } from '@safe-global/relay-kit/test-utils' -describe('SafeOperation', () => { +describe('BaseSafeOperation', () => { it('should add and get signatures', () => { const safeOperation = new SafeOperationV07(fixtures.USER_OPERATION_V07, { chainId: BigInt(fixtures.CHAIN_ID), diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts b/packages/relay-kit/src/packs/safe-4337/BaseSafeOperation.ts similarity index 94% rename from packages/relay-kit/src/packs/safe-4337/SafeOperation.ts rename to packages/relay-kit/src/packs/safe-4337/BaseSafeOperation.ts index d16acd35a..8f4d68534 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperation.ts +++ b/packages/relay-kit/src/packs/safe-4337/BaseSafeOperation.ts @@ -1,6 +1,7 @@ import { Hex, encodePacked, hashTypedData } from 'viem' import { EstimateGasData, + SafeOperation, SafeOperationOptions, SafeSignature, SafeUserOperation, @@ -12,7 +13,7 @@ import { EIP712_SAFE_OPERATION_TYPE_V07 } from '@safe-global/relay-kit/packs/safe-4337/constants' -abstract class SafeOperation { +abstract class BaseSafeOperation implements SafeOperation { userOperation: UserOperation options: SafeOperationOptions signatures: Map = new Map() @@ -69,4 +70,4 @@ abstract class SafeOperation { | typeof EIP712_SAFE_OPERATION_TYPE_V07 } -export default SafeOperation +export default BaseSafeOperation diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts index cbd70996e..6bc2073ef 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.test.ts @@ -9,7 +9,7 @@ import { } from '@safe-global/safe-modules-deployments' import { MetaTransactionData, OperationType } from '@safe-global/types-kit' import { Safe4337Pack } from './Safe4337Pack' -import SafeOperation from './SafeOperation' +import BaseSafeOperation from './BaseSafeOperation' import * as constants from './constants' import * as utils from './utils' import { @@ -382,7 +382,7 @@ describe('Safe4337Pack', () => { transactions }) - expect(safeOperation).toBeInstanceOf(SafeOperation) + expect(safeOperation).toBeInstanceOf(BaseSafeOperation) expect(safeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07, @@ -418,7 +418,7 @@ describe('Safe4337Pack', () => { transactions: [transferUSDC] }) - expect(safeOperation).toBeInstanceOf(SafeOperation) + expect(safeOperation).toBeInstanceOf(BaseSafeOperation) expect(safeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07, @@ -480,7 +480,7 @@ describe('Safe4337Pack', () => { transactions: [transferUSDC] }) - expect(sponsoredSafeOperation).toBeInstanceOf(SafeOperation) + expect(sponsoredSafeOperation).toBeInstanceOf(BaseSafeOperation) expect(sponsoredSafeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07, @@ -541,7 +541,7 @@ describe('Safe4337Pack', () => { const batch = [transferUSDC, approveTransaction] - expect(sponsoredSafeOperation).toBeInstanceOf(SafeOperation) + expect(sponsoredSafeOperation).toBeInstanceOf(BaseSafeOperation) expect(sponsoredSafeOperation.getSafeOperation()).toMatchObject({ safe: fixtures.SAFE_ADDRESS_v1_4_1_WITH_0_3_0_MODULE, entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07, diff --git a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts index 45695e8cd..7b1106ee5 100644 --- a/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts +++ b/packages/relay-kit/src/packs/safe-4337/Safe4337Pack.ts @@ -22,7 +22,7 @@ import { getSafeWebAuthnShareSignerDeployment } from '@safe-global/safe-modules-deployments' import { Hash, encodeFunctionData, zeroAddress, Hex, concat } from 'viem' -import SafeOperation from '@safe-global/relay-kit/packs/safe-4337/SafeOperation' +import BaseSafeOperation from '@safe-global/relay-kit/packs/safe-4337/BaseSafeOperation' import SafeOperationFactory from '@safe-global/relay-kit/packs/safe-4337/SafeOperationFactory' import { EstimateFeeProps, @@ -67,9 +67,9 @@ const EQ_OR_GT_1_4_1 = '>=1.4.1' */ export class Safe4337Pack extends RelayKitBasePack<{ EstimateFeeProps: EstimateFeeProps - EstimateFeeResult: SafeOperation + EstimateFeeResult: BaseSafeOperation CreateTransactionProps: Safe4337CreateTransactionProps - CreateTransactionResult: SafeOperation + CreateTransactionResult: BaseSafeOperation ExecuteTransactionProps: Safe4337ExecutableProps ExecuteTransactionResult: string }> { @@ -399,15 +399,15 @@ export class Safe4337Pack extends RelayKitBasePack<{ * Estimates gas for the SafeOperation. * * @param {EstimateFeeProps} props - The parameters for the gas estimation. - * @param {SafeOperation} props.safeOperation - The SafeOperation to estimate the gas. + * @param {BaseSafeOperation} props.safeOperation - The SafeOperation to estimate the gas. * @param {IFeeEstimator} props.feeEstimator - The function to estimate the gas. - * @return {Promise} The Promise object that will be resolved into the gas estimation. + * @return {Promise} The Promise object that will be resolved into the gas estimation. */ async getEstimateFee({ safeOperation, feeEstimator = new PimlicoFeeEstimator() - }: EstimateFeeProps): Promise { + }: EstimateFeeProps): Promise { const threshold = await this.protocolKit.getThreshold() const preEstimationData = await feeEstimator?.preEstimateUserOperationGas?.({ bundlerUrl: this.#BUNDLER_URL, @@ -457,12 +457,12 @@ export class Safe4337Pack extends RelayKitBasePack<{ * * @param {MetaTransactionData[]} transactions - The transactions to batch in a SafeOperation. * @param options - Optional configuration options for the transaction creation. - * @return {Promise} The Promise object will resolve a SafeOperation. + * @return {Promise} The Promise object will resolve a SafeOperation. */ async createTransaction({ transactions, options = {} - }: Safe4337CreateTransactionProps): Promise { + }: Safe4337CreateTransactionProps): Promise { const { amountToApprove, validUntil, validAfter, feeEstimator } = options const userOperation = await createUserOperation(this.protocolKit, transactions, { @@ -493,9 +493,9 @@ export class Safe4337Pack extends RelayKitBasePack<{ * Converts a SafeOperationResponse to an SafeOperation. * * @param {SafeOperationResponse} safeOperationResponse - The SafeOperationResponse to convert to SafeOperation - * @returns {SafeOperation} - The SafeOperation object + * @returns {BaseSafeOperation} - The SafeOperation object */ - #toSafeOperation(safeOperationResponse: SafeOperationResponse): SafeOperation { + #toSafeOperation(safeOperationResponse: SafeOperationResponse): BaseSafeOperation { const { validUntil, validAfter, userOperation } = safeOperationResponse const paymaster = (userOperation?.paymaster as Hex) || '0x' @@ -544,19 +544,19 @@ export class Safe4337Pack extends RelayKitBasePack<{ /** * Signs a safe operation. * - * @param {SafeOperation | SafeOperationResponse} safeOperation - The SafeOperation to sign. It can be: + * @param {BaseSafeOperation | SafeOperationResponse} safeOperation - The SafeOperation to sign. It can be: * - A response from the API (Tx Service) * - An instance of SafeOperation * @param {SigningMethod} signingMethod - The signing method to use. - * @return {Promise} The Promise object will resolve to the signed SafeOperation. + * @return {Promise} The Promise object will resolve to the signed SafeOperation. */ async signSafeOperation( - safeOperation: SafeOperation | SafeOperationResponse, + safeOperation: BaseSafeOperation | SafeOperationResponse, signingMethod: SigningMethod = SigningMethod.ETH_SIGN_TYPED_DATA_V4 - ): Promise { - let safeOp: SafeOperation + ): Promise { + let safeOp: BaseSafeOperation - if (safeOperation instanceof SafeOperation) { + if (safeOperation instanceof BaseSafeOperation) { safeOp = safeOperation } else { safeOp = this.#toSafeOperation(safeOperation) @@ -642,15 +642,15 @@ export class Safe4337Pack extends RelayKitBasePack<{ * Executes the relay transaction. * * @param {Safe4337ExecutableProps} props - The parameters for the transaction execution. - * @param {SafeOperation | SafeOperationResponse} props.executable - The SafeOperation to execute. It can be: + * @param {BaseSafeOperation | SafeOperationResponse} props.executable - The SafeOperation to execute. It can be: * - A response from the API (Tx Service) * - An instance of SafeOperation * @return {Promise} The user operation hash. */ async executeTransaction({ executable }: Safe4337ExecutableProps): Promise { - let safeOperation: SafeOperation + let safeOperation: BaseSafeOperation - if (executable instanceof SafeOperation) { + if (executable instanceof BaseSafeOperation) { safeOperation = executable } else { safeOperation = this.#toSafeOperation(executable) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts index 1d9dd0150..a9b470cfa 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationFactory.ts @@ -6,7 +6,7 @@ import { } from '@safe-global/types-kit' import SafeOperationV06 from '@safe-global/relay-kit/packs/safe-4337/SafeOperationV06' import SafeOperationV07 from '@safe-global/relay-kit/packs/safe-4337/SafeOperationV07' -import SafeOperation from '@safe-global/relay-kit/packs/safe-4337/SafeOperation' +import BaseSafeOperation from '@safe-global/relay-kit/packs/safe-4337/BaseSafeOperation' import { isEntryPointV6 } from '@safe-global/relay-kit/packs/safe-4337/utils' class SafeOperationFactory { @@ -19,7 +19,7 @@ class SafeOperationFactory { static createSafeOperation( userOperation: UserOperation, options: SafeOperationOptions - ): SafeOperation { + ): BaseSafeOperation { if (isEntryPointV6(options.entryPoint)) { return new SafeOperationV06(userOperation as UserOperationV06, options) } diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts index 2dc71701b..64a118640 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.test.ts @@ -2,7 +2,7 @@ import { Hex, encodePacked } from 'viem' import { EthSafeSignature } from '@safe-global/protocol-kit' import { fixtures } from '@safe-global/relay-kit/test-utils' import SafeOperationV06 from './SafeOperationV06' -import SafeOperation from './SafeOperation' +import BaseSafeOperation from './BaseSafeOperation' describe('SafeOperationV06', () => { it('should be an instance of SafeOperation', () => { @@ -12,7 +12,7 @@ describe('SafeOperationV06', () => { entryPoint: fixtures.ENTRYPOINT_ADDRESS_V06 }) - expect(safeOperation).toBeInstanceOf(SafeOperation) + expect(safeOperation).toBeInstanceOf(BaseSafeOperation) expect(safeOperation).toBeInstanceOf(SafeOperationV06) }) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts index bd8ef4ce4..30a6439b4 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV06.ts @@ -4,10 +4,10 @@ import { SafeUserOperation, SafeOperationOptions } from '@safe-global/types-kit' -import SafeOperation from '@safe-global/relay-kit/packs/safe-4337/SafeOperation' +import BaseSafeOperation from '@safe-global/relay-kit/packs/safe-4337/BaseSafeOperation' import { EIP712_SAFE_OPERATION_TYPE_V06 } from '@safe-global/relay-kit/packs/safe-4337/constants' -class SafeOperationV06 extends SafeOperation { +class SafeOperationV06 extends BaseSafeOperation { userOperation!: UserOperationV06 constructor(userOperation: UserOperationV06, options: SafeOperationOptions) { diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts index 878040ea7..db8babc8e 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.test.ts @@ -2,7 +2,7 @@ import { Hex, concat, encodePacked } from 'viem' import { EthSafeSignature } from '@safe-global/protocol-kit' import { fixtures } from '@safe-global/relay-kit/test-utils' import SafeOperationV07 from './SafeOperationV07' -import SafeOperation from './SafeOperation' +import BaseSafeOperation from './BaseSafeOperation' describe('SafeOperationV07', () => { it('should be an instance of SafeOperation', () => { @@ -12,7 +12,7 @@ describe('SafeOperationV07', () => { entryPoint: fixtures.ENTRYPOINT_ADDRESS_V07 }) - expect(safeOperation).toBeInstanceOf(SafeOperation) + expect(safeOperation).toBeInstanceOf(BaseSafeOperation) expect(safeOperation).toBeInstanceOf(SafeOperationV07) }) diff --git a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts index a68850806..f8944403c 100644 --- a/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts +++ b/packages/relay-kit/src/packs/safe-4337/SafeOperationV07.ts @@ -5,10 +5,10 @@ import { SafeOperationOptions } from '@safe-global/types-kit' import { concat, Hex, isAddress, pad, toHex } from 'viem' -import SafeOperation from '@safe-global/relay-kit/packs/safe-4337/SafeOperation' +import BaseSafeOperation from '@safe-global/relay-kit/packs/safe-4337/BaseSafeOperation' import { EIP712_SAFE_OPERATION_TYPE_V07 } from '@safe-global/relay-kit/packs/safe-4337/constants' -class SafeOperationV07 extends SafeOperation { +class SafeOperationV07 extends BaseSafeOperation { userOperation!: UserOperationV07 constructor(userOperation: UserOperationV07, options: SafeOperationOptions) { diff --git a/packages/relay-kit/src/packs/safe-4337/types.ts b/packages/relay-kit/src/packs/safe-4337/types.ts index 07e21792c..66e7063b7 100644 --- a/packages/relay-kit/src/packs/safe-4337/types.ts +++ b/packages/relay-kit/src/packs/safe-4337/types.ts @@ -11,7 +11,7 @@ import { SafeVersion, UserOperation } from '@safe-global/types-kit' -import SafeOperation from '@safe-global/relay-kit/packs/safe-4337/SafeOperation' +import BaseSafeOperation from '@safe-global/relay-kit/packs/safe-4337/BaseSafeOperation' import { RPC_4337_CALLS } from '@safe-global/relay-kit/packs/safe-4337/constants' type ExistingSafeOptions = { @@ -81,7 +81,7 @@ export type Safe4337CreateTransactionProps = { } export type Safe4337ExecutableProps = { - executable: SafeOperation | SafeOperationResponse + executable: BaseSafeOperation | SafeOperationResponse } export type EstimateSponsoredGasData = ( @@ -158,7 +158,7 @@ export interface IFeeEstimator { } export type EstimateFeeProps = { - safeOperation: SafeOperation + safeOperation: BaseSafeOperation feeEstimator?: IFeeEstimator } diff --git a/packages/types-kit/src/types.ts b/packages/types-kit/src/types.ts index b2dfff96e..a5b240cea 100644 --- a/packages/types-kit/src/types.ts +++ b/packages/types-kit/src/types.ts @@ -394,3 +394,18 @@ export type SafeOperationResponse = { } export type SafeOperationConfirmationListResponse = ListResponse + +export interface SafeOperation { + userOperation: UserOperation + options: SafeOperationOptions + signatures: Map + + addEstimations(estimations: EstimateGasData): void + getSafeOperation(): SafeUserOperation + getSignature(signer: string): SafeSignature | undefined + addSignature(signature: SafeSignature): void + encodedSignatures(): string + getUserOperation(): UserOperation + getHash(): string + getEIP712Type(): unknown +}