Perform executions on a smart account's behalf
Delegation is the ability for a MetaMask smart account to grant permission to another account to perform executions on its behalf.
In this guide, you'll create a delegator account (Alice) and a delegate account (Bob), and grant Bob permission to perform executions on Alice's behalf. You'll complete the delegation lifecycle (create, sign, and redeem a delegation).
Prerequisites
Install and set up the Smart Accounts Kit.
Steps
1. Set up a Public Client
Set up a Public ClientPublic Client An interface for fetching blockchain state, reading from smart contracts, and monitoring transactions. using Viem's createPublicClient function.
You will configure Alice's account (the delegatorDelegator account The account that creates and signs a delegation to grant limited authority to another account.) and the Bundler Client with the Public Client, which you can use to query the signer's account state and interact with smart contracts.
import { createPublicClient, http } from "viem"
import { sepolia as chain } from "viem/chains"
const publicClient = createPublicClient({
chain,
transport: http(),
})
2. Set up a Bundler Client
Set up a Bundler ClientBundler A service that collects user operations from smart accounts, packages them, and submits them to the network. using Viem's createBundlerClient function.
You can use the bundler service to estimate gas for user operationsUser operation A signed instruction package that tells a smart account what executions to perform. and submit transactions to the network.
import { createBundlerClient } from "viem/account-abstraction"
const bundlerClient = createBundlerClient({
client: publicClient,
transport: http("https://your-bundler-rpc.com"),
})
3. Create a delegator account
Create an account to represent Alice, the delegatorDelegator account The account that creates and signs a delegation to grant limited authority to another account. who will create a delegation.
The delegator must be a MetaMask smart accountMetaMask smart account A smart contract account that supports programmable behavior, delegated permissions, flexible signing options, and other advanced features.; use the toolkit's toMetaMaskSmartAccount method to create the delegator account.
This example configures a Hybrid smart account, which is a flexible smart account implementation that supports both an EOAExternally owned account (EOA) A private-key-controlled account with no built-in programmable execution logic. owner and any number of passkeyPasskey A cryptographic key that can be used to sign transactions instead of a private key. (WebAuthn) signers:
import { Implementation, toMetaMaskSmartAccount } from "@metamask/smart-accounts-kit"
import { privateKeyToAccount } from "viem/accounts"
const delegatorAccount = privateKeyToAccount("0x...")
const delegatorSmartAccount = await toMetaMaskSmartAccount({
client: publicClient,
implementation: Implementation.Hybrid,
deployParams: [delegatorAccount.address, [], [], []],
deploySalt: "0x",
signer: { account: delegatorAccount },
})
4. Create a delegate account
Create an account to represent Bob, the delegateDelegate account The account that receives delegated authority and can redeem a delegation under its constraints. who will receive the delegation. The delegate can be a smart accountMetaMask smart account A smart contract account that supports programmable behavior, delegated permissions, flexible signing options, and other advanced features. or an EOAExternally owned account (EOA) A private-key-controlled account with no built-in programmable execution logic.:
- Smart account
- EOA
import { Implementation, toMetaMaskSmartAccount } from "@metamask/smart-accounts-kit"
import { privateKeyToAccount } from "viem/accounts"
const delegateAccount = privateKeyToAccount("0x...")
const delegateSmartAccount = await toMetaMaskSmartAccount({
client: publicClient,
implementation: Implementation.Hybrid, // Hybrid smart account
deployParams: [delegateAccount.address, [], [], []],
deploySalt: "0x",
signer: { account: delegateAccount },
})
import { privateKeyToAccount } from "viem/accounts";
import { sepolia as chain } from "viem/chains";
import { createWalletClient, http } from "viem";
const delegateAccount = privateKeyToAccount("0x...");
export const delegateWalletClient = createWalletClient({
account: delegateAccount,
chain,
transport: http(),
})
5. Create a delegation
Create a root delegation from Alice to Bob. With a root delegation, Alice is delegating her own authority away, as opposed to redelegating permissions she received from a previous delegation.
Use the toolkit's createDelegation method to create a root delegation. When creating
delegation, you need to configure the scope of the delegation to define the initial authority.
This example uses the erc20TransferAmount scope, allowing Alice to delegate to Bob the ability to spend her USDC, with a
specified limit on the total amount.
Before creating a delegation, ensure that the delegator account (in this example, Alice's account) has been deployed. If the account is not deployed, redeeming the delegation will fail.
import { createDelegation, ScopeType } from "@metamask/smart-accounts-kit"
import { parseUnits } from "viem"
// USDC address on Ethereum Sepolia.
const tokenAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"
const delegation = createDelegation({
to: delegateSmartAccount.address, // This example uses a delegate smart account
from: delegatorSmartAccount.address,
environment: delegatorSmartAccount.environment
scope: {
type: ScopeType.Erc20TransferAmount,
tokenAddress,
// 10 USDC
maxAmount: parseUnits("10", 6),
},
})
6. Sign the delegation
Sign the delegation with Alice's account, using the signDelegation method from MetaMaskSmartAccount. Alternatively, you can use the toolkit's signDelegation utility method. Bob will later use the signed delegation to perform actions on Alice's behalf.
const signature = await delegatorSmartAccount.signDelegation({
delegation,
})
const signedDelegation = {
...delegation,
signature,
}
7. Redeem the delegation
Bob can now redeem the delegation. The redeem transaction is sent to the DelegationManager contract, which validates the delegation and executes actions on Alice's behalf.
To prepare the calldata for the redeem transaction, use the redeemDelegations method from DelegationManager.
Since Bob is redeeming a single delegation chain, use the SingleDefault execution mode.
Bob can redeem the delegation by submitting a user operationUser operation A signed instruction package that tells a smart account what executions to perform. if his account is a smart account, or a regular transaction if his account is an EOA. In this example, Bob transfers 1 USDC from Alice’s account to his own.
- Redeem with a smart account
- Redeem with an EOA
- config.ts
import { createExecution, ExecutionMode } from "@metamask/smart-accounts-kit"
import { DelegationManager } from "@metamask/smart-accounts-kit/contracts"
import { zeroAddress } from "viem"
import { callData } from "./config.ts"
const delegations = [signedDelegation]
// USDC address on Ethereum Sepolia.
const tokenAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"
const executions = [createExecution({ target: tokenAddress, callData })]
const redeemDelegationCalldata = DelegationManager.encode.redeemDelegations({
delegations: [delegations],
modes: [ExecutionMode.SingleDefault],
executions: [executions],
})
const userOperationHash = await bundlerClient.sendUserOperation({
account: delegateSmartAccount,
calls: [
{
to: delegateSmartAccount.address,
data: redeemDelegationCalldata,
},
],
maxFeePerGas: 1n,
maxPriorityFeePerGas: 1n,
})
import { createExecution, getSmartAccountsEnvironment, ExecutionMode } from "@metamask/smart-accounts-kit"
import { DelegationManager } from "@metamask/smart-accounts-kit/contracts"
import { zeroAddress } from "viem"
import { callData } from "./config.ts"
const delegations = [signedDelegation]
// USDC address on Ethereum Sepolia.
const tokenAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"
const executions = [createExecution({ target: tokenAddress, callData })]
const redeemDelegationCalldata = DelegationManager.encode.redeemDelegations({
delegations: [delegations],
modes: [ExecutionMode.SingleDefault],
executions: [executions]
});
const transactionHash = await delegateWalletClient.sendTransaction({
to: getSmartAccountsEnvironment(chain.id).DelegationManager,
data: redeemDelegationCalldata,
chain,
})
import { encodeFunctionData, erc20Abi, parseUnits } from "viem"
// calldata to transfer 1 USDC to delegate address.
export const callData = encodeFunctionData({
abi: erc20Abi,
args: [ delegateSmartAccount.address, parseUnits("1", 6) ],
functionName: 'transfer',
})
Next steps
- See how to configure different scopes to define the initial authority of a delegation.
- See how to further refine the authority of a delegation using caveat enforcers.
- See how to disable a delegation to revoke permissions.