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.
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 theprovider
doesn't resolve to a signer account. If theprovider
resolves to multiple signer addresses, thesigner
property 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_supportedEntryPoints
method from the provider API.safe4337ModuleAddress
: The address of theSafe4337Module
. Defaults tosafe-modules-deployments
using the current version.addModulesLibAddress
: The address of theAddModulesLib
library. Defaults tosafe-modules-deployments
using 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
: ThepaymasterTokenAddress
amount 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
safeModulesVersion
orsafeVersion
are not specified. You can find more details about the current versions here (opens in a new tab). - The
saltNonce
derives different Safe addresses by using theprotocol-kit
methodpredictSafeAddress
. 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
safeAddress
prop. The other way is by using theowners
,threshold
,saltNonce
, andsafeVersion
props 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_supportedEntryPoints
for a defaultentryPointAddress
if not given. It fetchessafe4337ModuleAddress
andaddModulesLibAddress
from thesafe-modules-deployments
repository 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
paymasterTokenAddress
in the Safe account for fees. Make sure to provide thepaymasterAddress
as well. - You can choose to use a paymaster to sponsor transactions by setting the
isSponsored
prop. 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
paymasterTokenAddress
of 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
paymasterTokenAddress
using theamountToApprove
prop. 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 }_10}
transactions
: Array ofMetaTransactionData
to 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 theIFeeEstimator
interface.
Returns
A promise that resolves to the SafeOperation
.
Caveats
- The
SafeOperation
is similar to the standard user operation but includes Safe-specific fields. Before sending it to the bundler, we convert theSafeOperation
to a regular user operation. We need to sign the operation for the bundler to execute it using theSafe4337Module
. - You can set the
amountToApprove
in this method to approve thepaymasterTokenAddress
for transaction payments, similar to howamountToApprove
works in theinit()
method. - We use a similar API to
protocol-kit
for developers transitioning toSafe4337Pack
. This API helps with creating and executing transactions, bundling user operations and sending them to the bundler. - Use
validUntil
andvalidAfter
to 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
feeEstimator
calculates gas needs for the UserOperation. We default to Pimlico'sfeeEstimator
, but you can use a different one by providing your own. The IFeeEstimator interface requires an object with specific methods.
_18IFeeEstimator {_18 setupEstimation?: EstimateFeeFunction_18 adjustEstimation?: EstimateFeeFunction_18 getPaymasterEstimation?: EstimateSponsoredFeeFunction_18}_18_18 EstimateFeeFunctionProps = {_18 userOperation: UserOperation_18 bundlerUrl: string_18 entryPoint: string_18}_18_18EstimateSponsoredFeeFunctionProps = {_18 userOperation: UserOperation_18 paymasterUrl: string_18 entryPoint: string_18 sponsorshipPolicyId?: string_18}
All methods are optional and will be called in the specified order if you provide any of them:
setupEstimation
: This method, called before using the bundlereth_estimateUserOperationGas
in the pack code, allows you to adjust the user operation before the bundler estimates it, as each provider has its own recommendations.adjustEstimation
: This method is used after callingeth_estimateUserOperationGas
in the pack code to adjust the bundler estimation.getPaymasterEstimation
: After using the bundlereth_estimateUserOperationGas
from the package code, this method is used if the user operation is sponsored. It helps adjust the bundler's estimation when a paymaster sponsors the transaction that use to involve some specific fee estimations.
signSafeOperation(safeOperation, signingMethod)
Signs a SafeOperation
.
Parameters
safeOperation
: TheEthSafeOperation | SafeOperationResponse
to sign. Can either be created by theSafe4337Pack
or 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
SafeOperation
is generated with thecreateTransaction
method. - This method adds the signer's signature to the signatures map of the
SafeOperation
object. Additional signatures can be included from multiple owners. - It works similar to
signTransaction
andsignMessage
methods in theprotocol-kit
but usingSafeOperation
instead ofSafeTransaction
orSafeMessage
. 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
: TheSafeOperation
to execute. Can either be created by theSafe4337Pack
or fetched viaapi-kit
.
Returns A promise, resolves to the user operation hash.
Caveats
- The process converts the
SafeOperation
to a standard user operation, then forwards it to the bundler. TheSafeOperation
must 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 theexecuteTransaction
method. 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
UserOperation
was 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.