Skip to content

Latest commit

 

History

History
273 lines (201 loc) · 9.78 KB

File metadata and controls

273 lines (201 loc) · 9.78 KB
description Use delegations to perform executions on a smart account's behalf.
sidebar_label Execute on a smart account's behalf
keywords
execution
smart account
create
redeem
delegation
delegator
delegate

import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; import GlossaryTerm from '@theme/GlossaryTerm';

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 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. Set up a Bundler Client

Set up a 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 ; 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 EOA owner and any number of passkey (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 },
})

:::note See how to configure other smart account types. :::

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

:::warning 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, 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 operation 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.

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