Safe4337Pack
The Safe4337Pack enables Safe accounts to interact with user operations through the implementation of the RelayKitBasePack. You can find more about ERC-4337 at this link (opens in a new tab).
Install dependencies
To use Safe4337Pack in your project, start by installing the relay-kit package with this command:
_10yarn add @safe-global/relay-kit
Reference
The Safe4337Pack class make easy to use the Safe 4337 Module (opens in a new tab) with your Safe. It enables creating, signing, and executing transactions grouped in user operations using a selected provider. You can select your preferred bundler (opens in a new tab) and paymaster (opens in a new tab).
_10const safe4337Pack = await Safe4337Pack.init({_10 provider,_10 signer,_10 bundlerUrl,_10 safeModulesVersion,_10 customContracts,_10 options,_10 paymasterOptions_10})
init(safe4337InitOptions)
The static method init() generates an instance of Safe4337Pack. Use this method to create the initial instance instead of the regular constructor.
We have added support for then Entrypoint v0.7 contract but we are not making it the default yet.
If you are using Entrypoint v0.7, you need to set the safeModuleVersion to 0.3.0 when calling the Safe4337Pack.init method. This version of the Safe 4337 Module is the one compatible with the Entrypoint v0.7.
Parameters
The Safe4337InitOptions used in the init() method are:
_47Safe4337InitOptions = {_47 provider: Eip1193Provider | HttpTransport | SocketTransport_47 signer?: HexAddress | PrivateKey | PasskeyArgType_47 bundlerUrl: string_47 safeModulesVersion?: string_47 customContracts?: {_47 entryPointAddress?: string_47 safe4337ModuleAddress?: string_47 addModulesLibAddress?: string_47 }_47 options: ExistingSafeOptions | PredictedSafeOptions_47 paymasterOptions?: PaymasterOptions_47}_47_47HexAddress = string_47PrivateKey = string_47HttpTransport = string_47SocketTransport = string_47_47Eip1193Provider = {_47 request: (args: RequestArguments) => Promise<unknown>_47}_47_47RequestArguments = {_47 method: string_47 params?: readonly unknown[] | object_47}_47_47ExistingSafeOptions = {_47 safeAddress: string_47}_47_47PredictedSafeOptions = {_47 owners: string[]_47 threshold: number_47 safeVersion?: SafeVersion_47 saltNonce?: string_47}_47_47PaymasterOptions = {_47 paymasterUrl?: string_47 isSponsored?: boolean_47 sponsorshipPolicyId?: string_47 paymasterAddress: string_47 paymasterTokenAddress?: string_47 amountToApprove?: bigint_47}
provider: The EIP-1193 compatible provider or RPC URL of the selected chain.signer: A passkey or the signer private key if theproviderdoesn't resolve to a signer account. If theproviderresolves to multiple signer addresses, thesignerproperty can be used to specify which account to connect, otherwise the first address returned will be used.rpcUrl: The RPC URL of the selected chain.bundlerUrl: The bundler's URL.safeModulesVersion: The version of the Safe Modules contract (opens in a new tab).customContracts: An object with custom contract addresses. This is optional, if no custom contracts are provided, default ones will be used.entryPointAddress: The address of the entry point. Defaults to the address returned by theeth_supportedEntryPointsmethod from the provider API.safe4337ModuleAddress: The address of theSafe4337Module. Defaults tosafe-modules-deploymentsusing the current version.addModulesLibAddress: The address of theAddModulesLiblibrary. Defaults tosafe-modules-deploymentsusing the current version.
options: The Safe account options.safeAddress: The Safe address. You can only use this prop to specify an existing Safe account.owners: The array with Safe owners.threshold: The Safe threshold. This is the number of owners required to sign and execute a transaction.safeVersion: The version of the Safe contract (opens in a new tab). Defaults to the current version.saltNonce: The Safe salt nonce. Changing this value enables the creation of different safe (predicted) addresses using the same configuration (owners,threshold, andsafeVersion).
paymasterOptions: The paymaster options.paymasterUrl: The paymaster URL. You can obtain the URL from the management dashboard of the selected services provider. This URL will be used for gas estimations.isSponsored: A boolean flag to indicate if we want to use a paymaster to sponsor transactions.sponsorshipPolicyId: The sponsorship policy ID can be obtained from the management dashboard of the selected payment services provider.paymasterAddress: The address of the paymaster contract to use.paymasterTokenAddress: The paymaster token address for transaction fee payments.amountToApprove: ThepaymasterTokenAddressamount to approve.
Returns
A promise that resolves to an instance of the Safe4337Pack.
Caveats
- Use this method to create the initial instance instead of the standard constructor.
- You should search for some API services URLs and contract addresses in the management dashboards of your selected provider. These include
bundlerUrl,paymasterUrl,paymasterAddress,paymasterTokenAddress,sponsorshipPolicyId, andrpcUrl(In this case any valid RPC should be fine). - The SDK uses default versions when
safeModulesVersionorsafeVersionare not specified. You can find more details about the current versions here (opens in a new tab). - The
saltNoncederives different Safe addresses by using theprotocol-kitmethodpredictSafeAddress. You can find more details about this process here (opens in a new tab). - We typically initialize the pack in two ways. One way is by using an existing account with the
safeAddressprop. The other way is by using theowners,threshold,saltNonce, andsafeVersionprops to create a new Safe account. You can also apply the second method to existing addresses, as the output address will be the same if the inputs are identical. - The SDK queries
eth_supportedEntryPointsfor a defaultentryPointAddressif not given. It fetchessafe4337ModuleAddressandaddModulesLibAddressfrom thesafe-modules-deploymentsrepository if not provided. You can find them at: safe-modules-deployments (opens in a new tab). - To use a paymaster without sponsorship, you need to hold a certain amount of
paymasterTokenAddressin the Safe account for fees. Make sure to provide thepaymasterAddressas well. - You can choose to use a paymaster to sponsor transactions by setting the
isSponsoredprop. When sponsoring transactions, you need to provide thepaymasterUrl,paymasterAddress, and optionally thesponsorshipPolicyId. - An approval for the concrete ERC-20 token is required to use the paymaster so remember to add the
paymasterTokenAddressof the ERC-20 token that will pay the fees. The SDK will encode this approval internally and send it to the bundler with the rest of the user operation. - Specify the amount to approve for the
paymasterTokenAddressusing theamountToApproveprop. This is necessary when the Safe account is not deployed, and you need to approve the paymaster token for fee payments and Safe account setup.
new Safe4337Pack({protocolKit, bundlerClient, publicClient, bundlerUrl, paymasterOptions, entryPointAddress, safe4337ModuleAddress})
The Safe4337Pack constructor method is used within the init() method and should not be directly accessed. The parameters are calculated or provided by the init() method.
createTransaction(safe4337CreateTransactionProps)
Create a SafeOperation from a transaction batch. You can send multiple transactions to this method. The SDK internally bundles these transactions into a batch sent to the bundler as a UserOperation. If the transaction is only one then no batch is created a it's not necessary.
Parameters
The Safe4337CreateTransactionProps
_10Safe4337CreateTransactionProps = {_10 transactions: MetaTransactionData[]_10 options?: {_10 amountToApprove?: bigint_10 validUntil?: number_10 validAfter?: number_10 feeEstimator?: IFeeEstimator_10 customNonce?: bigint_10 }_10}
transactions: Array ofMetaTransactionDatato batch in aSafeOperation(using the MultiSend contract if more than one transaction is included).options: Optional parameters.amountToApprove: The amount to approve to thepaymasterTokenAddress.validUntil: The UserOperation will remain valid until this block's timestamp.validAfter: The UserOperation will be valid after this block's timestamp.feeEstimator: The fee estimator calculates gas requirements by implementing theIFeeEstimatorinterface.customNonce: The custom nonce for the SafeOperation. If not provided, the nonce will be calculated internally using thegetNoncemethod from the Entrypoint contract.
Returns
A promise that resolves to the SafeOperation.
Caveats
- The
SafeOperationis similar to the standard user operation but includes Safe-specific fields. Before sending it to the bundler, we convert theSafeOperationto a regular user operation. We need to sign the operation for the bundler to execute it using theSafe4337Module. - You can set the
amountToApprovein this method to approve thepaymasterTokenAddressfor transaction payments, similar to howamountToApproveworks in theinit()method. - We use a similar API to
protocol-kitfor developers transitioning toSafe4337Pack. This API helps with creating and executing transactions, bundling user operations and sending them to the bundler. - Use
validUntilandvalidAfterto set the block timestamp range for the user operation's validity. The operation will be rejected if the block timestamp falls outside this range. - The
feeEstimatorcalculates gas needs for the UserOperation. We default toPimlicoFeeEstimator, but therelay-kitpackage also offers an alternative,GenericFeeEstimator, which does not depend on any specific bundler. You can also provide your own estimator. The IFeeEstimator interface requires an object with specific methods. - User operations support the usage of custom nonce (opens in a new tab). You can use a custom nonce by leveraging the
createTransactionmethod and passing thecustomNonceproperty as part of theoptionsparameter. We exported an utility methodencodeNoncein therelay-kitto make it easier to compose the nonce.
_17IFeeEstimator {_17 preEstimateUserOperationGas?: EstimateFeeFunction_17 postEstimateUserOperationGas?: EstimateFeeFunction _17}_17_17EstimateFeeFunctionProps = {_17 userOperation: UserOperation_17 bundlerUrl: string_17 entryPoint: string_17}_17_17EstimateSponsoredFeeFunctionProps = {_17 userOperation: UserOperation_17 paymasterUrl: string_17 entryPoint: string_17 sponsorshipPolicyId?: string_17}
All methods are optional and will be called in the specified order if you provide any of them:
preEstimateUserOperationGas: This method is used before calling the standard RPCeth_estimateUserOperationGasmethod, allowing you to setup the User operation before the bundler gas estimation.postEstimateUserOperationGas: This method is used after callingeth_estimateUserOperationGasmethod to adjust the bundler estimation.
signSafeOperation(safeOperation, signingMethod)
Signs a SafeOperation.
Parameters
safeOperation: TheEthSafeOperation | SafeOperationResponseto sign. Can either be created by theSafe4337Packor fetched viaapi-kit.signingMethod: The method to use for signing the transaction. The default isSigningMethod.ETH_SIGN_TYPED_DATA_V4.
Returns
A promise that resolves to the signed SafeOperation.
Caveats
- Use this method after the
SafeOperationis generated with thecreateTransactionmethod. - This method adds the signer's signature to the signatures map of the
SafeOperationobject. Additional signatures can be included from multiple owners. - It works similar to
signTransactionandsignMessagemethods in theprotocol-kitbut usingSafeOperationinstead ofSafeTransactionorSafeMessage. For more information, refer to the Safe docs (opens in a new tab).
executeTransaction(safe4337ExecutableProps)
This method sends the user operation to the bundler.
If you are not using a paymaster and need to deploy a new Safe (counterfactual deployment), you must hold in the predicted Safe address the amount of native token required to cover the fees.
Parameters
The Safe4337ExecutableProps
_10Safe4337ExecutableProps = {_10 executable: EthSafeOperation | SafeOperationResponse_10}
executable: TheSafeOperationto execute. Can either be created by theSafe4337Packor fetched viaapi-kit.
Returns A promise, resolves to the user operation hash.
Caveats
- The process converts the
SafeOperationto a standard user operation, then forwards it to the bundler. TheSafeOperationmust be created and signed by the Safe owner. - You can use the user operation hash to browse the status (e.g
https://jiffyscan.xyz/userOpHash/{userOpHash})
getUserOperationByHash(userOpHash)
Retrieve the user operation using its hash.
Parameters
userOpHash: The user operation hash is returned by theexecuteTransactionmethod. The user operation can be executed or pending, and the method will return the payload data for the user operation.
Returns
A Promise that resolves to UserOperationWithPayload.
_10UserOperationWithPayload = {_10 userOperation: UserOperation_10 entryPoint: string_10 transactionHash: string_10 blockHash: string_10 blockNumber: string_10}
Caveats
- Use this method to request information about the user operation sent to the bundler, but do not use it for the execution status.
getUserOperationReceipt(userOpHash)
Get UserOperation receipt by a hash.
Parameters
userOpHash: Unique identifier for theUserOperation
Returns
A Promise that resolves to UserOperationReceipt after the user operation is executed.
_10UserOperationReceipt = {_10 userOpHash: string_10 sender: string_10 nonce: string_10 actualGasUsed: string_10 actualGasCost: string_10 success: boolean_10 logs: Log[]_10 receipt: Receipt_10}
Caveats
- Use this method to obtain the full execution trace and status.
- You can use this method to check if the
UserOperationwas successful by calling it repeatedly until the receipt is available.
getSupportedEntryPoints()
Retrieve all supported entry points.
Returns A promise that resolves to an array of entry point addresses (strings) supported by the bundler.
Caveats
We use this method to obtain the default entry point if not provided in the init() method.
getChainId()
Retrieve the EIP-155 Chain ID.
Returns A promise that resolves to the EIP-155 Chain ID string.