Send a user operation
User operations are the ERC-4337 counterpart to traditional blockchain transactions. They incorporate significant enhancements that improve user experience and provide greater flexibility in account management and transaction execution.
Viem's Account Abstraction API allows a developer to specify an array of Calls
that will be executed as a user operation via Viem's sendUserOperation
method.
The Delegation Toolkit encodes and executes the provided calls.
User operations are not directly sent to the network.
Instead, they are sent to a bundler, which validates, optimizes, and aggregates them before network submission.
See Viem's BundlerClient
for details on how to interact with the bundler.
If a user operation is sent from a smart contract account that has not been deployed, the toolkit configures the user operation to automatically deploy the account.
Prerequisites
- Install and set up the MetaMask Delegation Toolkit.
- Configure the toolkit.
- Create a delegator account.
Send a user operation
The following is a simplified example of sending a user operation using Viem Core SDK. Viem Core SDK offers more granular control for developers who require it.
In the example, a user operation is created with the necessary gas limits.
This user operation is passed to a bundler instance, and the EntryPoint
address is retrieved from the client.
// Configuration arguments are excluded here for clarity.
const bundler = createBundlerClient({...});
const delegatorSmartAccount = await toMetaMaskSmartAccount({...})
// Appropriate fee per gas must be determined for the specific bundler being used.
const maxFeePerGas = 1n;
const maxPriorityFeePerGas = 1n;
const userOperationHash = await bundler.sendUserOperation({
account: delegatorSmartAccount,
calls: [
{
to: "0x1234567890123456789012345678901234567890",
value: parseEther("1")
}
],
maxFeePerGas,
maxPriortyFeePerGas
});
Estimate fee per gas
Different bundlers have different ways to estimate maxFeePerGas
and maxPriorityFeePerGas
, and can reject requests with insufficient values.
The following example uses constant values, but the Hello Gator example use Pimlico's Alto bundler,
which fetches user operation gas price using the RPC method pimlico_getUserOperationPrice
.
import { createPimlicoClient } from "permissionless/clients/pimlico";
// ...
const pimlicoClient = createPimlicoClient({
transport: http(BUNDLER_URL),
});
// ...
const { fast: fee } = await pimlicoClient.getUserOperationGasPrice();
const userOperationHash = await bundler.sendUserOperation({
account: delegatorSmartAccount,
calls: [
{
to: "0x1234567890123456789012345678901234567890",
value: parseEther("1")
}
],
...fee
});
Wait for the transaction receipt
After submitting the user operation, it's crucial to wait for the receipt to ensure that it has been successfully included in the blockchain. Use the waitForUserOperationReceipt
method provided by the bundler client.
const receipt = await bundler.waitForUserOperationReceipt({
hash: userOperationHash
});
console.log(receipt.receipt.transactionHash);
The receipt
object is the UserOperationReceipt
, while receipt.receipt
is the TransactionReceipt
.