> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# MetaMask Delegation Framework (EIP-7710) with Dynamic

> Use a Dynamic embedded wallet as a MetaMask smart account delegator: grant scoped authority to an agent or another smart account to execute transactions on your behalf.

## Overview

[EIP-7710](https://eips.ethereum.org/EIPS/eip-7710) is the on-chain delegation standard that underpins the MetaMask Delegation Framework. It lets a smart account (the **delegator**) sign a delegation granting a second account (the **delegate**) the right to execute transactions on its behalf — subject to optional caveats like an ERC-20 spend cap.

The common pattern with Dynamic is a user's embedded wallet acting as delegator and an agent wallet (or another user's embedded wallet) acting as delegate. Both sides are MetaMask smart accounts.

**Signer vs smart account:** Each smart account is controlled by an underlying **signer**—a viem `Account` whose address appears in `walletClient.account`. The signer holds the key material (MPC for embedded wallets). The **smart account address** (`delegatorSmartAccount.address`, `delegateSmartAccount.address`) is the on-chain contract that holds assets and appears in delegation `from` / `to` fields. Do not confuse the two addresses.

This guide uses the JavaScript SDK throughout.

> [!NOTE]
>
> The delegator account must be deployed on-chain before any delegation can be redeemed. The guide covers deployment via a direct factory call — no bundler required on the delegator side.

## Stack

| Piece | Role |
| - | - |
| `@dynamic-labs-sdk/client` | Dynamic client, `getPrimaryWalletAccount` |
| `@dynamic-labs-sdk/evm` | EVM extensions and type guards |
| `@dynamic-labs-sdk/evm/viem` | `createWalletClientForWalletAccount` |
| `@metamask/smart-accounts-kit` | `toMetaMaskSmartAccount`, `createDelegation`, `ScopeType`, `ExecutionMode` |
| `@metamask/smart-accounts-kit/contracts` | `DelegationManager` |
| `viem` + `viem/account-abstraction` | Chain types, ABI encoding, bundler client |

## Dynamic's role

The MetaMask Delegation Framework requires a smart account as the delegator. Normally you'd use a plain private key as the signer for that smart account — Dynamic replaces that key with an embedded wallet backed by MPC. Everything else (smart accounts, delegation signing, on-chain redemption) is standard MetaMask SDK behaviour, documented in the [MetaMask Smart Accounts Kit guides](https://docs.metamask.io/smart-accounts-kit/guides/delegation/execute-on-smart-accounts-behalf/).

## Setup

**1. Install dependencies**

```bash npm theme={"system"}
npm install @dynamic-labs-sdk/client @dynamic-labs-sdk/evm @metamask/smart-accounts-kit viem
```

```bash yarn theme={"system"}
yarn add @dynamic-labs-sdk/client @dynamic-labs-sdk/evm @metamask/smart-accounts-kit viem
```

```bash pnpm theme={"system"}
pnpm add @dynamic-labs-sdk/client @dynamic-labs-sdk/evm @metamask/smart-accounts-kit viem
```

**2. Initialize Dynamic and get the embedded wallet**

Call `addWaasEvmExtension()` immediately after creating the client to register the embedded wallet provider.

```typescript theme={"system"}
import { createDynamicClient, getPrimaryWalletAccount } from '@dynamic-labs-sdk/client';
import { isEvmWalletAccount } from '@dynamic-labs-sdk/evm';
import { addWaasEvmExtension } from '@dynamic-labs-sdk/evm/waas';
import { createWalletClientForWalletAccount } from '@dynamic-labs-sdk/evm/viem';
import { createPublicClient, http } from 'viem';
import { sepolia } from 'viem/chains';

createDynamicClient({ environmentId: 'YOUR_ENVIRONMENT_ID' });
addWaasEvmExtension();

// After the user authenticates:
const walletAccount = getPrimaryWalletAccount();
if (!walletAccount || !isEvmWalletAccount(walletAccount)) {
  throw new Error('No EVM embedded wallet found');
}

const walletClient = await createWalletClientForWalletAccount({ walletAccount });
const publicClient = createPublicClient({ chain: sepolia, transport: http() });
```

* **`publicClient`** — read-only RPC (bytecode checks, receipts).
* **`walletClient`** — signs and sends transactions as the user’s embedded wallet (`walletClient.account` is `signerAccount` in the next step).

**3. Wrap signers as MetaMask smart accounts**

`toMetaMaskSmartAccount` turns a viem signer into a MetaMask **Hybrid** smart account. `Implementation.Hybrid` supports an EOA signer (your embedded wallet) controlling a contract wallet.

| Field | Meaning |
| - | - |
| `deployParams` | Constructor args for the smart account. For Hybrid: `[ownerAddress, validators, hooks, modules]`. Use the signer’s address as owner; empty arrays `[]` accept defaults for the rest. |
| `deploySalt` | Salt for deterministic deployment. `'0x'` uses the default. |
| `signer.account` | The viem account that signs UserOps and delegation EIP-712 payloads. For embedded wallets, signing routes through Dynamic’s MPC key share. |

### Delegator (user embedded wallet)

