Skip to main content

Delegation Toolkit documentation

Embed MetaMask Smart Accounts into your dapp, enabling new user experiences.

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 Delegation Toolkit.

Steps

1. Create a Public Client

Create a Viem Public Client using Viem's createPublicClient function. You will configure Alice's account (the delegator) 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. Create a Bundler Client

Create a Viem Bundler Client using Viem's createBundlerClient function. You can 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 MetaMask smart account; use the toolkit's toMetaMaskSmartAccount method to create the delegator account.

A Hybrid smart account is a flexible smart account implementation that supports both an externally owned account (EOA) owner and any number of P256 (passkey) signers. This examples configures a Hybrid smart account with an Account signatory:

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):

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, // Hybrid smart account
deployParams: [delegateAccount.address, [], [], []],
deploySalt: "0x",
signatory: { account: delegateAccount },
})

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. 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 spend her USDC to Bob, limiting the amount to 100 USDC.

Important

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, // This example uses a delegate smart account
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 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 SINGLE_DEFAULT_MODE execution mode.

Bob can redeem the delegation by submitting a user operation if his account is a smart account, or a regular transaction if his account is an EOA:

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,
})