Delegation quickstart
Delegation is the ability for a MetaMask smart account to grant permission to another account to perform executions on its behalf.
In this quickstart, you will create a delegator account (the account that grants the permission) and delegate account (the account that receives the permission), and complete the delegation lifecycle (create, sign, and redeem a delegation).
This quickstart will refer to the delegator account as "Alice," who grants permission to "Bob," the delegate account, to perform executions on her behalf.
Prerequisites
Install and set up the Delegation Toolkit.
Steps
1. Set up a Public Client
Set up a Viem Public Client using Viem's createPublicClient
function. This client will let the delegator account 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 Viem Bundler Client using Viem's createBundlerClient
function. This lets you use the bundler service to estimate gas for user operations 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 delegator who will create a delegation. The delegator must be a smart account.
This example configures a Hybrid smart account, which is a flexible smart account implementation that supports both an externally owned account (EOA) owner and any number of P256 (passkey) signers:
import { Implementation, toMetaMaskSmartAccount } from '@metamask/delegation-toolkit'
import { privateKeyToAccount } from 'viem/accounts'
const delegatorAccount = privateKeyToAccount('0x...')
const delegatorSmartAccount = await toMetaMaskSmartAccount({
client: publicClient,
implementation: Implementation.Hybrid,
deployParams: [delegatorAccount.address, [], [], []],
deploySalt: '0x',
signatory: { account: delegatorAccount },
})
4. Create a delegate account
Create an account to represent Bob, the delegate who will receive the delegation. The delegate can be a smart account or an externally owned account (EOA).
This example configures a Hybrid smart account:
import { Implementation, toMetaMaskSmartAccount } from '@metamask/delegation-toolkit'
import { privateKeyToAccount } from 'viem/accounts'
const delegateAccount = privateKeyToAccount('0x...')
const delegateSmartAccount = await toMetaMaskSmartAccount({
client: publicClient,
implementation: Implementation.Hybrid,
deployParams: [delegateAccount.address, [], [], []],
deploySalt: '0x',
signatory: { account: delegateAccount },
})
5. Create a delegation
Create a root delegation from Alice to Bob. A root delegation is a delegation that doesn't derive its authority from another delegation. Alice is delegating her own authority away, as opposed to redelegating permissions she received from a previous delegation.
This example passes an empty caveats
array, which means Bob can perform any action on Alice's behalf. We recommend restricting the delegation by adding caveat enforcers.
For example, Alice can delegate the ability to sepnd her USDC to Bob, limiting the amount to 100 USDC.
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 } from '@metamask/delegation-toolkit'
const delegation = createDelegation({
to: delegateSmartAccount.address,
from: delegatorSmartAccount.address,
caveats: [], // Empty caveats array - we recommend adding appropriate restrictions.
})
6. Sign the delegation
Sign the delegation with Alice's account, using the signDelegation
method from MetaMaskSmartAccount
. Alternatively, you can use the Delegation Toolkit's signDelegation
utility. 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 redeemDelegation
utility function from the Delegation Toolkit.
import { createExecution } from '@metamask/delegation-toolkit'
import { DelegationManager } from '@metamask/delegation-toolkit/contracts'
import { SINGLE_DEFAULT_MODE } from '@metamask/delegation-toolkit/utils'
import { zeroAddress } from 'viem'
const delegations = [signedDelegation]
const executions = createExecution({ target: zeroAddress })
const redeemDelegationCalldata = DelegationManager.encode.redeemDelegations({
delegations: [delegations],
modes: [SINGLE_DEFAULT_MODE],
executions: [executions],
})
const userOperationHash = await bundlerClient.sendUserOperation({
account: delegateSmartAccount,
calls: [
{
to: delegateSmartAccount.address,
data: redeemDelegationCalldata,
},
],
maxFeePerGas: 1n,
maxPriorityFeePerGas: 1n,
})
If Bob's account (the delegate account) is an externally owned account (EOA) instead of a smart account, see how to redeem the delegation with an EOA.