`signerAccount` is the authenticated user’s embedded wallet—the account behind `walletClient` from the previous step.

```typescript theme={"system"}
import { toMetaMaskSmartAccount, Implementation } from '@metamask/smart-accounts-kit';

const signerAccount = walletClient.account;
if (!signerAccount) throw new Error('walletClient has no account');

const delegatorSmartAccount = await toMetaMaskSmartAccount({
  client: publicClient,
  implementation: Implementation.Hybrid,
  deployParams: [signerAccount.address, [], [], []],
  deploySalt: '0x',
  signer: { account: signerAccount },
});
```

`delegatorSmartAccount.address` is the on-chain delegator contract address (not `signerAccount.address`).

### Delegate signer (`agentSignerAccount`)

The **delegate** redeems the delegation—often a backend **agent** or a second embedded wallet. It needs its own viem signer, same shape as `signerAccount`.

**`agentSignerAccount`** is that delegate signer: `agentWalletClient.account` from whichever wallet backs your agent. Common sources:

* A **server wallet** you create for the agent (see [Agents overview](https://docs.dynamic.xyz/docs/agents/overview.md))
* A second **embedded wallet** authenticated in your app
* Any viem `Account` / `PrivateKeyAccount` you control

Obtain it the same way as the user wallet—`createWalletClientForWalletAccount` for embedded wallets, or your server wallet’s viem client:

```typescript theme={"system"}
import { createWalletClientForWalletAccount } from '@dynamic-labs-sdk/evm/viem';

// Example: agent backed by a server or second embedded wallet
const agentWalletClient = await createWalletClientForWalletAccount({
  walletAccount: agentWalletAccount, // from getPrimaryWalletAccount() or your server wallet setup
});

const agentSignerAccount = agentWalletClient.account;
if (!agentSignerAccount) throw new Error('Agent walletClient has no account');
```

### Delegate smart account

Wrap `agentSignerAccount` the same way as the delegator:

```typescript theme={"system"}
const delegateSmartAccount = await toMetaMaskSmartAccount({
  client: publicClient,
  implementation: Implementation.Hybrid,
  deployParams: [agentSignerAccount.address, [], [], []],
  deploySalt: '0x',
  signer: { account: agentSignerAccount },
});
```

`delegateSmartAccount.address` is what you pass to `createDelegation` as `to`.

**4. Deploy the delegator smart account**

Counterfactual smart accounts have no bytecode until deployed. The **DelegationManager** verifies delegations by calling `isValidSignature` on the delegator contract, so that contract must exist on-chain before redemption.

`getFactoryArgs()` returns the MetaMask factory `to` address and `data` calldata. A single `sendTransaction` from the user’s `walletClient` deploys the contract—no bundler on the delegator side.

```typescript theme={"system"}
const code = await publicClient.getCode({ address: delegatorSmartAccount.address });

if (!code || code === '0x') {
  const { factory, factoryData } = await delegatorSmartAccount.getFactoryArgs();
  if (!factory || !factoryData) throw new Error('Factory args unavailable');

  await walletClient.sendTransaction({ to: factory, data: factoryData, value: 0n });
  console.log('Delegator deployed:', delegatorSmartAccount.address);
}
```

**5. Create and sign a delegation**

`createDelegation` builds the unsigned delegation struct. Fields that often need clarification:

| Field | Meaning |
| - | - |
| `from` / `to` | Smart account addresses (`delegatorSmartAccount.address` → `delegateSmartAccount.address`), not signer EOAs. |
| `environment` | Chain-specific MetaMask contract addresses (including DelegationManager). Copy from `delegatorSmartAccount.environment` so the delegation targets the same network as the account. |
| `scope` | On-chain **caveat** limiting what the delegate may do (here, max ERC-20 transfer per redemption). |

`signDelegation` signs the struct as EIP-712 typed data through the delegator’s MPC key. **`signedDelegation`** is `{ ...delegation, signature }`—store or pass it to the delegate for redemption. The DelegationManager checks this signature on-chain.

```typescript theme={"system"}
import { createDelegation, ScopeType } from '@metamask/smart-accounts-kit';
import { parseUnits } from 'viem';

const TOKEN_ADDRESS = '0xTOKEN_ADDRESS';

const delegation = createDelegation({
  from: delegatorSmartAccount.address,
  to: delegateSmartAccount.address,
  environment: delegatorSmartAccount.environment,
  scope: {
    type: ScopeType.Erc20TransferAmount,
    tokenAddress: TOKEN_ADDRESS,
    maxAmount: parseUnits('10', 6), // delegate can transfer up to 10 USDC
  },
});

const signature = await delegatorSmartAccount.signDelegation({ delegation });
const signedDelegation = { ...delegation, signature };
```

> [!NOTE]
>
> For an unrestricted delegation, use `createOpenDelegation` instead. The `scope` field is required on `createDelegation`. Unrestricted delegations should only be used in trusted contexts such as agent wallets you control.

**6. Redeem the delegation (delegate side)**

The delegate smart account submits an ERC-4337 **UserOperation** (via a **bundler** RPC). The UserOp calls `redeemDelegations` on the delegate contract; the **DelegationManager** validates `signedDelegation` and executes the inner call (for example the ERC-20 `transfer`) **on the delegator’s behalf**.

```typescript theme={"system"}
import { createExecution, ExecutionMode } from '@metamask/smart-accounts-kit';
import { DelegationManager } from '@metamask/smart-accounts-kit/contracts';
import { encodeFunctionData, parseUnits, erc20Abi } from 'viem';
import { createBundlerClient } from 'viem/account-abstraction';
import { http } from 'viem';

const bundlerClient = createBundlerClient({
  client: publicClient,
  transport: http('YOUR_BUNDLER_RPC'),
});

const callData = encodeFunctionData({
  abi: erc20Abi,
  functionName: 'transfer',
  args: ['0xRECIPIENT', parseUnits('5', 6)],
});

// createExecution = one call on the delegator's behalf; ExecutionMode.SingleDefault = one batch
const redeemCalldata = DelegationManager.encode.redeemDelegations({
  delegations: [[signedDelegation]],
  modes: [ExecutionMode.SingleDefault],
  executions: [[createExecution({ target: TOKEN_ADDRESS, callData })]],
});

const userOpHash = await bundlerClient.sendUserOperation({
  account: delegateSmartAccount,
  calls: [
    {
      to: delegateSmartAccount.address,
      data: redeemCalldata,
      value: 0n,
    },
  ],
});

await bundlerClient.waitForUserOperationReceipt({ hash: userOpHash });
console.log('Delegation redeemed');
```

> [!TIP]
>
> Use a paymaster on the bundler client to sponsor gas for the delegate. See the [Pimlico](https://docs.pimlico.io) or [Alchemy](https://docs.alchemy.com/docs/rundler) docs for paymaster setup.

> [accordion] EOA delegate (simpler alternative)
>
> If the delegate is a plain EOA rather than a smart account, it sends a regular transaction to the DelegationManager directly — no bundler needed.
>
> **`delegateWalletClient`** is a viem `WalletClient` for that EOA (same pattern as `walletClient`, but using the delegate’s account as `account`). The delegate signs and sends the redemption transaction itself.
>
> ```typescript theme={"system"}
> import { getSmartAccountsEnvironment } from '@metamask/smart-accounts-kit';
> import { sepolia } from 'viem/chains';
>
> const redeemCalldata = DelegationManager.encode.redeemDelegations({
>   delegations: [[signedDelegation]],
>   modes: [ExecutionMode.SingleDefault],
>   executions: [[createExecution({ target: TOKEN_ADDRESS, callData })]],
> });
>
> await delegateWalletClient.sendTransaction({
>   to: getSmartAccountsEnvironment(sepolia.id).DelegationManager,
>   data: redeemCalldata,
>   chain: sepolia,
>   account: delegateWalletClient.account,
> });
> ```

## Revoking a delegation

Send a UserOp from the delegator's smart account to disable the delegation on-chain. Reuse the same `bundlerClient` and `publicClient` from the redemption step.

```typescript theme={"system"}
import { DelegationManager } from '@metamask/smart-accounts-kit/contracts';
import { getSmartAccountsEnvironment } from '@metamask/smart-accounts-kit';
import { sepolia } from 'viem/chains';

const revokeCalldata = DelegationManager.encode.disableDelegation({
  delegation: signedDelegation,
});

await bundlerClient.sendUserOperation({
  account: delegatorSmartAccount,
  calls: [
    {
      to: getSmartAccountsEnvironment(sepolia.id).DelegationManager,
      data: revokeCalldata,
      value: 0n,
    },
  ],
});
```

## Available scope types

| `ScopeType` | What it constrains |
| - | - |
| `Erc20TransferAmount` | Max ERC-20 transfer amount per redemption |
| `NativeTokenTransferAmount` | Max native token (ETH) value per execution |
| `AllowedTargets` | Restrict which contract addresses can be called |
| `AllowedMethods` | Restrict which function selectors can be called |

A single `scope` sets one caveat. Combine several by passing a `caveats` array on `createDelegation` (each entry uses a `ScopeType` like the table above).

## Network support

Use `getSmartAccountsEnvironment(chainId)` to retrieve contract addresses for the target network — it throws if the chain is unsupported. Check MetaMask's [supported networks list](https://docs.metamask.io/smart-accounts-kit/get-started/supported-networks/) before going to production.

## References

* [MetaMask Smart Accounts Kit docs](https://docs.metamask.io/smart-accounts-kit/)
* [EIP-7710 specification](https://eips.ethereum.org/EIPS/eip-7710)
* [Dynamic JS SDK — Adding EVM extensions](https://docs.dynamic.xyz/docs/javascript/reference/evm/adding-evm-extensions.md)
* [Dynamic JS SDK — Getting a viem WalletClient](https://docs.dynamic.xyz/docs/javascript/reference/evm/getting-viem-wallet-client.md)
* [MetaMask smart account provider (React)](https://docs.dynamic.xyz/docs/react/smart-wallets/smart-wallet-providers/metamask.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.