# MetaMask Smart Accounts Kit documentation > Complete documentation for MetaMask Smart Accounts Kit This file contains all documentation content in a single document following the llmstxt.org standard. ## Advanced Permissions (ERC-7715) The Smart Accounts Kit supports Advanced Permissions ([ERC-7715](https://eips.ethereum.org/EIPS/eip-7715)), which lets you request fine-grained permissions from a MetaMask user to execute transactions on their behalf. For example, a user can grant your dapp permission to spend 10 USDC per day to buy ETH over the course of a month. Once the permission is granted, your dapp can use the allocated 10 USDC each day to purchase ETH directly from the MetaMask user's account. Advanced Permissions eliminate the need for users to approve every transaction, which is useful for highly interactive dapps. It also enables dapps to execute transactions for users without an active wallet connection. :::note This feature requires [MetaMask Flask 13.5.0](/snaps/get-started/install-flask) or later. ::: ## ERC-7715 technical overview [ERC-7715](https://eips.ethereum.org/EIPS/eip-7715) defines a JSON-RPC method `wallet_grantPermissions`. Dapps can use this method to request a wallet to grant the dapp permission to execute transactions on a user's behalf. `wallet_grantPermissions` requires a `signer` parameter, which identifies the entity requesting or managing the permission. Common signer implementations include wallet signers, single key and multisig signers, and account signers. The Smart Accounts Kit supports multiple signer types. The documentation uses [an account signer](../guides/advanced-permissions/execute-on-metamask-users-behalf.md) as a common implementation example. When you use an account signer, a session account is created solely to request and redeem Advanced Permissions, and doesn't contain tokens. The session account can be granted with permissions and redeem them as specified in [ERC-7710](https://eips.ethereum.org/EIPS/eip-7710). The session account can be a smart account or an externally owned account (EOA). The MetaMask user that the session account requests permissions from must be upgraded to a [MetaMask smart account](smart-accounts.md). ## Advanced Permissions vs. delegations Advanced Permissions expand on regular [delegations](delegation/index.md) by enabling permission sharing *via the MetaMask browser extension*. With regular delegations, the dapp constructs a delegation and requests the user to sign it. These delegations are not human-readable, so it is the dapp's responsibility to provide context for the user. Regular delegations cannot be signed through the MetaMask extension, because if a dapp requests a delegation without constraints, the whole wallet can be exposed to the dapp. In contrast, Advanced Permissions enable dapps (and AI agents) to request permissions from a user directly via the MetaMask extension. Advanced Permissions require a permission configuration which displays a human-readable confirmation for the MetaMask user. The user can modify the permission parameters if the request is configured to allow adjustments. For example, the following Advanced Permissions request displays a rich UI including the start time, amount, and period duration for an [ERC-20 token periodic transfer](../guides/advanced-permissions/use-permissions/erc20-token.md#erc-20-periodic-permission): ## Advanced Permissions lifecycle The Advanced Permissions lifecycle is as follows: 1. **Set up a session account** - Set up a session account to execute transactions on behalf of the MetaMask user. It can be a [smart account](smart-accounts.md) or an externally owned account (EOA). 2. **Request permissions** - Request permissions from the user. The Smart Accounts Kit supports [ERC-20 token permissions](../guides/advanced-permissions/use-permissions/erc20-token.md) and [native token permissions](../guides/advanced-permissions/use-permissions/native-token.md). 4. **Redeem permissions** - Once the permission is granted, the session account can redeem the permission, executing on the user's behalf. See [how to perform executions on a MetaMask user's behalf](../guides/advanced-permissions/execute-on-metamask-users-behalf.md) to get started with the Advanced Permissions lifecycle. --- ## Caveat enforcers The Smart Accounts Kit provides *caveat enforcers*, which are smart contracts that implement rules and restrictions (*caveats*) on delegations. They serve as the underlying mechanism that enables conditional execution within the [Delegation Framework](./index.md#delegation-framework). A caveat enforcer acts as a gate that validates whether a delegation can be used for a particular execution. When a delegate attempts to execute an action on behalf of a delegator, each caveat enforcer specified in the delegation evaluates whether the execution meets its defined criteria. :::warning Important - Without caveat enforcers, a delegation has infinite and unbounded authority to make any execution the original account can make. We strongly recommend using caveat enforcers. - Caveat enforcers safeguard the execution process but do not guarantee a final state post-redemption. Always consider the full impact of combined caveat enforcers. ::: ## Smart contract interface Caveat enforcers are Solidity contracts that implement the [`ICaveatEnforcer`](https://github.com/MetaMask/delegation-framework/blob/main/src/interfaces/ICaveatEnforcer.sol) interface: ```solidity // SPDX-License-Identifier: MIT AND Apache-2.0 pragma solidity 0.8.23; /** * This is an abstract contract that exposes pre and post Execution hooks during delegation redemption. */ interface ICaveatEnforcer { /** * Enforces conditions before any actions in a batch redemption process begin. */ function beforeAllHook( bytes calldata _terms, // The terms to enforce set by the delegator. bytes calldata _args, // An optional input parameter set by the redeemer at time of invocation. ModeCode _mode, // The mode of execution for the executionCalldata. bytes calldata _executionCalldata, // The data representing the execution. bytes32 _delegationHash, // The hash of the delegation. address _delegator, // The address of the delegator. address _redeemer // The address that is redeeming the delegation. ) external; /** * Enforces conditions before the execution tied to a specific delegation in the redemption process. */ function beforeHook( bytes calldata _terms, bytes calldata _args, ModeCode _mode, bytes calldata _executionCalldata, bytes32 _delegationHash, address _delegator, address _redeemer ) external; /** * Enforces conditions after the execution tied to a specific delegation in the redemption process. */ function afterHook( bytes calldata _terms, bytes calldata _args, ModeCode _mode, bytes calldata _executionCalldata, bytes32 _delegationHash, address _delegator, address _redeemer ) external; /** * Enforces conditions after all actions in a batch redemption process have been executed. */ function afterAllHook( bytes calldata _terms, bytes calldata _args, ModeCode _mode, bytes calldata _executionCalldata, bytes32 _delegationHash, address _delegator, address _redeemer ) external; } ``` The interface consists of four key hook functions that are called at different stages of the delegation redemption process: 1. **`beforeAllHook`**: Called before any actions in a batch redemption process begin. This can be used to verify conditions that must be true for the entire batch execution. 2. **`beforeHook`**: Called before the execution tied to a specific delegation. This allows for pre-execution validation of conditions specific to that delegation. 3. **`afterHook`**: Called after the execution tied to a specific delegation completes. This can verify post-execution state changes or effects specific to that delegation. 4. **`afterAllHook`**: Called after all actions in a batch redemption process have completed. This enables verification of final conditions after the entire batch has executed. Each of these hooks receives comprehensive information about the execution context, including: - The caveat terms specified by the delegator. - Optional arguments provided by the redeemer. - The execution mode and calldata. - The delegation hash. - The delegator and redeemer addresses. ### Caveat enforcer rejection The most important safety feature of these hooks is their ability to block executions: - If any hook determines its conditions aren't met, it will **revert** (throw an exception). - When a reversion occurs, the entire delegation redemption process is canceled. - This prevents partial or invalid executions from occurring. - No state changes from the attempted execution will be committed to the blockchain. This "all-or-nothing" approach ensures that delegations only execute exactly as intended by their caveats. ## Caveat builder While caveat enforcers operate at the smart contract level, most developers interact with them through the `CaveatBuilder` interface in the Smart Accounts Kit. The `CaveatBuilder` provides a developer-friendly TypeScript API that: - Abstracts away the complexity of correctly formatting and encoding caveat terms. - Provides type-checking and validation for caveat parameters. - Handles the creation of the `caveats` array needed when creating a delegation. Each [caveat type](../../reference/delegation/caveats.md) in the `CaveatBuilder` corresponds to a specific caveat enforcer contract. For example, when you use: ```typescript caveatBuilder.addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92"]); ``` The builder is creating a caveat that references the [`AllowedTargetsEnforcer`](../../reference/delegation/caveats.md#allowedtargets) contract address and properly encodes the provided addresses as terms for that enforcer. ## Caveat enforcer best practices When designing delegations with caveats, consider these best practices: - **Combine caveat enforcers appropriately** - Use multiple caveat enforcers to create comprehensive restrictions. - **Consider caveat enforcer order** - When using caveat enforcers that modify external contract states, the order matters. For example, using [`NativeTokenPaymentEnforcer`](../../reference/delegation/caveats.md#nativetokenpayment) before [`NativeBalanceChangeEnforcer`](../../reference/delegation/caveats.md#nativebalancechange) might cause validation failures. - **Be careful with unbounded delegations** - Always include appropriate caveat enforcers to limit what a delegate can do. ## Available caveat enforcers The Smart Accounts Kit provides [out-of-the-box caveat enforcers](../../reference/delegation/caveats.md) for common restriction patterns, including: - Limiting target addresses and methods. - Setting time or block number constraints. - Restricting token transfers and approvals. - Limiting execution frequency. For other restriction patterns, you can also [create custom caveat enforcers](/tutorials/create-custom-caveat-enforcer) by implementing the `ICaveatEnforcer` interface. ## Attenuating authority with redelegations When creating chains of delegations via [redelegations](./index.md#delegation-types), it's important to understand how authority flows and can be restricted. Caveats applied to a chain of delegations are *accumulative*—they stack on top of each other: - Each delegation in the chain inherits all restrictions from its parent delegation. - New caveats can add further restrictions, but can't remove existing ones. This means that a delegate can only redelegate with equal or lesser authority than they received. ### Example: Narrowing permissions Imagine a simple financial delegation scenario: 1. **Alice delegates to Bob**, allowing him to withdraw up to 100 USDC on her behalf. 2. **Bob re-delegates to Carol**, but limits the permission to: - Only 50 USDC (reducing the amount). - Only before the end of the week (adding a time constraint). Carol now has a more restricted version of Alice's original delegation. Bob couldn't give Carol more authority than he had (such as allowing her to withdraw 200 USDC), but he could narrow the permission. --- ## Delegation *Delegation* is the ability for a [MetaMask smart account](../smart-accounts.md) to grant permission to another smart account or externally owned account (EOA) to perform specific executions on its behalf. The account that grants the permission is called the *delegator account*, while the account that receives the permission is called the *delegate account*. The Smart Accounts Kit follows the [ERC-7710](https://eips.ethereum.org/EIPS/eip-7710) standard for smart contract delegation. In addition, users can use [caveat enforcers](caveat-enforcers.md) to apply rules and restrictions to delegations. For example: Alice delegates the ability to spend her USDC to Bob, limiting the amount to 100 USDC. ## Delegation lifecycle The delegation lifecycle is as follows: 1. **Create a delegation** - The delegator account creates a delegation, applying *caveats* which specify conditions under which the delegation can be redeemed. The delegator signs the delegation. 3. **Store the delegation** - A dapp can store the delegation, enabling retrieval for future redemption. 4. **Redeem the delegation** - The delegate (the account being granted the permission) redeems the delegation via the Delegation Manager, which verifies that the delegated authority is valid in order to perform the execution. See [how to perform executions on a smart account's behalf](../../guides/delegation/execute-on-smart-accounts-behalf.md) to get started with the delegation lifecycle. ## Delegation types You can create the following delegation types: - **Root delegation** - A root delegation is when a delegator delegates their own authority away, as opposed to *redelegating* permissions they received from a previous delegation. In a chain of delegations, the first delegation is the root delegation. For example, Alice delegates the ability to spend her USDC to Bob, limiting the amount to 100 USDC. Use [`createDelegation`](../../reference/delegation/index.md#createdelegation) to create a root delegation. - **Open root delegation** - An open root delegation is a root delegation that doesn't specify a delegate. This means that any account can redeem the delegation. For example, Alice delegates the ability to spend 100 of her USDC to anyone. You must create open root delegations carefully, to ensure that they are not misused. Use [`createOpenDelegation`](../../reference/delegation/index.md#createopendelegation) to create an open root delegation. - **Redelegation** - A delegate can redelegate permissions that have been granted to them, creating a chain of delegations across trusted parties. For example, Alice delegates the ability to spend 100 of her USDC to Bob. Bob redelegates the ability to spend 50 of Alice's 100 USDC to Carol. Use [`createDelegation`](../../reference/delegation/index.md#createdelegation) to create a redelegation. - **Open redelegation** - An open redelegation is a redelegation that doesn't specify a delegate. This means that any account can redeem the redelegation. For example, Alice delegates the ability to spend 100 of her USDC to Bob. Bob redelegates the ability to spend 50 of Alice's 100 USDC to anyone. As with open root delegations, you must create open redelegations carefully, to ensure that they are not misused. Use [`createOpenDelegation`](../../reference/delegation/index.md#createopendelegation) to create an open redelegation. ## Delegation Framework The Smart Accounts Kit includes the Delegation Framework, which is a [set of comprehensively audited smart contracts](https://github.com/MetaMask/delegation-framework) that collectively handle delegator account creation, the delegation lifecycle, and caveat enforcement. It consists of the following components: - **Delegator Core** - Delegator Core contains the logic for the ERC-4337 compliant delegator accounts. It defines the interface needed for the Delegation Manager to invoke executions on behalf of the accounts. - **Delegator account implementations** - Delegator accounts are smart accounts, and there are [multiple smart account implementations](../smart-accounts.md#smart-account-implementation-types), with differing signature schemes used to manage the underlying account. - **Delegation Manager** - The Delegation Manager validates delegations and triggers executions on behalf of the delegator, ensuring tasks are executed accurately and securely. When you redeem a delegation using [`redeemDelegations`](../../reference/delegation/index.md#redeemdelegations), the Delegation Manager performs the following steps. It processes a single step for all redemptions before proceeding to the next one: 1. Validates the input data by ensuring the lengths of `delegations`, `modes`, and `executions` match. 2. Decodes and validates the delegation, checking that the caller is the delegate and that there are no empty signatures. 3. Verifies delegation signatures, ensuring validity using ECDSA (for EOAs) or `isValidSignature` (for contracts). 4. Validates the delegation chain's authority and ensures delegations are not disabled. 5. Executes the `beforeHook` for each [caveat](caveat-enforcers.md) in the delegation, passing relevant data (`terms`, `arguments`, `mode`, `execution` `calldata`, and `delegationHash`) to the caveat enforcer. 6. Calls `executeFromExecutor` to perform the delegation's execution, either by the delegator or the caller for self-authorized executions. 7. Executes the `afterHook` for each caveat, similar to the `beforeHook`, passing required data to enforce post-execution conditions. 8. Emits `RedeemedDelegation` events for each delegation that was successfully redeemed. - **Caveat enforcers** - [Caveat enforcers](caveat-enforcers.md) manage rules and restrictions for delegations, providing fine-tuned control over delegated executions. ## Delegation flow This diagram shows how a delegation is created and redeemed with the Delegation Manager. The Delegation Manager is responsible for validating the signature of the delegation and the caveat enforcers. If everything is correct, it allows a delegate to execute an action on behalf of the delegator. Learn more about the caveat enforcer hooks in the [Caveat enforcers](caveat-enforcers.md) section. ```mermaid %%{ init: { 'sequence': { 'actorMargin': 30, 'width': 250 } } }%% sequenceDiagram participant Delegator participant Delegate participant Manager as Delegation Manager participant Enforcer as Caveat enforcer Delegator->>Delegator: Create delegation with caveat enforcers Delegator->>Delegator: Sign delegation Delegator->>Delegate: Send signed delegation Note right of Delegate: Hold delegation until redemption Delegate->>Manager: redeemDelegations() with delegation & execution details Manager->>Delegator: isValidSignature() Delegator-->>Manager: Confirm valid (or not) Manager->>Enforcer: beforeAllHook() Note right of Manager: Expect no error Manager->>Enforcer: beforeHook() Note right of Manager: Expect no error Manager->>Delegator: executeFromExecutor() with execution details Delegator->>Delegator: Perform execution Note right of Manager: Expect no error Manager->>Enforcer: afterHook() Note right of Manager: Expect no error Manager->>Enforcer: afterAllHook() Note right of Manager: Expect no error ``` ## Execution modes When redeeming a delegation using [`redeemDelegations`](../../reference/delegation/index.md#redeemdelegations), you must pass an execution mode for each delegation chain you pass to the method. The Smart Accounts Kit supports the following execution modes, based on [ERC-7579](https://erc7579.com/): | Execution mode | Number of delegation chains passed to `redeemDelegations` | Processing method | Does user operation continue execution if redemption reverts? | |--|--|--|--| | `SingleDefault` | One | Sequential | No | | `SingleTry` | One | Sequential | Yes | | `BatchDefault` | Multiple | Interleaved | No | | `BatchTry` | Multiple | Interleaved | Yes | ### Sequential processing In `Single` modes, processing is sequential: 1. For each delegation in the chain, all caveats' `before` hooks are called. 2. The single redeemed action is executed. 3. For each delegation in the chain, all caveats' `after` hooks are called. ### Interleaved processing In `Batch` modes, processing is interleaved: 1. For each chain in the batch, and each delegation in the chain, all caveats' `before` hooks are called. 2. Each redeemed action is executed. 3. For each chain in the batch, and each delegation in the chain, all caveats' `after` hooks are called. `Batch` mode allows for powerful use cases, but the Delegation Framework currently does not include any `Batch` compatible caveat enforcers. --- ## MetaMask Smart Accounts The Smart Accounts Kit enables you to create and manage *MetaMask Smart Accounts*. MetaMask Smart Accounts are [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) smart contract accounts that support programmable account behavior and advanced features such as multi-signature approvals, automated transaction batching, and custom security policies. Unlike traditional wallets, which rely on private keys for every transaction, MetaMask Smart Accounts use smart contracts to govern account logic. MetaMask Smart Accounts are referenced in the toolkit as `MetaMaskSmartAccount`. ## Account abstraction (ERC-4337) Account abstraction, specified by [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337), is a mechanism that enables users to manage smart contract accounts containing arbitrary verification logic. ERC-4337 enables smart contracts to be used as primary accounts in place of traditional private key-based accounts, or externally owned accounts (EOAs). ERC-4337 introduces the following concepts: - **User operation** - A package of instructions signed by a user, specifying executions for the smart account to perform. User operations are collected and submitted to the network by bundlers. - **Bundler** - A service that collects multiple user operations, packages them into a single transaction, and submits them to the network, optimizing gas costs and transaction efficiency. - **Entry point contract** - A contract that validates and processes bundled user operations, ensuring they adhere to the required rules and security checks. - **Paymasters** - Entities that handle the payment of gas fees on behalf of users, often integrated into smart accounts to facilitate gas abstraction. ## Smart account implementation types The toolkit supports three types of MetaMask Smart Accounts, each offering unique features and use cases. See [Create a smart account](../guides/smart-accounts/create-smart-account.md) to learn how to use these different account types. ### Hybrid smart account The Hybrid smart account is a flexible implementation that supports both an externally owned account (EOA) owner and any number of passkey (WebAuthn) signers. You can configure any of these signers, and use them to sign any data, including user operations, on behalf of the smart account. This type is referenced in the toolkit as `Implementation.Hybrid`. ### Multisig smart account The Multisig smart account is an implementation that supports multiple signers with a configurable threshold, allowing for enhanced security and flexibility in account management. A valid signature requires signatures from at least the number of signers specified by the threshold. This type is referenced in the toolkit as `Implementation.Multisig`. ### Stateless 7702 smart account The Stateless 7702 smart account implementation represents an externally owned account (EOA) upgraded to support smart account functionality as defined by [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702). This implementation enables EOAs to perform smart account operations, including the creation and management of delegations. This type is referenced in the toolkit as `Implementation.Stateless7702`. ## Smart account flow The MetaMask Smart Accounts flow is as follows: 1. **Account setup** - A user creates a smart account by deploying a smart contract, and initializing it with ownership and security settings. The user can customize the smart account in the following ways: - **Account logic** - They can configure custom logic for actions such as multi-signature approvals, spending limits, and automated transaction batching. - **Security and recovery** - They can configure advanced security features such as two-factor authentication and mechanisms for account recovery involving trusted parties. - **Gas management** - They can configure flexible gas payment options, including alternative tokens or third-party sponsorship. 2. **User operation creation** - For actions such as sending transactions, a user operation is created with necessary details and signed by the configured signers. 3. **Bundlers and mempool** - The signed user operation is submitted to a special mempool, where bundlers collect and package multiple user operations into a single transaction to save on gas costs. 4. **Validation and execution** - The bundled transaction goes to an entry point contract, which validates each user operation and executes them if they meet the smart contract's rules. ## Delegator accounts Delegator accounts are a type of MetaMask smart account that allows users to grant permission to other smart accounts or EOAs to perform specific executions on their behalf, under defined rules and restrictions. Learn more about [delegation](delegation/index.md). --- ## Install and set up the Smart Accounts Kit This page provides instructions to install and set up the Smart Accounts Kit, enabling you to create and interact with [MetaMask Smart Accounts](../concepts/smart-accounts.md) into your dapp. ## Prerequisites - Install [Node.js](https://nodejs.org/en/blog/release/v18.18.0) v18 or later. - Install [Yarn](https://yarnpkg.com/), [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm), or another package manager. - If you plan to use any smart contracts (for example, to [create a custom caveat enforcer](/tutorials/create-custom-caveat-enforcer)), install [Foundry](https://book.getfoundry.sh/getting-started/installation). ## Steps ### 1. Install the Smart Accounts Kit Install the [Smart Accounts Kit](https://www.npmjs.com/package/@metamask/smart-accounts-kit): ```bash npm2yarn npm install @metamask/smart-accounts-kit ``` ### 2. (Optional) Install the contracts If you plan to extend the Delegation Framework smart contracts (for example, to [create a custom caveat enforcer](/tutorials/create-custom-caveat-enforcer)), install the contract package using Foundry's command-line tool, Forge: ```bash forge install metamask/delegation-framework@v1.3.0 ``` Add `@metamask/delegation-framework/=lib/metamask/delegation-framework/` in your `remappings.txt` file. ### 3. Get started You're now ready to start using the Smart Accounts Kit. See the [MetaMask Smart Accounts quickstart](smart-account-quickstart/index.md) to walk through a simple example. --- ## EIP-7702 quickstart This quickstart demonstrates how to upgrade your externally owned account (EOA) to support MetaMask Smart Accounts functionality using an [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) transaction. This enables your EOA to leverage the benefits of account abstraction, such as batch transactions, gas sponsorship, and [delegation capabilities](../../concepts/delegation/index.md). ## Prerequisites - Install [Node.js](https://nodejs.org/en/blog/release/v18.18.0) v18 or later. - Install [Yarn](https://yarnpkg.com/), [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm), or another package manager. - [Install Viem](https://viem.sh/). ## Steps ### 1. Install the Smart Accounts Kit Install the [Smart Accounts Kit](https://www.npmjs.com/package/@metamask/smart-accounts-kit): ```bash npm2yarn npm install @metamask/smart-accounts-kit ``` ### 2. Set up a Public Client Set up a [Viem Public Client](https://viem.sh/docs/clients/public) using Viem's `createPublicClient` function. This client will let the EOA query the account state and interact with the blockchain network. ```typescript const publicClient = createPublicClient({ chain, transport: http(), }); ``` ### 3. Set up a Bundler Client Set up a [Viem Bundler Client](https://viem.sh/account-abstraction/clients/bundler) using Viem's `createBundlerClient` function. This lets you use the bundler service to estimate gas for user operations and submit transactions to the network. ```typescript const bundlerClient = createBundlerClient({ client: publicClient, transport: http("https://your-bundler-rpc.com"), }); ``` ### 4. Set up a Wallet Client Set up [Viem Wallet Client](https://viem.sh/docs/clients/wallet) using Viem's `createWalletClient` function. This lets you sign and submit EIP-7702 authorization. ```typescript export const account = privateKeyToAccount("0x..."); export const walletClient = createWalletClient({ account, chain, transport: http(), }); ``` ### 5. Authorize a 7702 delegation Create an authorization to map the contract code to an EOA, and sign it using Viem's [`signAuthorization`](https://viem.sh/docs/eip7702/signAuthorization) action. The `signAuthorization` action does not support JSON-RPC accounts. This example uses [`EIP7702StatelessDeleGator`](https://github.com/MetaMask/delegation-framework/blob/main/src/EIP7702/EIP7702StatelessDeleGator.sol) as the EIP-7702 delegator contract. It follows a stateless design, as it does not store signer data in the contract's state. This approach provides a lightweight and secure way to upgrade an EOA to a smart account. ```typescript Implementation, toMetaMaskSmartAccount, getSmartAccountsEnvironment, } from "@metamask/smart-accounts-kit"; const environment = getSmartAccountsEnvironment(sepolia.id); const contractAddress = environment.implementations.EIP7702StatelessDeleGatorImpl; const authorization = await walletClient.signAuthorization({ account, contractAddress, executor: "self", }); ``` ### 6. Submit the authorization Once you have signed an authorization, you can send an EIP-7702 transaction to set the EOA code. Since the authorization cannot be sent by itself, you can include it alongside a dummy transaction. ```ts const hash = await walletClient.sendTransaction({ authorizationList: [authorization], data: "0x", to: zeroAddress, }); ``` ### 7. Create a MetaMask smart account Create a smart account instance for the EOA and start leveraging the benefits of account abstraction. ```ts Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const addresses = await walletClient.getAddresses(); const address = addresses[0]; const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Stateless7702, address, signer: { walletClient }, }); ``` ### 8. Send a user operation Send a user operation through the upgraded EOA, using Viem's [`sendUserOperation`](https://viem.sh/account-abstraction/actions/bundler/sendUserOperation) method. ```ts // Appropriate fee per gas must be determined for the specific bundler being used. const maxFeePerGas = 1n; const maxPriorityFeePerGas = 1n; const userOperationHash = await bundlerClient.sendUserOperation({ account: smartAccount, calls: [ { to: "0x1234567890123456789012345678901234567890", value: parseEther("1") } ], maxFeePerGas, maxPriorityFeePerGas }); ``` ## Next steps - To grant specific permissions to other accounts from your smart account, [create a delegation](../../guides/delegation/execute-on-smart-accounts-behalf.md). - To quickly bootstrap a MetaMask Smart Accounts project, [use the CLI](../use-the-cli.md). - You can also [use MetaMask SDK to upgrade a MetaMask account to a smart account](/tutorials/upgrade-eoa-to-smart-account). --- ## MetaMask Smart Accounts quickstart You can get started quickly with [MetaMask Smart Accounts](../../concepts/smart-accounts.md) by creating your first smart account and sending a user operation. ## Prerequisites - Install [Node.js](https://nodejs.org/en/blog/release/v18.18.0) v18 or later. - Install [Yarn](https://yarnpkg.com/), [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm), or another package manager. ## Steps ### 1. Install the Smart Accounts Kit Install the [Smart Accounts Kit](https://www.npmjs.com/package/@metamask/smart-accounts-kit): ```bash npm2yarn npm install @metamask/smart-accounts-kit ``` ### 2. Set up a Public Client Set up a [Viem Public Client](https://viem.sh/docs/clients/public) using Viem's `createPublicClient` function. This client will let the smart account query the signer's account state and interact with the blockchain network. ```typescript const publicClient = createPublicClient({ chain, transport: http(), }); ``` ### 3. Set up a Bundler Client Set up a [Viem Bundler Client](https://viem.sh/account-abstraction/clients/bundler) using Viem's `createBundlerClient` function. This lets you use the bundler service to estimate gas for user operations and submit transactions to the network. ```typescript const bundlerClient = createBundlerClient({ client: publicClient, transport: http("https://your-bundler-rpc.com"), }); ``` ### 4. Create a MetaMask smart account [Create a MetaMask smart account](../../guides/smart-accounts/create-smart-account.md) to send the first user operation. 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 passkey (WebAuthn) signers: ```typescript const account = privateKeyToAccount("0x..."); const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", signer: { account }, }); ``` ### 5. Send a user operation Send a user operation using Viem's [`sendUserOperation`](https://viem.sh/account-abstraction/actions/bundler/sendUserOperation) method. See [Send a user operation](../../guides/smart-accounts/send-user-operation.md) to learn how to estimate fee per gas, and wait for the transaction receipt. The smart account will remain counterfactual until the first user operation. If the smart account is not deployed, it will be automatically deployed upon the sending first user operation. ```ts // Appropriate fee per gas must be determined for the specific bundler being used. const maxFeePerGas = 1n; const maxPriorityFeePerGas = 1n; const userOperationHash = await bundlerClient.sendUserOperation({ account: smartAccount, calls: [ { to: "0x1234567890123456789012345678901234567890", value: parseEther("1"), }, ], maxFeePerGas, maxPriorityFeePerGas, }); ``` ## Next steps - To grant specific permissions to other accounts from your smart account, [create a delegation](../../guides/delegation/execute-on-smart-accounts-behalf.md). - This quickstart example uses a Hybrid smart account. You can also [configure other smart account types](../../guides/smart-accounts/create-smart-account.md). - To upgrade an EOA to a smart account, see the [EIP-7702 quickstart](eip7702.md). - To quickly bootstrap a MetaMask Smart Accounts project, [use the CLI](../use-the-cli.md). --- ## Supported networks The following tables display the networks supported by each version of the Smart Accounts Kit. If you don't see the network you're looking for, you can request support by emailing hellogators@consensys.net. ## MetaMask Smart Accounts ### Mainnet networks | Network Name | v0.1.0 | | ------------------- | -------| | Arbitrum Nova | ✅ | | Arbitrum One | ✅ | | Base | ✅ | | Berachain | ✅ | | Binance Smart Chain | ✅ | | Ethereum | ✅ | | Gnosis Chain | ✅ | | Ink | ✅ | | Linea | ✅ | | Optimism | ✅ | | Polygon | ✅ | | Sonic | ✅ | | Unichain | ✅ | ### Testnet networks | Network Name | v0.1.0 | | --------------------------- | -------| | Arbitrum Sepolia | ✅ | | Base Sepolia | ✅ | | Berachain Bepolia | ✅ | | Binance Smart Chain | ✅ | | Citrea | ✅ | | Ethereum Sepolia | ✅ | | Gnosis Chiado | ✅ | | Hoodi | ✅ | | Ink Sepolia | ✅ | | Linea Sepolia | ✅ | | MegaEth | ✅ | | Monad | ✅ | | Optimism Sepolia | ✅ | | Polygon Amoy | ✅ | | Sei | ✅ | | Sonic | ✅ | | Unichain Sepolia | ✅ | ## Advanced Permissions (ERC-7715) ### Mainnet networks | Network Name | v0.1.0 | | ------------------- | -------| | Arbitrum Nova | ✅ | | Arbitrum One | ✅ | | Base | ✅ | | Berachain | ✅ | | Binance Smart Chain | ✅ | | Ethereum | ✅ | | Gnosis | ✅ | | Optimism | ✅ | | Polygon | ✅ | | Sonic | ✅ | | Unichain | ✅ | ### Testnet networks | Network Name | v0.1.0 | | ------------------- | -------| | Arbitrum Sepolia | ✅ | | Base Sepolia | ✅ | | Berachain Bepolia | ✅ | | Binance Smart Chain | ✅ | | Chiado | ✅ | | Citrea | ✅ | | Hoodi | ✅ | | MegaEth | ✅ | | Optimism Sepolia | ✅ | | Polygon Amoy | ✅ | | Sepolia | ✅ | | Sonic | ✅ | | Unichain Sepolia | ✅ | --- ## Use Advanced Permissions with Scaffold-ETH 2 Use the [Advanced Permissions (ERC-7715) extension](https://github.com/MetaMask/erc-7715-extension) for [Scaffold-ETH 2](https://docs.scaffoldeth.io/) to bootstrap a project in under two minutes. This extension helps you quickly generate the boilerplate code to request fine-grained permissions from a MetaMask user, and execute transactions on their behalf. ## Prerequisites - Install [Node.js](https://nodejs.org/en/blog/release/v20.18.3) v20.18.3 or later. - Install [Yarn](https://yarnpkg.com/) package manager. - Install [Git](https://git-scm.com/install/). - [Create a Pimlico API key](https://docs.pimlico.io/guides/create-api-key#create-api-key). ### 1. Install the extension Run the following command to install the Smart Accounts Kit extension: ```bash npx create-eth@latest -e metamask/erc-7715-extension your-project-name ``` ### 2. Set up enviroment variables Navigate into the project's `nextjs` package, and create a `.env.local` file. Once created, update the `NEXT_PUBLIC_PIMLICO_API_KEY` environment variable with your Pimlico API Key. ```bash cd your-project-name/packages/nextjs cp .env.example .env.local ``` ### 3. Start the frontend In the project's root directory start the development server. ```bash yarn start ``` ### 4. Complete the Advanced Permissions lifecycle Navigate to the **Advanced Permissions (ERC-7715)** page in your Scaffold-ETH frontend at http://localhost:3000/erc-7715-permissions, and follow the steps to request an advanced permission, and execute a transaction on the user's behalf. You can view the completed transaction on Etherscan. ## Next steps Learn more about [Advanced Permissions (ERC-7715)](../../concepts/advanced-permissions.md). --- ## Use MetaMask Smart Accounts with Scaffold-ETH 2 Use the [MetaMask Smart Accounts extension](https://github.com/metamask/gator-extension) for [Scaffold-ETH 2](https://docs.scaffoldeth.io/) to bootstrap a project in under two minutes. This extension helps you quickly generate the boilerplate code to create an embedded smart account, and complete the delegation lifecycle (create, sign, and redeem a delegation). ## Prerequisites - Install [Node.js](https://nodejs.org/en/blog/release/v20.18.3) v20.18.3 or later. - Install [Yarn](https://yarnpkg.com/) package manager. - Install [Git](https://git-scm.com/install/). - [Create a Pimlico API key](https://docs.pimlico.io/guides/create-api-key#create-api-key). ## Steps ### 1. Install the extension Run the following command to install the Smart Accounts Kit extension: ```bash npx create-eth@latest -e metamask/gator-extension your-project-name ``` ### 2. Set up enviroment variables Navigate into the project's `nextjs` package, and create a `.env.local` file. Once created, update the `NEXT_PUBLIC_PIMLICO_API_KEY` environment variable with your Pimlico API Key. ```bash cd your-project-name/packages/nextjs cp .env.example .env.local ``` ### 3. Start the frontend In the project's root directory start the development server. ```bash yarn start ``` ### 4. Complete the delegation lifecycle Navigate to the **MetaMask Smart Accounts & Delegation** page in your Scaffold-ETH frontend at http://localhost:3000/delegations, and follow the steps to deploy a delegator account, create a delegate wallet, create a delegation, and redeem a delegation. You can view the completed transaction on Etherscan. ## Next steps Learn more about [MetaMask Smart Accounts](../../concepts/smart-accounts.md) and [delegation](../../concepts/delegation/index.md). --- ## Use the Smart Accounts Kit CLI Use the `@metamask/create-gator-app` interactive CLI to bootstrap a project with the Smart Accounts Kit in under two minutes. The CLI automatically installs the required dependencies and sets up a project structure using a selected template, allowing you to focus on building your dapp. ## Run the CLI Run the following command to automatically install the `@metamask/create-gator-app` package: ```bash npx @metamask/create-gator-app@latest ``` Upon installation, you'll be asked the following prompts: ```bash ? What is your project named? (my-gator-app) ? Pick a framework: (Use arrow keys) ❯ nextjs vite-react ? Pick a template: (Use arrow keys) ❯ MetaMask Smart Accounts Starter MetaMask Smart Accounts & Delegation Starter Farcaster Mini App Delegation Starter Advanced Permissions (ERC-7715) Starter ? Pick a package manager: (Use arrow keys) ❯ npm yarn pnpm ``` Once you've answered the prompts with the required configuration and selected a template, the CLI will create the project using the specified name and settings. See the following section to learn more about available CLI configurations. ## Options The CLI provides the following options to display CLI details, and further customize the template configuration. | Option | Description | |---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| | `-v` or `--version` | Check the current version of the `@metamask/create-gator-app` CLI. | | `-h` or `--help` | Display the available options. | | `--skip-install` | Skip the installation of dependencies. | | `--add-web3auth` | Add [MetaMask Embedded Wallets (previously Web3Auth)](/embedded-wallets) as a signer for the delegator account.Supported templates:- MetaMask Smart Accounts Starter- MetaMask Smart Accounts & Delegation Starter | ## Examples ### MetaMask Embedded Wallets configuration To create a project that uses [MetaMask Embedded Wallets](/embedded-wallets) as the signer for your delegator account, use the `--add-web3auth` option with `@metamask/create-gator-app`: ```bash npx @metamask/create-gator-app --add-web3auth ``` You'll be prompted to provide additional Web3Auth configuration details: ```bash ? Which Web3Auth network do you want to use? (Use arrow keys) ❯ Sapphire Devnet Sapphire Mainnet ``` ## Supported templates | Template | Next.js | Vite React | |----------------------------------------------------|---------|------------| | MetaMask Smart Accounts Starter | ✅ | ✅ | | MetaMask Smart Accounts & Delegation Starter | ✅ | ✅ | | Farcaster Mini App Delegation Starter | ✅ | | | Advanced Permissions (ERC-7715) Starter | ✅ | | --- ## Perform executions on a MetaMask user's behalf [Advanced Permissions (ERC-7115)](../../concepts/advanced-permissions.md) are fine-grained permissions that your dapp can request from a MetaMask user to execute transactions on their behalf. For example, a user can grant your dapp permission to spend 10 USDC per day to buy ETH over the course of a month. Once the permission is granted, your dapp can use the allocated 10 USDC each day to purchase ETH directly from the MetaMask user's account. In this guide, you'll request an ERC-20 periodic transfer permission from a MetaMask user to transfer 1 USDC every day on their behalf. ## Prerequisites - [Install and set up the Smart Accounts Kit.](../../get-started/install.md) - [Install MetaMask Flask 13.5.0 or later.](/snaps/get-started/install-flask) ### 1. Set up a Wallet Client Set up a [Viem Wallet Client](https://viem.sh/docs/clients/wallet) using Viem's `createWalletClient` function. This client will help you interact with MetaMask Flask. Then, extend the Wallet Client functionality using `erc7715ProviderActions`. These actions enable you to request Advanced Permissions from the user. ```typescript const walletClient = createWalletClient({ transport: custom(window.ethereum), }).extend(erc7715ProviderActions()); ``` ### 2. Set up a Public Client Set up a [Viem Public Client](https://viem.sh/docs/clients/public) using Viem's `createPublicClient` function. This client will help you query the account state and interact with the blockchain network. ```typescript const publicClient = createPublicClient({ chain, transport: http(), }); ``` ### 3. Set up a session account Set up a session account which can either be a smart account or an externally owned account (EOA) to request Advanced Permissions. The requested permissions are granted to the session account, which is responsible for executing transactions on behalf of the user. ```typescript toMetaMaskSmartAccount, Implementation } from "@metamask/smart-accounts-kit"; const privateKey = "0x..."; const account = privateKeyToAccount(privateKey); const sessionAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", signer: { account }, }); ``` ```typescript const sessionAccount = privateKeyToAccount("0x..."); ``` ### 4. Check the EOA account code With MetaMask Flask 13.9.0 or later, Advanced Permissions support automatically upgrading a user’s account to a [MetaMask smart account](../../concepts/smart-accounts.md). On earlier versions, upgrade the user to a smart account before requesting Advanced Permissions. If the user has not yet been upgraded, you can handle the upgrade [programmatically](/wallet/how-to/send-transactions/send-batch-transactions/#about-atomic-batch-transactions) or ask the user to [switch to a smart account manually](https://support.metamask.io/configure/accounts/switch-to-or-revert-from-a-smart-account/#how-to-switch-to-a-metamask-smart-account). :::info Why is a Smart Account upgrade is required? MetaMask's Advanced Permissions (ERC-7115) implementation requires the user to be upgraded to a MetaMask Smart Account because, under the hood, you're requesting a signature for an [ERC-7710 delegation](../../concepts/delegation/index.md). ERC-7710 delegation is one of the core features supported only by MetaMask Smart Accounts. ::: ```typescript const addresses = await walletClient.requestAddresses(); const address = addresses[0]; // Get the EOA account code const code = await publicClient.getCode({ address, }); if (code) { // The address to which EOA has delegated. According to EIP-7702, 0xef0100 || address // represents the delegation. // // You need to remove the first 8 characters (0xef0100) to get the delegator address. const delegatorAddress = `0x${code.substring(8)}`; const statelessDelegatorAddress = getSmartAccountsEnvironment(chain.id) .implementations .EIP7702StatelessDeleGatorImpl; // If account is not upgraded to MetaMask smart account, you can // either upgrade programmatically or ask the user to switch to a smart account manually. const isAccountUpgraded = delegatorAddress.toLowerCase() === statelessDelegatorAddress.toLowerCase(); } ``` ### 5. Request Advanced Permissions Request Advanced Permissions from the user with the Wallet Client's `requestExecutionPermissions` action. In this example, you'll request an [ERC-20 periodic permission](use-permissions/erc20-token.md#erc-20-periodic-permission). See the [`requestExecutionPermissions`](../../reference/advanced-permissions/wallet-client.md#requestexecutionpermissions) API reference for more information. ```typescript // Since current time is in seconds, we need to convert milliseconds to seconds. const currentTime = Math.floor(Date.now() / 1000); // 1 week from now. const expiry = currentTime + 604800; // USDC address on Ethereum Sepolia. const tokenAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; const grantedPermissions = await walletClient.requestExecutionPermissions([{ chainId: chain.id, expiry, signer: { type: "account", data: { // The requested permissions will granted to the // session account. address: sessionAccount.address, }, }, permission: { type: "erc20-token-periodic", data: { tokenAddress, // 1 USDC in WEI format. Since USDC has 6 decimals, 10 * 10^6 periodAmount: parseUnits("10", 6), // 1 day in seconds periodDuration: 86400, justification?: "Permission to transfer 1 USDC every day", }, }, isAdjustmentAllowed: true, }]); ``` ### 6. Set up a Viem client Set up a Viem client depending on your session account type. For a smart account, set up a [Viem Bundler Client](https://viem.sh/account-abstraction/clients/bundler) using Viem's `createBundlerClient` function. This lets you use the bundler service to estimate gas for user operations and submit transactions to the network. For an EOA, set up a [Viem Wallet Client](https://viem.sh/docs/clients/wallet) using Viem's `createWalletClient` function. This lets you send transactions directly to the network. The toolkit provides public actions for both of the clients which can be used to redeem Advanced Permissions, and execute transactions on a user's behalf. ```typescript const bundlerClient = createBundlerClient({ client: publicClient, transport: http("https://your-bundler-rpc.com"), // Allows you to use the same Bundler Client as paymaster. paymaster: true }).extend(erc7710BundlerActions()); ``` ```typescript const sessionAccountWalletClient = createWalletClient({ account: sessionAccount, chain, transport: http(), }).extend(erc7710WalletActions()); ``` ### 7. Redeem Advanced Permissions The session account can now redeem the permissions. The redeem transaction is sent to the `DelegationManager` contract, which validates the delegation and executes actions on the user's behalf. To redeem the permissions, use the client action based on your session account type. A smart account uses the Bundler Client's `sendUserOperationWithDelegation` action, and an EOA uses the Wallet Client's `sendTransactionWithDelegation` action. See the [`sendUserOperationWithDelegation`](../../reference/advanced-permissions/bundler-client.md#senduseroperationwithdelegation) and [`sendTransactionWithDelegation`](../../reference/advanced-permissions/wallet-client.md#sendtransactionwithdelegation) API reference for more information. ```typescript // These properties must be extracted from the permission response. const permissionsContext = grantedPermissions[0].context; const delegationManager = grantedPermissions[0].signerMeta.delegationManager; // USDC address on Ethereum Sepolia. const tokenAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; // Calls without permissionsContext and delegationManager will be executed // as a normal user operation. const userOperationHash = await bundlerClient.sendUserOperationWithDelegation({ publicClient, account: sessionAccount, calls: [ { to: tokenAddress, data: calldata, permissionsContext, delegationManager, }, ], // Appropriate values must be used for fee-per-gas. maxFeePerGas: 1n, maxPriorityFeePerGas: 1n, }); ``` ```typescript // These properties must be extracted from the permission response. const permissionsContext = grantedPermissions[0].context; const delegationManager = grantedPermissions[0].signerMeta.delegationManager; // USDC address on Ethereum Sepolia. const tokenAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; const transactionHash = await sessionAccountWalletClient.sendTransactionWithDelegation({ to: tokenAddress, data: calldata, permissionsContext, delegationManager, }); ``` ```typescript export const calldata = encodeFunctionData({ abi: erc20Abi, args: [ sessionAccount.address, parseUnits("1", 6) ], functionName: 'transfer', }); ``` ## Next steps See how to configure different [ERC-20 token permissions](use-permissions/erc20-token.md) and [native token permissions](use-permissions/native-token.md). --- ## Use ERC-20 token permissions [Advanced Permissions (ERC-7715)](../../../concepts/advanced-permissions.md) supports ERC-20 token permission types that allow you to request fine-grained permissions for ERC-20 token transfers with time-based (periodic) or streaming conditions, depending on your use case. ## Prerequisites - [Install and set up the Smart Accounts Kit.](../../../get-started/install.md) - [Configure the Smart Accounts Kit.](../../configure-toolkit.md) - [Create a session account.](../execute-on-metamask-users-behalf.md#3-set-up-a-session-account) ## ERC-20 periodic permission This permission type ensures a per-period limit for ERC-20 token transfers. At the start of each new period, the allowance resets. For example, a user signs an ERC-7715 permission that lets a dapp spend up to 10 USDC on their behalf each day. The dapp can transfer a total of 10 USDC per day; the limit resets at the beginning of the next day. See the [ERC-20 periodic permission API reference](../../../reference/advanced-permissions/permissions.md#erc-20-periodic-permission) for more information. ```typescript // Since current time is in seconds, convert milliseconds to seconds. const currentTime = Math.floor(Date.now() / 1000); // 1 week from now. const expiry = currentTime + 604800; // USDC address on Ethereum Sepolia. const tokenAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; const grantedPermissions = await walletClient.requestExecutionPermissions([{ chainId: chain.id, expiry, signer: { type: "account", data: { // Session account created as a prerequisite. // // The requested permissions will granted to the // session account. address: sessionAccountAddress, }, }, permission: { type: "erc20-token-periodic", data: { tokenAddress, // 10 USDC in WEI format. Since USDC has 6 decimals, 10 * 10^6. periodAmount: parseUnits("10", 6), // 1 day in seconds. periodDuration: 86400, justification?: "Permission to transfer 1 USDC every day", }, }, isAdjustmentAllowed: true, }]); ``` ```typescript export const walletClient = createWalletClient({ transport: custom(window.ethereum), }).extend(erc7715ProviderActions()); ``` ## ERC-20 stream permission This permission type ensures a linear streaming transfer limit for ERC-20 tokens. Token transfers are blocked until the defined start timestamp. At the start, a specified initial amount is released, after which tokens accrue linearly at the configured rate, up to the maximum allowed amount. For example, a user signs an ERC-7715 permission that allows a dapp to spend 0.1 USDC per second, starting with an initial amount of 1 USDC, up to a maximum of 2 USDC. See the [ERC-20 stream permission API reference](../../../reference/advanced-permissions/permissions.md#erc-20-stream-permission) for more information. ```typescript // Since current time is in seconds, convert milliseconds to seconds. const currentTime = Math.floor(Date.now() / 1000); // 1 week from now. const expiry = currentTime + 604800; // USDC address on Ethereum Sepolia. const tokenAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; const grantedPermissions = await walletClient.requestExecutionPermissions([{ chainId: chain.id, expiry, signer: { type: "account", data: { // Session account created as a prerequisite. // // The requested permissions will granted to the // session account. address: sessionAccountAddress, }, }, permission: { type: "erc20-token-stream", data: { tokenAddress, // 0.1 USDC in WEI format. Since USDC has 6 decimals, 0.1 * 10^6. amountPerSecond: parseUnits("0.1", 6), // 1 USDC in WEI format. Since USDC has 6 decimals, 1 * 10^6. initialAmount: parseUnits("1", 6), // 2 USDC in WEI format. Since USDC has 6 decimals, 2 * 10^6. maxAmount: parseUnits("2", 6), startTime: currentTime, justification: "Permission to use 0.1 USDC per second", }, }, isAdjustmentAllowed: true, }]); ``` ```typescript export const walletClient = createWalletClient({ transport: custom(window.ethereum), }).extend(erc7715ProviderActions()); ``` --- ## Use native token permissions [Advanced Permissions (ERC-7115)](../../../concepts/advanced-permissions.md) supports native token permission types that allow you to request fine-grained permissions for native token transfers with time-based (periodic) or streaming conditions, depending on your use case. ## Prerequisites - [Install and set up the Smart Accounts Kit.](../../../get-started/install.md) - [Configure the Smart Accounts Kit.](../../configure-toolkit.md) - [Create a session account.](../execute-on-metamask-users-behalf.md#3-set-up-a-session-account) ## Native token periodic permission This permission type ensures a per-period limit for native token transfers. At the start of each new period, the allowance resets. For example, a user signs an ERC-7715 permission that lets a dapp spend up to 0.001 ETH on their behalf each day. The dapp can transfer a total of 0.001 USDC per day; the limit resets at the beginning of the next day. See the [native token periodic permission API reference](../../../reference/advanced-permissions/permissions.md#native-token-periodic-permission) for more information. ```typescript // Since current time is in seconds, convert milliseconds to seconds. const currentTime = Math.floor(Date.now() / 1000); // 1 week from now. const expiry = currentTime + 604800; const grantedPermissions = await walletClient.requestExecutionPermissions([{ chainId: chain.id, expiry, signer: { type: "account", data: { // Session account created as a prerequisite. // // The requested permissions will granted to the // session account. address: sessionAccountAddress, }, }, permission: { type: "native-token-periodic", data: { // 0.001 ETH in wei format. periodAmount: parseEther("0.001"), // 1 hour in seconds. periodDuration: 86400, startTime: currentTime, justification: "Permission to use 0.001 ETH every day", }, }, isAdjustmentAllowed: true, }]); ``` ```typescript export const walletClient = createWalletClient({ transport: custom(window.ethereum), }).extend(erc7715ProviderActions()); ``` ## Native token stream permission This permission type ensures a linear streaming transfer limit for native tokens. Token transfers are blocked until the defined start timestamp. At the start, a specified initial amount is released, after which tokens accrue linearly at the configured rate, up to the maximum allowed amount. For example, a user signs an ERC-7715 permission that allows a dapp to spend 0.0001 ETH per second, starting with an initial amount of 0.1 ETH, up to a maximum of 1 ETH. See the [native token stream permission API reference](../../../reference/advanced-permissions/permissions.md#native-token-stream-permission) for more information. ```typescript // Since current time is in seconds, convert milliseconds to seconds. const currentTime = Math.floor(Date.now() / 1000); // 1 week from now. const expiry = currentTime + 604800; const grantedPermissions = await walletClient.requestExecutionPermissions([{ chainId: chain.id, expiry, signer: { type: "account", data: { // Session account created as a prerequisite. // // The requested permissions will granted to the // session account. address: sessionAccountAddress, }, }, permission: { type: "native-token-stream", data: { // 0.0001 ETH in wei format. amountPerSecond: parseEther("0.0001"), // 0.1 ETH in wei format. initialAmount: parseEther("0.1"), // 1 ETH in wei format. maxAmount: parseEther("1"), startTime: currentTime, justification: "Permission to use 0.0001 ETH per second", }, }, isAdjustmentAllowed: true, }]); ``` ```typescript export const walletClient = createWalletClient({ transport: custom(window.ethereum), }).extend(erc7715ProviderActions()); ``` --- ## Configure the Smart Accounts Kit The Smart Accounts Kit is highly configurable, providing support for custom [bundlers and paymasters](#configure-the-bundler). You can also configure the [toolkit environment](#optional-configure-the-toolkit-environment) to interact with the [Delegation Framework](../concepts/delegation/index.md#delegation-framework). ## Prerequisites [Install and set up the Smart Accounts Kit.](../get-started/install.md) ## Configure the bundler The toolkit uses Viem's Account Abstraction API to configure custom bundlers and paymasters. This provides a robust and flexible foundation for creating and managing [MetaMask Smart Accounts](../concepts/smart-accounts.md). See Viem's [account abstraction documentation](https://viem.sh/account-abstraction) for more information on the API's features, methods, and best practices. To use the bundler and paymaster clients with the toolkit, create instances of these clients and configure them as follows: ```typescript createPaymasterClient, createBundlerClient, } from "viem/account-abstraction"; // Replace these URLs with your actual bundler and paymaster endpoints. const bundlerUrl = "https://your-bundler-url.com"; const paymasterUrl = "https://your-paymaster-url.com"; // The paymaster is optional. const paymasterClient = createPaymasterClient({ transport: http(paymasterUrl), }); const bundlerClient = createBundlerClient({ transport: http(bundlerUrl), paymaster: paymasterClient, chain, }); ``` Replace the bundler and paymaster URLs with your bundler and paymaster endpoints. For example, you can use endpoints from [Pimlico](https://docs.pimlico.io/references/bundler), [Infura](/services), or [ZeroDev](https://docs.zerodev.app/meta-infra/intro). :::note Providing a paymaster is optional when configuring your bundler client. However, if you choose not to use a paymaster, the smart contract account must have enough funds to pay gas fees. ::: ## (Optional) Configure the toolkit environment The toolkit environment (`SmartAccountsEnvironment`) defines the contract addresses necessary for interacting with the [Delegation Framework](../concepts/delegation/index.md#delegation-framework) on a specific network. It serves several key purposes: - It provides a centralized configuration for all the contract addresses required by the Delegation Framework. - It enables easy switching between different networks (for example, Mainnet and testnet) or custom deployments. - It ensures consistency across different parts of the application that interact with the Delegation Framework. ### Resolve the environment When you create a [MetaMask smart account](../concepts/smart-accounts.md), the toolkit automatically resolves the environment based on the version it requires and the chain configured. If no environment is found for the specified chain, it throws an error. ```typescript const environment: SmartAccountsEnvironment = delegatorSmartAccount.environment; ``` ```typescript Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const publicClient = createPublicClient({ chain, transport: http(), }); const delegatorAccount = privateKeyToAccount("0x..."); const delegatorSmartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [delegatorAccount.address, [], [], []], deploySalt: "0x", signer: { account: delegatorAccount }, }); export delegatorSmartAccount; ``` :::note See the changelog of the toolkit version you are using (in the left sidebar) for supported chains. ::: Alternatively, you can use the [`getSmartAccountsEnvironment`](../reference/delegation/index.md#getsmartaccountsenvironment) function to resolve the environment. This function is especially useful if your delegator is not a smart account when creating a [redelegation](../concepts/delegation/index.md#delegation-types). ```typescript getSmartAccountsEnvironment, SmartAccountsEnvironment, } from "@metamask/smart-accounts-kit"; // Resolves the SmartAccountsEnvironment for Sepolia const environment: SmartAccountsEnvironment = getSmartAccountsEnvironment(11155111); ``` ### Deploy a custom environment You can deploy the contracts using any method, but the toolkit provides a convenient [`deploySmartAccountsEnvironment`](../reference/delegation/index.md#deploysmartaccountsenvironment) function. This function simplifies deploying the Delegation Framework contracts to your desired EVM chain. This function requires a Viem [Public Client](https://viem.sh/docs/clients/public), [Wallet Client](https://viem.sh/docs/clients/wallet), and [Chain](https://viem.sh/docs/glossary/types#chain) to deploy the contracts and resolve the `SmartAccountsEnvironment`. Your wallet must have a sufficient native token balance to deploy the contracts. ```typescript const environment = await deploySmartAccountsEnvironment( walletClient, publicClient, chain ); ``` ```typescript // Your deployer wallet private key. const privateKey = "0x123.."; const account = privateKeyToAccount(privateKey); export const walletClient = createWalletClient({ account, chain, transport: http() }); export const publicClient = createPublicClient({ transport: http(), chain, }); ``` You can also override specific contracts when calling `deploySmartAccountsEnvironment`. For example, if you've already deployed the `EntryPoint` contract on the target chain, you can pass the contract address to the function. ```typescript // The config.ts is the same as in the previous example. const environment = await deploySmartAccountsEnvironment( walletClient, publicClient, chain, // add-start + { + EntryPoint: "0x0000000071727De22E5E9d8BAf0edAc6f37da032" + } // add-end ); ``` Once the contracts are deployed, you can use them to override the environment. ### Override the environment To override the environment, the toolkit provides an [`overrideDeployedEnvironment`](../reference/delegation/index.md#overridedeployedenvironment) function to resolve `SmartAccountsEnvironment` with specified contracts for the given chain and contract version. ```typescript // The config.ts is the same as in the previous example. overrideDeployedEnvironment, deploySmartAccountsEnvironment } from "@metamask/smart-accounts-kit"; const environment: SmartAccountsEnvironment = await deploySmartAccountsEnvironment( walletClient, publicClient, chain ); overrideDeployedEnvironment( chain.id, "1.3.0", environment, ); ``` If you've already deployed the contracts using a different method, you can create a `SmartAccountsEnvironment` instance with the required contract addresses, and pass it to the function. ```typescript // remove-start - import { walletClient, publicClient } from "./config.ts"; - import { sepolia as chain } from "viem/chains"; // remove-end overrideDeployedEnvironment, // remove-next-line - deploySmartAccountsEnvironment } from "@metamask/smart-accounts-kit"; // remove-start - const environment: SmartAccountsEnvironment = await deploySmartAccountsEnvironment( - walletClient, - publicClient, - chain - ); // remove-end // add-start + const environment: SmartAccountsEnvironment = { + SimpleFactory: "0x124..", + // ... + implementations: { + // ... + }, + }; // add-end overrideDeployedEnvironment( chain.id, "1.3.0", environment ); ``` :::note Make sure to specify the Delegation Framework version required by the toolkit. See the changelog of the toolkit version you are using (in the left sidebar) for its required Framework version. ::: --- ## Check the delegation state When using [spending limit delegation scopes](use-delegation-scopes/spending-limit.md) or relevant [caveat enforcers](../../reference/delegation/caveats.md), you might need to check the remaining transferrable amount in a delegation. For example, if a delegation allows a user to spend 10 USDC per week and they have already spent 10 - n USDC in the current period, you can determine how much of the allowance is still available for transfer. Use the `CaveatEnforcerClient` to check the available balances for specific scopes or caveats. ## Prerequisites - [Install and set up the Smart Accounts Kit.](../../get-started/install.md) - [Create a delegator account.](execute-on-smart-accounts-behalf.md#3-create-a-delegator-account) - [Create a delegate account.](execute-on-smart-accounts-behalf.md#4-create-a-delegate-account) - [Create a delegation with an ERC-20 periodic scope.](use-delegation-scopes/spending-limit.md#erc-20-periodic-scope) ## Create a `CaveatEnforcerClient` To check the delegation state, create a [`CaveatEnforcerClient`](../../reference/delegation/caveat-enforcer-client.md). This client allows you to interact with the caveat enforcers of the delegation, and read the required state. ```typescript const caveatEnforcerClient = createCaveatEnforcerClient({ environment, client, }) ``` ```typescript export const environment = getSmartAccountsEnvironment(chain.id) export const publicClient = createPublicClient({ chain, transport: http(), }) ``` ## Read the caveat enforcer state This example uses the [`getErc20PeriodTransferEnforcerAvailableAmount`](../../reference/delegation/caveat-enforcer-client.md#geterc20periodtransferenforceravailableamount) method to read the state and retrieve the remaining amount for the current transfer period. ```typescript // Returns the available amount for current period. const { availableAmount } = await caveatEnforcerClient.getErc20PeriodTransferEnforcerAvailableAmount({ delegation, }) ``` ```typescript // startDate should be in seconds. const startDate = Math.floor(Date.now() / 1000); export const delegation = createDelegation({ scope: { type: 'erc20PeriodTransfer', tokenAddress: '0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da', periodAmount: parseUnits('10', 6), periodDuration: 86400, startDate, }, to: delegateAccount, from: delegatorAccount, environment: delegatorAccount.environment, }) ``` ## Next steps See the [Caveat Enforcer Client reference](../../reference/delegation/caveat-enforcer-client.md) for the full list of available methods. --- ## Disable a delegation Delegations are created off-chain and can be stored anywhere, but you can disable a delegation on-chain using the toolkit. When a delegation is disabled, any attempt to redeem it will revert, effectively revoking the permissions that were previously granted. For example, if Alice has given permission to Bob to spend 10 USDC on her behalf, and after a week she wants to revoke that permission, Alice can disable the delegation she created for Bob. If Bob tries to redeem the disabled delegation, the transaction will revert, preventing him from spending Alice's USDC. ## Prerequisites - [Install and set up the Smart Accounts Kit.](../../get-started/install.md) - [Create a delegator account.](execute-on-smart-accounts-behalf.md#3-create-a-delegator-account) - [Create a delegate account.](execute-on-smart-accounts-behalf.md#4-create-a-delegate-account) ## Disable a delegation To disable a delegation, you can use the [`disableDelegation`](../../reference/delegation/index.md#disabledelegation) utility function from the toolkit to generate calldata. Once the calldata is prepared, you can send it to the Delegation Manager to disable the delegation. ```typescript const disableDelegationData = DelegationManager.encode.disableDelegation({ delegation, }); // Appropriate fee per gas must be determined for the specific bundler being used. const maxFeePerGas = 1n; const maxPriorityFeePerGas = 1n; const userOperationHash = await bundlerClient.sendUserOperation({ account: delegatorAccount, calls: [ { to: environment.DelegationManager, data: disableDelegationData } ], maxFeePerGas, maxPriorityFeePerGas }); ``` ```typescript export const environment = getSmartAccountsEnvironment(chain.id) const currentTime = Math.floor(Date.now() / 1000) export const delegation = createDelegation({ scope: { type: 'nativeTokenPeriodTransfer', periodAmount: parseEther('0.01'), periodDuration: 86400, startDate: currentTime, }, to: delegateAccount, from: delegatorAccount, environment: delegatorAccount.environment, }) const publicClient = createPublicClient({ chain, transport: http() }) export const bundlerClient = createBundlerClient({ client: publicClient, transport: http('https://api.pimlico.io/v2/11155111/rpc?apikey=') }); ``` --- ## Perform executions on a smart account's behalf [Delegation](../../concepts/delegation/index.md) is the ability for a [MetaMask smart account](../../concepts/smart-accounts.md) 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.](../../get-started/install.md) ## Steps ### 1. Create a Public Client Create a [Viem Public Client](https://viem.sh/docs/clients/public) 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. ```typescript const publicClient = createPublicClient({ chain, transport: http(), }) ``` ### 2. Create a Bundler Client Create a [Viem Bundler Client](https://viem.sh/account-abstraction/clients/bundler) using Viem's `createBundlerClient` function. You can use the bundler service to estimate gas for user operations and submit transactions to the network. ```typescript 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`](../../reference/smart-account.md#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 signer](../smart-accounts/create-smart-account.md#create-a-hybrid-smart-account-with-an-account-signer): ```typescript 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](../smart-accounts/create-smart-account.md). ::: ### 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): ```typescript const delegateAccount = privateKeyToAccount("0x...") const delegateSmartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, // Hybrid smart account deployParams: [delegateAccount.address, [], [], []], deploySalt: "0x", signer: { account: delegateAccount }, }) ``` ```typescript const delegateAccount = privateKeyToAccount("0x..."); export const delegateWalletClient = createWalletClient({ account: delegateAccount, chain, transport: http(), }) ``` ### 5. Create a delegation Create a [root delegation](../../concepts/delegation/index.md#delegation-types) 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`](../../reference/delegation/index.md#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`](use-delegation-scopes/spending-limit.md#erc-20-transfer-scope) 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. ::: ```typescript // 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: "erc20TransferAmount", tokenAddress, // 10 USDC maxAmount: parseUnits("10", 6), }, }) ``` ### 6. Sign the delegation Sign the delegation with Alice's account, using the [`signDelegation`](../../reference/smart-account.md#signdelegation) method from `MetaMaskSmartAccount`. Alternatively, you can use the toolkit's [`signDelegation`](../../reference/delegation/index.md#signdelegation) utility method. Bob will later use the signed delegation to perform actions on Alice's behalf. ```typescript 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`](../../reference/delegation/index.md#redeemdelegations) method from `DelegationManager`. Since Bob is redeeming a single delegation chain, use the [`SingleDefault`](../../concepts/delegation/index.md#execution-modes) 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. ```typescript 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, }) ``` ```typescript 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, }) ``` ```typescript // 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](use-delegation-scopes/index.md) to define the initial authority of a delegation. - See [how to further refine the authority of a delegation](use-delegation-scopes/constrain-scope.md) using caveat enforcers. - See [how to disable a delegation](disable-delegation.md) to revoke permissions. --- ## Constrain a delegation scope [Delegation scopes](index.md) define the delegation's initial authority and help prevent delegation misuse. You can further constrain these scopes and limit the delegation's authority by applying [caveat enforcers](../../../concepts/delegation/caveat-enforcers.md). ## Prerequisites [Configure a delegation scope.](index.md) ## Apply a caveat enforcer For example, Alice creates a delegation with an [ERC-20 transfer scope](spending-limit.md#erc-20-transfer-scope) that allows Bob to spend up to 10 USDC. If Alice wants to further restrict the scope to limit Bob's delegation to be valid for only seven days, she can apply the [`timestamp`](../../../reference/delegation/caveats.md#timestamp) caveat enforcer. The following example creates a delegation using [`createDelegation`](../../../reference/delegation/index.md#createdelegation), applies the ERC-20 transfer scope with a spending limit of 10 USDC, and applies the `timestamp` caveat enforcer to restrict the delegation's validity to a seven-day period: ```typescript // Convert milliseconds to seconds. const currentTime = Math.floor(Date.now() / 1000); // Seven days after current time. const beforeThreshold = currentTime + 604800; const caveats = [{ type: "timestamp", afterThreshold: currentTime, beforeThreshold, }]; const delegation = createDelegation({ scope: { type: "erc20TransferAmount", tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", maxAmount: 10000n, }, // Apply caveats to the delegation. caveats, to: delegateAccount, from: delegatorAccount, environment: delegatorAccount.environment, }); ``` ## Next steps - See the [caveats reference](../../../reference/delegation/caveats.md) for the full list of caveat types and their parameters. - For more specific or custom control, you can also [create custom caveat enforcers](/tutorials/create-custom-caveat-enforcer) and apply them to delegations. --- ## Use the function call scope The function call scope defines the specific methods, contract addresses, and calldata that are allowed for the delegation. For example, Alice delegates to Bob the ability to call the `approve` function on the USDC contract, with the approval amount set to `0`. ## Prerequisites - [Install and set up the Smart Accounts Kit.](../../../get-started/install.md) - [Configure the Smart Accounts Kit.](../../configure-toolkit.md) - [Create a delegator account.](../execute-on-smart-accounts-behalf.md#3-create-a-delegator-account) - [Create a delegate account.](../execute-on-smart-accounts-behalf.md#4-create-a-delegate-account) ## Function call scope This scope requires `targets`, which specifies the permitted contract addresses, and `selectors`, which specifies the allowed methods. Internally, this scope uses the [`allowedTargets`](../../../reference/delegation/caveats.md#allowedtargets) and [`allowedMethods`](../../../reference/delegation/caveats.md#allowedmethods) caveat enforcers, and optionally uses the [`allowedCalldata`](../../../reference/delegation/caveats.md#allowedcalldata) or [`exactCalldata`](../../../reference/delegation/caveats.md#exactcalldata) caveat enforcers when those parameters are specified. See the [function call scope reference](../../../reference/delegation/delegation-scopes.md#function-call-scope) for more details. The following example sets the delegation scope to allow the delegate to call the `approve` function on the USDC token contract: ```typescript // USDC address on Sepolia. const USDC_ADDRESS = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" const delegation = createDelegation({ scope: { type: "functionCall", targets: [USDC_ADDRESS], selectors: ["approve(address, uint256)"], }, to: delegateAccount, from: delegatorAccount, environment: delegatorAccount.environment, }); ``` ### Define allowed calldata You can further restrict the scope by defining the `allowedCalldata`. For example, you can set `allowedCalldata` so the delegate is only permitted to call the `approve` function on the USDC token contract with an allowance value of `0`. This effectively limits the delegate to revoking ERC-20 approvals. :::important Usage The `allowedCalldata` doesn't support multiple selectors. Each entry in the list represents a portion of calldata corresponding to the same function signature. You can include or exclude specific parameters to precisely define what parts of the calldata are valid. ::: ```typescript // USDC address on Sepolia. const USDC_ADDRESS = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; const delegation = createDelegation({ scope: { type: "functionCall", targets: [USDC_ADDRESS], selectors: ["approve(address, uint256)"], allowedCalldata: [ { // Limits the allowance amount to be 0. value: encodeAbiParameters( [{ name: 'amount', type: 'uint256' }], [0n], ), // The first 4 bytes are for selector, and next 32 bytes // are for spender address. startIndex: 36, }, ] }, to: delegateAccount, from: delegatorAccount, environment: delegatorAccount.environment, }); ``` ### Define exact calldata You can define the `exactCalldata` instead of the `allowedCalldata`. For example, you can set `exactCalldata` so the delegate is permitted to call only the `approve` function on the USDC token contract, with a specific spender address and an allowance value of 0. This effectively limits the delegate to revoking ERC-20 approvals for a specific spender. ```typescript // USDC address on Sepolia. const USDC_ADDRESS = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; const delegation = createDelegation({ scope: { type: "functionCall", targets: [USDC_ADDRESS], selectors: ["approve(address, uint256)"], exactCalldata: { calldata: encodeFunctionData({ abi: erc20Abi, args: ["0x0227628f3F023bb0B980b67D528571c95c6DaC1c", 0n], functionName: 'approve', }) } }, to: delegateAccount, from: delegatorAccount, environment: delegatorAccount.environment, }); ``` ## Next steps See [how to further constrain the authority of a delegation](constrain-scope.md) using caveat enforcers. --- ## Use delegation scopes When [creating a delegation](../execute-on-smart-accounts-behalf.md), you can configure a scope to define the delegation's initial authority and help prevent delegation misuse. You can further constrain this initial authority by [adding caveats to a delegation](constrain-scope.md). The Smart Accounts Kit currently supports three categories of scopes: | Scope type | Description | |------------|-------------| | [Spending limit scopes](spending-limit.md) | Restricts the spending of native, ERC-20, and ERC-721 tokens based on defined conditions. | | [Function call scope](function-call.md) | Restricts the delegation to specific contract methods, contract addresses, or calldata. | | [Ownership transfer scope](ownership-transfer.md) | Restricts the delegation to only allow ownership transfers, specifically the `transferOwnership` function for a specified contract. | --- ## Use the ownership transfer scope The ownership transfer scope restricts a delegation to ownership transfer calls only. For example, Alice has deployed a smart contract, and she delegates to Bob the ability to transfer ownership of that contract. ## Prerequisites - [Install and set up the Smart Accounts Kit.](../../../get-started/install.md) - [Configure the Smart Accounts Kit.](../../configure-toolkit.md) - [Create a delegator account.](../execute-on-smart-accounts-behalf.md#3-create-a-delegator-account) - [Create a delegate account.](../execute-on-smart-accounts-behalf.md#4-create-a-delegate-account) ## Ownership transfer scope This scope requires a `contractAddress`, which represents the address of the deployed contract. Internally, this scope uses the [`ownershipTransfer`](../../../reference/delegation/caveats.md#ownershiptransfer) caveat enforcer. See the [ownership transfer scope reference](../../../reference/delegation/delegation-scopes.md#ownership-transfer-scope) for more details. ```typescript const contractAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" const delegation = createDelegation({ scope: { type: "ownershipTransfer", contractAddress, }, to: delegateAccount, from: delegatorAccount, environment: delegatorAccount.environment, }); ``` ## Next steps See [how to further constrain the authority of a delegation](constrain-scope.md) using caveat enforcers. --- ## Use spending limit scopes Spending limit scopes define how much a delegate can spend in native, ERC-20, or ERC-721 tokens. You can set transfer limits with or without time-based (periodic) or streaming conditions, depending on your use case. ## Prerequisites - [Install and set up the Smart Accounts Kit.](../../../get-started/install.md) - [Configure the Smart Accounts Kit.](../../configure-toolkit.md) - [Create a delegator account.](../execute-on-smart-accounts-behalf.md#3-create-a-delegator-account) - [Create a delegate account.](../execute-on-smart-accounts-behalf.md#4-create-a-delegate-account) ## ERC-20 periodic scope This scope ensures a per-period limit for ERC-20 token transfers. You set the amount, period, and start data. At the start of each new period, the allowance resets. For example, Alice creates a delegation that lets Bob spend up to 10 USDC on her behalf each day. Bob can transfer a total of 10 USDC per day; the limit resets at the beginning of the next day. When this scope is applied, the toolkit automatically disallows native token transfers (sets the native token transfer limit to `0`). Internally, this scope uses the [`erc20PeriodTransfer`](../../../reference/delegation/caveats.md#erc20periodtransfer) and [`valueLte`](../../../reference/delegation/caveats.md#valuelte) caveat enforcers. See the [ERC-20 periodic scope reference](../../../reference/delegation/delegation-scopes.md#erc-20-periodic-scope) for more details. ```typescript // startDate should be in seconds. const startDate = Math.floor(Date.now() / 1000); const delegation = createDelegation({ scope: { type: "erc20PeriodTransfer", tokenAddress: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", // USDC has 6 decimal places. periodAmount: parseUnits("10", 6), periodDuration: 86400, startDate, }, to: delegateAccount, from: delegatorAccount, environment: delegatorAccount.environment, }); ``` ## ERC-20 streaming scope This scopes ensures a linear streaming transfer limit for ERC-20 tokens. Token transfers are blocked until the defined start timestamp. At the start, a specified initial amount is released, after which tokens accrue linearly at the configured rate, up to the maximum allowed amount. For example, Alice creates a delegation that allows Bob to spend 0.1 USDC per second, starting with an initial amount of 10 USDC, up to a maximum of 100 USDC. When this scope is applied, the toolkit automatically disallows native token transfers (sets the native token transfer limit to `0`). Internally, this scope uses the [`erc20Streaming`](../../../reference/delegation/caveats.md#erc20streaming) and [`valueLte`](../../../reference/delegation/caveats.md#valuelte) caveat enforcers. See the [ERC-20 streaming scope reference](../../../reference/delegation/delegation-scopes.md#erc-20-streaming-scope) for more details. ```typescript // startTime should be in seconds. const startTime = Math.floor(Date.now() / 1000); const delegation = createDelegation({ scope: { type: "erc20Streaming", tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", // USDC has 6 decimal places. amountPerSecond: parseUnits("0.1", 6), initialAmount: parseUnits("10", 6), maxAmount: parseUnits("100", 6), startTime, }, to: delegateAccount, from: delegatorAccount, environment: delegatorAccount.environment, }); ``` ## ERC-20 transfer scope This scope ensures that ERC-20 token transfers are limited to a predefined maximum amount. This scope is useful for setting simple, fixed transfer limits without any time-based or streaming conditions. For example, Alice creates a delegation that allows Bob to spend up to 10 USDC without any conditions. Bob may use the 10 USDC in a single transaction or make multiple transactions, as long as the total does not exceed 10 USDC. When this scope is applied, the toolkit automatically disallows native token transfers (sets the native token transfer limit to `0`). Internally, this scope uses the [`erc20TransferAmount`](../../../reference/delegation/caveats.md#erc20transferamount) and [`valueLte`](../../../reference/delegation/caveats.md#valuelte) caveat enforcers. See the [ERC-20 transfer scope reference](../../../reference/delegation/delegation-scopes.md#erc-20-transfer-scope) for more details. ```typescript const delegation = createDelegation({ scope: { type: "erc20TransferAmount", tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", // USDC has 6 decimal places. maxAmount: parseUnits("10", 6), }, to: delegateAccount, from: delegatorAccount, environment: delegatorAccount.environment, }); ``` ## ERC-721 scope This scope limits the delegation to ERC-721 token transfers only. For example, Alice creates a delegation that allows Bob to transfer an NFT she owns on her behalf. Internally, this scope uses the [`erc721Transfer`](../../../reference/delegation/caveats.md#erc721transfer) caveat enforcer. See the [ERC-721 scope reference](../../../reference/delegation/delegation-scopes.md#erc-721-scope) for more details. ```typescript const delegation = createDelegation({ scope: { type: "erc721Transfer", tokenAddress: "0x3fF528De37cd95b67845C1c55303e7685c72F319", tokenId: 1n, }, to: delegateAccount, from: delegatorAccount, environment: delegatorAccount.environment, }); ``` ## Native token periodic scope This scope ensures a per-period limit for native token transfers. You set the amount, period, and start date. At the start of each new period, the allowance resets. For example, Alice creates a delegation that lets Bob spend up to 0.01 ETH on her behalf each day. Bob can transfer a total of 0.01 ETH per day; the limit resets at the beginning of the next day. When this scope is applied, the toolkit disallows ERC-20 and ERC-721 token transfers by default (sets `exactCalldata` to `0x`). You can optionally configure `exactCalldata` to restrict transactions to a specific operation, or configure `allowedCalldata` to allow transactions that match certain patterns or ranges. Internally, this scope uses the [`nativeTokenPeriodTransfer`](../../../reference/delegation/caveats.md#nativetokenperiodtransfer) caveat enforcer, and optionally uses the [`allowedCalldata`](../../../reference/delegation/caveats.md#allowedcalldata) or [`exactCalldata`](../../../reference/delegation/caveats.md#exactcalldata) caveat enforcers when those parameters are specified. See the [native token periodic scope reference](../../../reference/delegation/delegation-scopes.md#native-token-periodic-scope) for more details. ```typescript // startDate should be in seconds. const startDate = Math.floor(Date.now() / 1000); const delegation = createDelegation({ scope: { type: "nativeTokenPeriodTransfer", periodAmount: parseEther("0.01"), periodDuration: 86400, startDate, }, to: delegateAccount, from: delegatorAccount, environment: delegatorAccount.environment, }); ``` ## Native token streaming scope This scopes ensures a linear streaming transfer limit for native tokens. Token transfers are blocked until the defined start timestamp. At the start, a specified initial amount is released, after which tokens accrue linearly at the configured rate, up to the maximum allowed amount. For example, Alice creates delegation that allows Bob to spend 0.001 ETH per second, starting with an initial amount of 0.01 ETH, up to a maximum of 0.1 ETH. When this scope is applied, the toolkit disallows ERC-20 and ERC-721 token transfers by default (sets `exactCalldata` to `0x`). You can optionally configure `exactCalldata` to restrict transactions to a specific operation, or configure `allowedCalldata` to allow transactions that match certain patterns or ranges. Internally, this scope uses the [`nativeTokenStreaming`](../../../reference/delegation/caveats.md#nativetokenstreaming) caveat enforcer, and optionally uses the [`allowedCalldata`](../../../reference/delegation/caveats.md#allowedcalldata) or [`exactCalldata`](../../../reference/delegation/caveats.md#exactcalldata) caveat enforcers when those parameters are specified. See the [native token streaming scope reference](../../../reference/delegation/delegation-scopes.md#native-token-streaming-scope) for more details. ```typescript // startTime should be in seconds. const startTime = Math.floor(Date.now() / 1000); const delegation = createDelegation({ scope: { type: "nativeTokenStreaming", amountPerSecond: parseEther("0.001"), initialAmount: parseEther("0.01"), maxAmount: parseEther("0.1"), startTime, }, to: delegateAccount, from: delegatorAccount, environment: delegatorAccount.environment, }); ``` ## Native token transfer scope This scope ensures that native token transfers are limited to a predefined maximum amount. This scope is useful for setting simple, fixed transfer limits without any time-based or streaming conditions. For example, Alice creates a delegation that allows Bob to spend up to 0.1 ETH without any conditions. Bob may use the 0.1 ETH in a single transaction or make multiple transactions, as long as the total does not exceed 0.1 ETH. When this scope is applied, the toolkit disallows ERC-20 and ERC-721 token transfers by default (sets `exactCalldata` to `0x`). You can optionally configure `exactCalldata` to restrict transactions to a specific operation, or configure `allowedCalldata` to allow transactions that match certain patterns or ranges. Internally, this scope uses the [`nativeTokenTransferAmount`](../../../reference/delegation/caveats.md#nativetokentransferamount) caveat enforcer, and optionally uses the [`allowedCalldata`](../../../reference/delegation/caveats.md#allowedcalldata) or [`exactCalldata`](../../../reference/delegation/caveats.md#exactcalldata) caveat enforcers when those parameters are specified. See the [native token transfer scope reference](../../../reference/delegation/delegation-scopes.md#native-token-transfer-scope) for more details. ```typescript const delegation = createDelegation({ scope: { type: "nativeTokenTransferAmount", maxAmount: parseEther("0.001"), }, to: delegateAccount, from: delegatorAccount, environment: delegatorAccount.environment, }); ``` ## Next steps See [how to further constrain the authority of a delegation](constrain-scope.md) using caveat enforcers. --- ## Create a smart account You can enable users to create a [MetaMask smart account](../../concepts/smart-accounts.md) directly in your dapp. This page provides examples of using [`toMetaMaskSmartAccount`](../../reference/smart-account.md#tometamasksmartaccount) with Viem Core SDK to create different types of smart accounts with different signature schemes. An account's supported *signatories* can sign data on behalf of the smart account. ## Prerequisites [Install and set up the Smart Accounts Kit.](../../get-started/install.md) ## Create a Hybrid smart account A Hybrid smart account supports both an externally owned account (EOA) owner and any number of passkey (WebAuthn) signers. You can create a Hybrid smart account with the following types of signers. ### Create a Hybrid smart account with an Account signer Use [`toMetaMaskSmartAccount`](../../reference/smart-account.md#tometamasksmartaccount), and Viem's [`privateKeyToAccount` and `generatePrivateKey`](https://viem.sh/docs/accounts/local/privateKeyToAccount), to create a Hybrid smart account with a signer from a randomly generated private key: ```typescript Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", signer: { account }, }); ``` ```typescript const transport = http(); export const publicClient = createPublicClient({ transport, chain, }); ``` ```typescript const privateKey = generatePrivateKey(); export const account = privateKeyToAccount(privateKey); ``` ### Create a Hybrid smart account with a Wallet Client signer Use [`toMetaMaskSmartAccount`](../../reference/smart-account.md#tometamasksmartaccount) and Viem's [`createWalletClient`](https://viem.sh/docs/clients/wallet) to create a Hybrid smart account with a Wallet Client signer: ```typescript Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const addresses = await walletClient.getAddresses(); const owner = addresses[0]; const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [owner, [], [], []], deploySalt: "0x", signer: { walletClient }, }); ``` ```typescript const transport = http(); export const publicClient = createPublicClient({ transport, chain, }); ``` ```typescript const privateKey = generatePrivateKey(); const account = privateKeyToAccount(privateKey); export const walletClient = createWalletClient({ account, chain, transport: http() }) ``` ### Create a Hybrid smart account with a passkey signer Use [`toMetaMaskSmartAccount`](../../reference/smart-account.md#tometamasksmartaccount) and Viem's [`toWebAuthnAccount`](https://viem.sh/account-abstraction/accounts/webauthn) to create a Hybrid smart account with a passkey (WebAuthn) signer: :::info Installation required To work with WebAuthn, install the [Ox SDK](https://oxlib.sh/). ::: ```typescript Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; // Deserialize compressed public key const publicKey = PublicKey.fromHex(credential.publicKey); // Convert public key to address const owner = Address.fromPublicKey(publicKey); const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [owner, [toHex(credential.id)], [publicKey.x], [publicKey.y]], deploySalt: "0x", signer: { webAuthnAccount, keyId: toHex(credential.id) }, }); ``` ```typescript const transport = http(); export const publicClient = createPublicClient({ transport, chain, }); ``` ```typescript createWebAuthnCredential, toWebAuthnAccount, } from "viem/account-abstraction"; export const credential = await createWebAuthnCredential({ name: "MetaMask smart account", }); export const webAuthnAccount = toWebAuthnAccount({ credential }); ``` ## Create a Multisig smart account A [Multisig smart account](../../concepts/smart-accounts.md#multisig-smart-account) supports multiple EOA signers with a configurable threshold for execution. Use [`toMetaMaskSmartAccount`](../../reference/smart-account.md#tometamasksmartaccount) to create a Multsig smart account with a combination of account signers and Wallet Client signers: ```typescript Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const owners = [ account.address, walletClient.address ]; const signer = [ { account }, { walletClient } ]; const threshold = 2n const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.MultiSig, deployParams: [owners, threshold], deploySalt: "0x", signer, }); ``` ```typescript const transport = http(); export const publicClient = createPublicClient({ transport, chain, }); ``` ```typescript // This private key will be used to generate the first signer. const privateKey = generatePrivateKey(); export const account = privateKeyToAccount(privateKey); // This private key will be used to generate the second signer. const walletClientPrivatekey = generatePrivateKey(); const walletClientAccount = privateKeyToAccount(walletClientPrivatekey); export const walletClient = createWalletClient({ account: walletClientAccount, chain, transport: http() }); ``` :::note The number of signers in the signatories must be at least equal to the threshold for valid signature generation. ::: ## Create a Stateless 7702 smart account A [Stateless 7702 smart account](../../concepts/smart-accounts.md#stateless-7702-smart-account) represents an EOA that has been upgraded to support MetaMask Smart Accounts functionality as defined by [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702). :::note This implementation does not handle the upgrade process; see the [EIP-7702 quickstart](../../get-started/smart-account-quickstart/eip7702.md) to learn how to upgrade. ::: You can create a Stateless 7702 smart account with the following types of signatories. ### Create a Stateless 7702 smart account with an account signer Use [`toMetaMaskSmartAccount`](../../reference/smart-account.md#tometamasksmartaccount) and Viem's [`privateKeyToAccount`](https://viem.sh/docs/accounts/local/privateKeyToAccount) to create a Stateless 7702 smart account with a signer from a private key: ```typescript Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Stateless7702, address: account.address // Address of the upgraded EOA signer: { account }, }); ``` ```typescript const transport = http(); export const publicClient = createPublicClient({ transport, chain, }); ``` ```typescript const privateKey = generatePrivateKey(); export const account = privateKeyToAccount(privateKey); ``` ### Create a Stateless 7702 smart account with a Wallet Client signer Use [`toMetaMaskSmartAccount`](../../reference/smart-account.md#tometamasksmartaccount) and Viem's [`createWalletClient`](https://viem.sh/docs/clients/wallet) to create a Stateless 7702 smart account with a Wallet Client signer: ```typescript Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const addresses = await walletClient.getAddresses(); const address = addresses[0]; const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Stateless7702, address, // Address of the upgraded EOA signer: { walletClient }, }); ``` ```typescript const transport = http(); export const publicClient = createPublicClient({ transport, chain, }); ``` ```typescript const privateKey = generatePrivateKey(); const account = privateKeyToAccount(privateKey); export const walletClient = createWalletClient({ account, chain, transport: http(), }) ``` ## Next steps With a MetaMask smart account, you can perform the following functions: - In conjunction with [Viem Account Abstraction clients](../configure-toolkit.md), [deploy the smart account](deploy-smart-account.md) and [send user operations](send-user-operation.md). - [Create delegations](../delegation/execute-on-smart-accounts-behalf.md) that can be used to grant specific rights and permissions to other accounts. Smart accounts that create delegations are called *delegator accounts*. --- ## Deploy a smart account You can deploy MetaMask Smart Accounts in two different ways. You can either deploy a smart account automatically when sending the first user operation, or manually deploy the account. ## Prerequisites - [Install and set up the Smart Accounts Kit.](../../get-started/install.md) - [Create a MetaMask smart account.](create-smart-account.md) ## Deploy with the first user operation When you send the first user operation from a smart account, the Smart Accounts Kit checks whether the account is already deployed. If the account is not deployed, the toolkit adds the `initCode` to the user operation to deploy the account within the same operation. Internally, the `initCode` is encoded using the `factory` and `factoryData`. ```typescript // Appropriate fee per gas must be determined for the specific bundler being used. const maxFeePerGas = 1n; const maxPriorityFeePerGas = 1n; const userOperationHash = await bundlerClient.sendUserOperation({ account: smartAccount, calls: [ { to: "0x1234567890123456789012345678901234567890", value: parseEther("0.001"), } ], maxFeePerGas, maxPriorityFeePerGas }); ``` ```typescript Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const publicClient = createPublicClient({ chain, transport: http() }); const privateKey = generatePrivateKey(); const account = privateKeyToAccount(privateKey); export const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", signer: { account }, }); export const bundlerClient = createBundlerClient({ client: publicClient, transport: http("https://public.pimlico.io/v2/11155111/rpc") }); ``` ## Deploy manually To deploy a smart account manually, call the [`getFactoryArgs`](../../reference/smart-account.md#getfactoryargs) method from the smart account to retrieve the `factory` and `factoryData`. This allows you to use a relay account to sponsor the deployment without needing a paymaster. The `factory` represents the contract address responsible for deploying the smart account, while `factoryData` contains the calldata that will be executed by the `factory` to deploy the smart account. The relay account can be either an externally owned account (EOA) or another smart account. This example uses an EOA. ```typescript const { factory, factoryData } = await smartAccount.getFactoryArgs(); // Deploy smart account using relay account. const hash = await walletClient.sendTransaction({ to: factory, data: factoryData, }) ``` ```typescript Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const publicClient = createPublicClient({ chain, transport: http() }); const privateKey = generatePrivateKey(); const account = privateKeyToAccount(privateKey); export const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", signer: { account }, }); const relayAccountPrivateKey = "0x121.."; const relayAccount = privateKeyToAccount(relayAccountPrivateKey) export const walletClient = createWalletClient({ account: relayAccount, chain, transport: http() }) ``` ## Next steps - Learn more about [sending user operations](send-user-operation.md). - To sponsor gas for end users, see how to [send a gasless transaction](send-gasless-transaction.md). --- ## Generate a multisig signature The Smart Accounts Kit supports [Multisig smart accounts](../../concepts/smart-accounts.md#multisig-smart-account), allowing you to add multiple externally owned accounts (EOA) signers with a configurable execution threshold. When the threshold is greater than 1, you can collect signatures from the required signers and use the [`aggregateSignature`](../../reference/smart-account.md#aggregatesignature) function to combine them into a single aggregated signature. ## Prerequisites - [Install and set up the Smart Accounts Kit.](../../get-started/install.md) - [Create a Multisig smart account.](create-smart-account.md#create-a-multisig-smart-account) ## Generate a multisig signature The following example configures a Multisig smart account with two different signers: Alice and Bob. The account has a threshold of 2, meaning that signatures from both parties are required for any execution. ```typescript bundlerClient, aliceSmartAccount, bobSmartAccount, aliceAccount, bobAccount, } from "./config.ts"; const userOperation = await bundlerClient.prepareUserOperation({ account: aliceSmartAccount, calls: [ { target: zeroAddress, value: 0n, data: "0x", } ] }); const aliceSignature = await aliceSmartAccount.signUserOperation(userOperation); const bobSignature = await bobSmartAccount.signUserOperation(userOperation); const aggregatedSignature = aggregateSignature({ signatures: [{ signer: aliceAccount.address, signature: aliceSignature, type: "ECDSA", }, { signer: bobAccount.address, signature: bobSignature, type: "ECDSA", }], }); ``` ```typescript Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const publicClient = createPublicClient({ chain, transport: http() }); const alicePrivateKey = generatePrivateKey(); export const aliceAccount = privateKeyToAccount(alicePrivateKey); const bobPrivateKey = generatePrivateKey(); export const bobAccount = privateKeyToAccount(bobPrivateKey) const signers = [ aliceAccount.address, bobAccount.address ]; const threshold = 2n export const aliceSmartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.MultiSig, deployParams: [signers, threshold], deploySalt: "0x", signer: [ { account: aliceAccount } ], }); export const bobSmartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.MultiSig, deployParams: [signers, threshold], deploySalt: "0x", signer: [ { account: bobAccount } ], }); export const bundlerClient = createBundlerClient({ client: publicClient, transport: http("https://public.pimlico.io/v2/rpc") }); ``` --- ## Send a gasless transaction MetaMask Smart Accounts support gas sponsorship, which simplifies onboarding by abstracting gas fees away from end users. You can use any paymaster service provider, such as [Pimlico](https://docs.pimlico.io/references/paymaster) or [ZeroDev](https://docs.zerodev.app/meta-infra/rpcs), or plug in your own custom paymaster. ## Prerequisites - [Install and set up the Smart Accounts Kit.](../../get-started/install.md) - [Create a MetaMask smart account.](create-smart-account.md) ## Send a gasless transaction The following example demonstrates how to use Viem's [Paymaster Client](https://viem.sh/account-abstraction/clients/paymaster) to send gasless transactions. You can provide the paymaster client using the paymaster property in the [`sendUserOperation`](https://viem.sh/account-abstraction/actions/bundler/sendUserOperation#paymaster-optional) method, or in the [Bundler Client](https://viem.sh/account-abstraction/clients/bundler#paymaster-optional). In this example, the paymaster client is passed to the `sendUserOperation` method. ```typescript // Appropriate fee per gas must be determined for the specific bundler being used. const maxFeePerGas = 1n; const maxPriorityFeePerGas = 1n; const userOperationHash = await bundlerClient.sendUserOperation({ account: smartAccount, calls: [ { to: "0x1234567890123456789012345678901234567890", value: parseEther("0.001") } ], maxFeePerGas, maxPriorityFeePerGas, paymaster: paymasterClient, }); ``` ```typescript Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const publicClient = createPublicClient({ chain, transport: http() }); const privateKey = generatePrivateKey(); const account = privateKeyToAccount(privateKey); export const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", signer: { account }, }); export const bundlerClient = createBundlerClient({ client: publicClient, transport: http("https://api.pimlico.io/v2/11155111/rpc?apikey=") }); export const paymasterClient = createPaymasterClient({ // You can use the paymaster of your choice transport: http("https://api.pimlico.io/v2/11155111/rpc?apikey=") }); ``` --- ## Send a user operation User operations are the [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) counterpart to traditional blockchain transactions. They incorporate significant enhancements that improve user experience and provide greater flexibility in account management and transaction execution. Viem's Account Abstraction API allows a developer to specify an array of `Calls` that will be executed as a user operation via Viem's [`sendUserOperation`](https://viem.sh/account-abstraction/actions/bundler/sendUserOperation) method. The MetaMask Smart Accounts Kit encodes and executes the provided calls. User operations are not directly sent to the network. Instead, they are sent to a bundler, which validates, optimizes, and aggregates them before network submission. See [Viem's Bundler Client](https://viem.sh/account-abstraction/clients/bundler) for details on how to interact with the bundler. :::note If a user operation is sent from a MetaMask smart account that has not been deployed, the toolkit configures the user operation to automatically deploy the account. ::: ## Prerequisites - [Install and set up the Smart Accounts Kit.](../../get-started/install.md) - [Create a MetaMask smart account.](create-smart-account.md) ## Send a user operation The following is a simplified example of sending a user operation using Viem Core SDK. Viem Core SDK offers more granular control for developers who require it. In the example, a user operation is created with the necessary gas limits. This user operation is passed to a bundler instance, and the `EntryPoint` address is retrieved from the client. ```typescript // Appropriate fee per gas must be determined for the specific bundler being used. const maxFeePerGas = 1n; const maxPriorityFeePerGas = 1n; const userOperationHash = await bundlerClient.sendUserOperation({ account: smartAccount, calls: [ { to: "0x1234567890123456789012345678901234567890", value: parseEther("0.001") } ], maxFeePerGas, maxPriorityFeePerGas }); ``` ```typescript Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const publicClient = createPublicClient({ chain, transport: http() }); const privateKey = generatePrivateKey(); const account = privateKeyToAccount(privateKey); export const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", signer: { account }, }); export const bundlerClient = createBundlerClient({ client: publicClient, transport: http("https://public.pimlico.io/v2/11155111/rpc") }); ``` ### Estimate fee per gas Different bundlers have different ways to estimate `maxFeePerGas` and `maxPriorityFeePerGas`, and can reject requests with insufficient values. The following example updates the previous example to estimate the fees. This example uses constant values, but the [Hello Gator example](https://github.com/MetaMask/hello-gator) uses Pimlico's Alto bundler, which fetches user operation gas price using the RPC method [`pimlico_getUserOperationPrice`](https://docs.pimlico.io/infra/bundler/endpoints/pimlico_getUserOperationGasPrice). :::info Installation required To estimate the gas fee for Pimlico's bundler, install the [permissionless.js SDK](https://docs.pimlico.io/references/permissionless/). ::: ```typescript title="example.ts" // add-next-line + import { createPimlicoClient } from "permissionless/clients/pimlico"; // remove-start - const maxFeePerGas = 1n; - const maxPriorityFeePerGas = 1n; // remove-end // add-start + const pimlicoClient = createPimlicoClient({ + transport: http("https://api.pimlico.io/v2/11155111/rpc?apikey="), // You can get the API Key from the Pimlico dashboard. + }); + + const { fast: fee } = await pimlicoClient.getUserOperationGasPrice(); // add-end const userOperationHash = await bundlerClient.sendUserOperation({ account: smartAccount, calls: [ { to: "0x1234567890123456789012345678901234567890", value: parseEther("1") } ], // remove-start - maxFeePerGas, - maxPriorityFeePerGas // remove-end // add-next-line + ...fee }); ``` ### Wait for the transaction receipt After submitting the user operation, it's crucial to wait for the receipt to ensure that it has been successfully included in the blockchain. Use the `waitForUserOperationReceipt` method provided by the bundler client. ```typescript title="example.ts" const pimlicoClient = createPimlicoClient({ transport: http("https://api.pimlico.io/v2/11155111/rpc?apikey="), // You can get the API Key from the Pimlico dashboard. }); const { fast: fee } = await pimlicoClient.getUserOperationGasPrice(); const userOperationHash = await bundlerClient.sendUserOperation({ account: smartAccount, calls: [ { to: "0x1234567890123456789012345678901234567890", value: parseEther("1") } ], ...fee }); // add-start + const { receipt } = await bundlerClient.waitForUserOperationReceipt({ + hash: userOperationHash + }); + + console.log(receipt.transactionHash); // add-end ``` ## Next steps To sponsor gas for end users, see how to [send a gasless transaction](send-gasless-transaction.md). --- ## MetaMask Smart Accounts Kit introduction # MetaMask Smart Accounts Kit The MetaMask Smart Accounts Kit enables developers to create new experiences based on programmable account behavior and granular permission sharing. It offers a suite of contracts, libraries, and services designed for maximum composability, allowing developers to build and extend their dapps with ease. ## Build on MetaMask Smart Accounts The toolkit enables embedding [MetaMask Smart Accounts](concepts/smart-accounts.md) into dapps. Smart accounts support programmable account behavior and advanced features like delegated permissions, multi-signature approvals, and gas abstraction. [Delegation](concepts/delegation/index.md) is a core feature of smart accounts, enabling secure, rule-based permission sharing. Delegation is powered by the [Delegation Framework](https://github.com/metamask/delegation-framework), which defines how permissions are created, shared, and enforced. ## Request Advanced Permissions (ERC-7715) The toolkit supports [Advanced Permissions (ERC-7715)](concepts/advanced-permissions.md), which are fine-grained permissions dapps can request from users directly via the MetaMask browser extension. Advanced Permissions allow you to perform executions on the behalf of MetaMask users. ## Partner integrations The Smart Accounts Kit is integrated with multiple ecosystem partners. Check out the following documentation from these partners: --- ## Bundler Client actions reference The following actions are related to the [Viem Bundler Client](https://viem.sh/account-abstraction/clients/bundler) used to [execute on a MetaMask user's behalf](../../guides/advanced-permissions/execute-on-metamask-users-behalf.md). ## `sendUserOperationWithDelegation` Sends a user operation with redeem permissions according to the [ERC-7710](https://eips.ethereum.org/EIPS/eip-7710) specifications. :::info To use `sendUserOperationWithDelegation`, the Viem Bundler Client must be extended with `erc7710BundlerActions`. ::: ### Parameters See the [Viem `sendUserOperation` parameters](https://viem.sh/account-abstraction/actions/bundler/sendUserOperation). This function has the same parameters, except it does not accept `callData`. Objects in the `calls` array also require the following parameters: | Name | Type | Required | Description | | ---- | ---- | -------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `delegationManager` | `Address` | Yes | The address of Delegation Manager. | | `permissionsContext` | `Hex` | Yes | Encoded calldata for redeeming permissions. If you're not using Advanced Permissions (ERC-7715), you can use the [`redeemDelegations`](../delegation/index.md#redeemdelegations) utility function to generate the calldata manually. | ### Example ```ts // These properties must be extracted from the permission response. const permissionsContext = permissionsResponse[0].context; const delegationManager = permissionsResponse[0].signerMeta.delegationManager; // Calls without permissionsContext and delegationManager will be executed // as a normal user operation. const userOperationHash = await bundlerClient.sendUserOperationWithDelegation({ publicClient, account: sessionAccount, calls: [ { to: sessionAccount.address, data: "0x", value: 1n, permissionsContext, delegationManager, }, ], // Appropriate values must be used for fee-per-gas. maxFeePerGas: 1n, maxPriorityFeePerGas: 1n }); ``` ```ts export const publicClient = createPublicClient({ chain: chain, transport: http(), }); // Your session account for requesting and redeeming should be the same. const privateKey = "0x..."; const account = privateKeyToAccount(privateKey); export const sessionAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", signer: { account }, }); export const bundlerClient = createBundlerClient({ transport: http( `https://your-bundler-url` ), // Allows you to use the same Bundler Client as paymaster. paymaster: true }).extend(erc7710BundlerActions()); ``` --- ## Advanced Permissions reference When [executing on a MetaMask user's behalf](../../guides/advanced-permissions/execute-on-metamask-users-behalf.md), you can request the following permission types for ERC-20 token and native token transfers. Learn [how to use Advanced Permissions types](../../guides/advanced-permissions/use-permissions/erc20-token.md). ## ERC-20 token permissions ### ERC-20 periodic permission Ensures a per-period limit for ERC-20 token transfers. At the start of each new period, the allowance resets. #### Parameters | Name | Type | Required | Description | | ---------------- | --------- | -------- | ---------------------------------------------------------------------- | | `tokenAddress` | `Address` | Yes | The ERC-20 token contract address as a hex string. | | `periodAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred per period. | | `periodDuration` | `number` | Yes | The duration of each period in seconds. | | `startTime` | `number` | No | The start timestamp in seconds. The default is the current time. | | `justification` | `string` | No | A human-readable explanation of why the permission is being requested. | #### Example ```typescript const currentTime = Math.floor(Date.now() / 1000); const expiry = currentTime + 604800; const permission = { type: "erc20-token-periodic", data: { tokenAddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", periodAmount: parseUnits("10", 6), periodDuration: 86400, justification?: "Permission to transfer 1 USDC every day", }, }; ``` ### ERC-20 stream permission Ensures a linear streaming transfer limit for ERC-20 tokens. Token transfers are blocked until the defined start timestamp. At the start, a specified initial amount is released, after which tokens accrue linearly at the configured rate, up to the maximum allowed amount. #### Parameters | Name | Type | Required | Description | | ----------------- | --------- | -------- | ----------------------------------------------------------------------------- | | `tokenAddress` | `Address` | Yes | The ERC-20 token contract address. | | `initialAmount` | `bigint` | No | The initial amount that can be transferred at start time. The default is `0`. | | `maxAmount` | `bigint` | No | The maximum total amount that can be unlocked. The default is no limit. | | `amountPerSecond` | `bigint` | Yes | The rate at which tokens accrue per second. | | `startTime` | `number` | No | The start timestamp in seconds. The default is the current time. | | `justification` | `string` | No | A human-readable explanation of why the permission is being requested. | #### Example ```typescript const currentTime = Math.floor(Date.now() / 1000); const expiry = currentTime + 604800; const permission = { type: "erc20-token-stream", data: { tokenAddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", amountPerSecond: parseUnits("0.1", 6), initialAmount: parseUnits("1", 6), maxAmount: parseUnits("2", 6), startTime: currentTime, justification: "Permission to use 0.1 USDC per second", }, }; ``` ## Native token permissions ### Native token periodic permission Ensures a per-period limit for native token transfers. At the start of each new period, the allowance resets. #### Parameters | Name | Type | Required | Description | | ---------------- | --------- | -------- | ---------------------------------------------------------------------- | | `periodAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred per period. | | `periodDuration` | `number` | Yes | The duration of each period in seconds. | | `startTime` | `number` | No | The start timestamp in seconds. The default is the current time. | | `justification` | `string` | No | A human-readable explanation of why the permission is being requested. | #### Example ```typescript const currentTime = Math.floor(Date.now() / 1000); const expiry = currentTime + 604800; const permission = { type: "native-token-periodic", data: { periodAmount: parseEther("0.001"), periodDuration: 86400, startTime: currentTime, justification: "Permission to use 0.001 ETH every day", }, }; ``` ### Native token stream permission Ensures a linear streaming transfer limit for native tokens. Token transfers are blocked until the defined start timestamp. At the start, a specified initial amount is released, after which tokens accrue linearly at the configured rate, up to the maximum allowed amount. #### Parameters | Name | Type | Required | Description | | ----------------- | --------- | -------- | ----------------------------------------------------------------------------- | | `initialAmount` | `bigint` | No | The initial amount that can be transferred at start time. The default is `0`. | | `maxAmount` | `bigint` | No | The maximum total amount that can be unlocked. The default is no limit. | | `amountPerSecond` | `bigint` | Yes | The rate at which tokens accrue per second. | | `startTime` | `number` | No | The start timestamp in seconds. The default is the current time. | | `justification` | `string` | No | A human-readable explanation of why the permission is being requested. | #### Example ```typescript const currentTime = Math.floor(Date.now() / 1000); const expiry = currentTime + 604800; const permission = { type: "native-token-stream", data: { amountPerSecond: parseEther("0.0001"), initialAmount: parseEther("0.1"), maxAmount: parseEther("1"), startTime: currentTime, justification: "Permission to use 0.0001 ETH per second", }, }; ``` --- ## Wallet Client actions reference The following actions are related to the [Viem Wallet Client](https://viem.sh/docs/clients/wallet) used to [execute on a MetaMask user's behalf](../../guides/advanced-permissions/execute-on-metamask-users-behalf.md). ## `requestExecutionPermissions` Requests Advanced Permissions from the MetaMask extension account according to the [ERC-7715](https://eips.ethereum.org/EIPS/eip-7715) specifications. :::info To use `requestExecutionPermissions`, the Viem Wallet Client must be extended with `erc7715ProviderActions`. ::: ### Parameters | Name | Type | Required | Description | | ---- | ---- | -------- | ----------- | | `chainId` | `number` | Yes | The chain ID on which the permission is being requested. | | `address` | `Address` | No | Address of the wallet to which the permission is being requested. | | `expiry` | `number` | Yes | The timestamp (in seconds) by which the permission must expire. | | `permission` | `SupportedPermissionParams` | Yes | The permission to be requested. The toolkit supports multiple [Advanced Permissions types](permissions.md). | | `signer` | `SignerParam` | Yes | The account to which the permission will be assigned. | | `isAdjustmentAllowed` | `boolean` | Yes | Defines whether the user is allowed to modify the requested permission. | ### Example ```ts const currentTime = Math.floor(Date.now() / 1000); const expiry = currentTime + 604800; const grantedPermissions = await walletClient.requestExecutionPermissions([{ chainId: chain.id, expiry, signer: { type: "account", data: { // The requested permissions will granted to the address. address: "0x0955fFD7b83e5493a8c1FD5dC87e2CF37Eacc44a", }, }, permission: { type: "erc20-token-periodic", data: { tokenAddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", periodAmount: parseUnits("10", 6), periodDuration: 86400, justification?: "Permission to transfer 1 USDC every day", }, }, isAdjustmentAllowed: true, }]); ``` ```ts export const walletClient = createWalletClient({ transport: custom(window.ethereum), }).extend(erc7715ProviderActions()); ``` ## `sendTransactionWithDelegation` Sends a transaction to redeem delegated permissions according to the [ERC-7710](https://eips.ethereum.org/EIPS/eip-7710) specifications. :::info To use `sendTransactionWithDelegation`, the Viem Wallet Client must be extended with `erc7710WalletActions`. ::: ### Parameters See the [Viem `sendTransaction` parameters](https://viem.sh/docs/actions/wallet/sendTransaction#parameters). This function has the same parameters, and it also requires the following parameters: | Name | Type | Required | Description | | ---- | ---- | -------- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `delegationManager` | `Address` | Yes | The address of the Delegation Manager. | | `permissionsContext` | `Hex` | Yes | Encoded calldata for redeeming delegations. If you're not using Advanced Permissions (ERC-7715), you can use the [`redeemDelegations`](../delegation/index.md#redeemdelegations) utility function to generate the calldata manually. | ### Example ```ts // These properties must be extracted from the permission response. See // `grantPermissions` action to learn how to request permissions. const permissionsContext = permissionsResponse[0].context; const delegationManager = permissionsResponse[0].signerMeta.delegationManager; const hash = walletClient.sendTransactionWithDelegation({ chain, to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", value: 1n, permissionsContext, delegationManager }); ``` ```ts export const publicClient = createPublicClient({ chain, transport: http() }); // Your session account for requesting and redeeming should be the same. const privateKey = "0x..."; const account = privateKeyToAccount(privateKey); const walletClient = createWalletClient({ account, transport: http(), chain, }).extend(erc7710WalletActions()); ``` --- ## Caveat Enforcer Client reference The following API methods are related to `CaveatEnforcerClient` used to [check the delegation state](../../guides/delegation/check-delegation-state.md). ## `createCaveatEnforcerClient` Create a Viem Client extended with caveat enforcer actions. This client allows you to interact with the caveat enforcers of the delegation, and read the required state. ### Parameters | Name | Type | Required | Description | | ------------- | ---------------------- | -------- | ----------- | | `client` | `Client` | Yes | The Viem Client to interact with the caveat enforcer contracts and read their state. | | `environment` | `SmartAccountsEnvironment` | Yes | Environment to resolve the smart contracts for the current chain. | ### Example ```typescript const caveatEnforcerClient = createCaveatEnforcerClient({ environment, client, }) ``` ```typescript export const environment = getSmartAccountsEnvironment(chain.id) export const publicClient = createPublicClient({ chain, transport: http(), }) ``` ## `getErc20PeriodTransferEnforcerAvailableAmount` Returns the available amount from the ERC-20 period transfer enforcer for the current period. ### Parameters | Name | Type | Required | Description | | ------------- | ---------------------- | -------- | ----------- | | `delegation` | `Delegation` | Yes | The delegation object for which you want to check the available amount. | ### Example ```typescript // Returns the available amount for current period. const { availableAmount } = await caveatEnforcerClient.getErc20PeriodTransferEnforcerAvailableAmount({ delegation, }) ``` ```typescript const environment = getSmartAccountsEnvironment(chain.id) // Since current time is in seconds, we need to convert milliseconds to seconds. const startDate = Math.floor(Date.now() / 1000) export const delegation = createDelegation({ scope: { type: 'erc20PeriodTransfer', tokenAddress: '0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da', periodAmount: parseUnits('10', 6), periodDuration: 86400, startDate, }, to: 'DELEGATE_ADDRESS', from: 'DELEGATOR_ADDRESS', environment, }) ``` ## `getErc20StreamingEnforcerAvailableAmount` Returns the available amount from the ERC-20 streaming enforcer. ### Parameters | Name | Type | Required | Description | | ------------- | ---------------------- | -------- | ----------- | | `delegation` | `Delegation` | Yes | The delegation object for which you want to check the available amount. | ### Example ```typescript // Returns the available amount const { availableAmount } = await caveatEnforcerClient.getErc20StreamingEnforcerAvailableAmount({ delegation, }) ``` ```typescript const environment = getSmartAccountsEnvironment(chain.id) // Since current time is in seconds, we need to convert milliseconds to seconds. const startTime = Math.floor(Date.now() / 1000) export const delegation = createDelegation({ scope: { type: 'erc20Streaming', tokenAddress: '0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92', amountPerSecond: parseUnits('0.1', 6), initialAmount: parseUnits('1', 6), maxAmount: parseUnits('10', 6), startTime, }, to: 'DELEGATE_ADDRESS', from: 'DELEGATOR_ADDRESS', environment, }) ``` ## `getNativeTokenPeriodTransferEnforcerAvailableAmount` Returns the available amount from the native token period enforcer for the current period. ### Parameters | Name | Type | Required | Description | | ------------- | ---------------------- | -------- | ----------- | | `delegation` | `Delegation` | Yes | The delegation object for which you want to check the available amount. | ### Example ```typescript // Returns the available amount for current period. const { availableAmount } = await caveatEnforcerClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ delegation, }) ``` ```typescript const environment = getSmartAccountsEnvironment(chain.id) // Since current time is in seconds, we need to convert milliseconds to seconds. const startDate = Math.floor(Date.now() / 1000) export const delegation = createDelegation({ scope: { type: 'nativeTokenPeriodTransfer', periodAmount: parseEther('0.01', 6), periodDuration: 86400, startDate, }, to: 'DELEGATE_ADDRESS', from: 'DELEGATOR_ADDRESS', environment, }) ``` ## `getNativeTokenStreamingEnforcerAvailableAmount` Returns the available amount from the native streaming enforcer. ### Parameters | Name | Type | Required | Description | | ------------- | ---------------------- | -------- | ----------- | | `delegation` | `Delegation` | Yes | The delegation object for which you want to check the available amount. | ### Example ```typescript // Returns the available amount const { availableAmount } = await caveatEnforcerClient.getNativeTokenStreamingEnforcerAvailableAmount({ delegation, }) ``` ```typescript const environment = getSmartAccountsEnvironment(chain.id) // Since current time is in seconds, we need to convert milliseconds to seconds. const startTime = Math.floor(Date.now() / 1000) export const delegation = createDelegation({ scope: { type: "nativeTokenStreaming", amountPerSecond: parseEther('0.001'), initialAmount: parseEther('0.01'), maxAmount: parseEther('0.1'), startTime, }, to: 'DELEGATE_ADDRESS', from: 'DELEGATOR_ADDRESS', environment, }) ``` ## `getMultiTokenPeriodEnforcerAvailableAmount` Returns the available amount from the multi token period transfer enforcer for the current period. You'll need to encode the args for the token index you want to check the available amount. ### Parameters | Name | Type | Required | Description | | ------------- | ---------------------- | -------- | ----------- | | `delegation` | `Delegation` | Yes | The delegation object with token index for which you want to check the available amount. | ### Example ```typescript // Encode the args for the multiTokenPeriod enforcer. const args = encodePacked(['uint256'], [BigInt(0)]); // Ensure the index is correct when working with multiple enforcers. delegation.caveats[0].args = args // Returns the available amount for the first token in the list. const { availableAmount } = await caveatEnforcerClient.getMultiTokenPeriodEnforcerAvailableAmount({ delegation, }) ``` ```typescript const environment = getSmartAccountsEnvironment(chain.id) const caveatBuilder = createCaveatBuilder(environment) // Current time as start date. // Since startDate is in seconds, we need to convert milliseconds to seconds. const startDate = Math.floor(Date.now() / 1000); const tokenConfigs = [ { token: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", // 1 token with 6 decimals. periodAmount: parseUnits('1', 6), // 1 day in seconds. periodDuration: 86400, startDate }, { // For native token use zeroAddress token: zeroAddress, // 0.01 ETH in wei. periodAmount: parseEther('0.01'), // 1 hour in seconds. periodDuration: 3600, startDate } ] const caveats = caveatBuilder.addCaveat({ 'multiTokenPeriod', tokenConfigs }) export const delegation: Delegation = { delegate: 'DELEGATE_ADDRESS', delegator: 'DELEGATOR_ADDRESS', authority: ROOT_AUTHORITY, caveats: caveats.build(), salt: '0x', }; ``` --- ## Caveats reference When [constraining a delegation scope](../../guides/delegation/use-delegation-scopes/constrain-scope.md), you can specify the following caveat types. ## `allowedCalldata` Limits the calldata that is executed. You can use this caveat to enforce function parameters. We strongly recommend using this caveat to validate static types and not dynamic types. You can validate dynamic types through a series of `allowedCalldata` terms, but this is tedious and error-prone. Caveat enforcer contract: [`AllowedCalldataEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/AllowedCalldataEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ------------ | -------- | -------- | -------------------------------------------------------------------------------------------------------------------- | | `startIndex` | `number` | Yes | The index in the calldata byte array (including the 4-byte method selector) where the expected calldata starts. | | `value` | `Hex` | Yes | The expected calldata that must match at the specified index. | ### Example ```typescript const value = encodeAbiParameters( [ { type: "string" }, { type: "uint256" } ], [ "Hello Gator", 12345n ] ); const caveats = [{ type: "allowedCalldata", startIndex: 4, value, }]; ``` :::note This example uses Viem's [`encodeAbiParameters`](https://viem.sh/docs/abi/encodeAbiParameters) utility to encode the parameters as ABI-encoded hex strings. ::: ## `allowedMethods` Limits what methods the delegate can call. Caveat enforcer contract: [`AllowedMethodsEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/AllowedMethodsEnforcer.sol) ### Parameters | Name | Type | Required |Description | | ----------- | ------------------ | -------- | ---------- | | `selectors` | `MethodSelector[]` | Yes | The list of method selectors that the delegate is allowed to call. The selector value can be 4-byte hex string, ABI function signature, or ABI function object. | ### Example ```typescript const caveats = [{ type: "allowedMethods", selectors: [ // 4-byte Hex string. "0xa9059cbb", // ABI function signature. "transfer(address,uint256)", // ABI function object. { name: 'transfer', type: 'function', inputs: [ { name: 'recipient', type: 'address' }, { name: 'amount', type: 'uint256' } ], outputs: [], stateMutability: 'nonpayable', }, ] }]; ``` :::note This example adds the `transfer` function to the allowed methods in three different ways - as the 4-byte function selector, the ABI function signature, and the `ABIFunction` object. ::: ## `allowedTargets` Limits what addresses the delegate can call. Caveat enforcer contract: [`AllowedTargetsEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/AllowedTargetsEnforcer.sol) ### Parameters | Name | Type | Required | Description | | --------- | ----------- | -------- | ----------------------------------------------------------- | | `targets` | `Address[]` | Yes | The list of addresses that the delegate is allowed to call. | ### Example ```typescript const caveats = [{ type: "allowedTargets", targets: [ "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", "0xB2880E3862f1024cAC05E66095148C0a9251718b", ] }]; ``` ## `argsEqualityCheck` Ensures that the `args` provided when redeeming the delegation are equal to the terms specified on the caveat. Caveat enforcer contract: [`ArgsEqualityCheckEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ArgsEqualityCheckEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ------ | ----- | -------- | ------------------------------------------------------------------------ | | `args` | `Hex` | Yes | The expected args that must match exactly when redeeming the delegation. | ### Example ```typescript const caveats = [{ type: "argsEqualityCheck", args: "0xf2bef872456302645b7c0bb59dcd96ffe6d4a844f311ebf95e7cf439c9393de2", }]; ``` ## `blockNumber` Specifies a range of blocks through which the delegation will be valid. Caveat enforcer contract: [`BlockNumberEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/BlockNumberEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ----------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------- | | `afterThreshold` | `bigint` | Yes | The block number after which the delegation is valid. Set the value to `0n` to disable this threshold. | | `beforeThreshold` | `bigint` | Yes | The block number before which the delegation is valid. Set the value to `0n` to disable this threshold. | ### Example ```typescript const caveats = [{ type: "blockNumber", afterThreshold: 19426587n, beforeThreshold: 0n, }]; ``` ## `deployed` Ensures a contract is deployed, and if not, deploys the contract. Caveat enforcer contract: [`DeployedEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/DeployedEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ----------------- | --------- | -------- | ------------------------------------ | | `contractAddress` | `Address` | Yes | The contract address. | | `salt` | `Hex` | Yes | The salt to use with the deployment. | | `bytecode` | `Hex` | Yes | The bytecode of the contract. | ### Example ```typescript const caveats = [{ type: "deployed", contractAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", salt: "0x0e3e8e2381fde0e8515ed47ec9caec8ba2bc12603bc2b36133fa3e3fa4d88587", bytecode: "0x..." // The deploy bytecode for the contract at 0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92 }]; ``` ## `erc1155BalanceChange` Ensures that the recipient's ERC-1155 token balance has changed within the allowed bounds — either increased by a minimum or decreased by a maximum specified amount. Caveat enforcer contract: [`ERC1155BalanceChangeEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC1155BalanceChangeEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ---- | ---- | -------- | ------------- | | `tokenAddress` | `Address` | Yes | The ERC-1155 token contract address. | | `recipient` | `Address` | Yes | The address on which the checks will be applied. | | `tokenId` | `bigint` | Yes | The ID of the ERC-1155 token. | | `balance` | `bigint` | Yes | The amount by which the balance must be changed. | | `changeType` | `BalanceChangeType` | Yes | The balance change type for the ERC-1155 token. Specifies whether the balance should have increased or decreased. Valid parameters are `BalanceChangeType.Increase` and `BalanceChangeType.Decrease`. | ### Example ```typescript const caveats = [{ type: "erc1155BalanceChange", tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", recipient: "0x3fF528De37cd95b67845C1c55303e7685c72F319", tokenId: 1n, balance: 1000000n, changeType: BalanceChangeType.Increase, }]; ``` ## `erc20BalanceChange` Ensures that the recipient's ERC-20 token balance has changed within the allowed bounds — either increased by a minimum or decreased by a maximum specified amount. Caveat enforcer contract: [`ERC20BalanceChangeEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC20BalanceChangeEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ---- | ---- | -------- | ------------- | | `tokenAddress` | `Address` | Yes | The ERC-20 token contract addres. | | `recipient` | `Address` | Yes | The address on which the checks will be applied. | | `balance` | `bigint` | Yes | The amount by which the balance must be changed. | | `changeType` | `BalanceChangeType` | Yes | The balance change type for the ERC-20 token. Specifies whether the balance should have increased or decreased. Valid parameters are `BalanceChangeType.Increase` and `BalanceChangeType.Decrease`. | ### Example ```typescript const caveats = [{ type: "erc20BalanceChange", tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", recipient: "0x3fF528De37cd95b67845C1c55303e7685c72F319", balance: 1000000n, changeType: BalanceChangeType.Increase, }]; ``` ## `erc20PeriodTransfer` Ensures that ERC-20 token transfers remain within a predefined limit during a specified time window. At the start of each new period, the allowed transfer amount resets. Any unused transfer allowance from the previous period does not carry over and is forfeited. Caveat enforcer contract: [`ERC20PeriodTransferEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC20PeriodTransferEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ---------------- | --------- | -------- | ---------------------------------------------------------------- | | `tokenAddress` | `Address` | Yes | The ERC-20 token contract address as a hex string. | | `periodAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred per period. | | `periodDuration` | `number` | Yes | The duration of each period in seconds. | | `startDate` | `number` | Yes | The timestamp when the first period begins in seconds. | ### Example ```typescript // Current time as start date. // Since startDate is in seconds, we need to convert milliseconds to seconds. const startDate = Math.floor(Date.now() / 1000); const caveats = [{ type: "erc20PeriodTransfer", // Address of the ERC-20 token. tokenAddress: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", // 1 ERC-20 token - 18 decimals, in wei. periodAmount: 1000000000000000000n, // 1 day in seconds. periodDuration: 86400, startDate, }]; ``` ## `erc20Streaming` Enforces a linear streaming transfer limit for ERC-20 tokens. Block token access until the specified start timestamp. At the start timestamp, immediately release the specified initial amount. Afterward, accrue tokens linearly at the specified rate, up to the specified maximum. Caveat enforcer contract: [`ERC20StreamingEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC20StreamingEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ----------------- | --------- | -------- | --------------------------------------------------------- | | `tokenAddress` | `Address` | Yes | The ERC-20 token contract address. | | `initialAmount` | `bigint` | Yes | The initial amount that can be transferred at start time. | | `maxAmount` | `bigint` | Yes | The maximum total amount that can be unlocked. | | `amountPerSecond` | `bigint` | Yes | The rate at which tokens accrue per second. | | `startTime` | `number` | Yes | The start timestamp in seconds. | ### Example ```typescript // Current time as start date. // Since startDate is in seconds, we need to convert milliseconds to seconds. const startDate = Math.floor(Date.now() / 1000); const caveats = [{ type: "erc20Streaming", // Address of the ERC-20 token. tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", // 1 ERC-20 token - 18 decimals, in wei. initialAmount: 1000000000000000000n, // 10 ERC-20 token - 18 decimals, in wei. maxAmount: 10000000000000000000n // 0.00001 ERC-20 token - 18 decimals, in wei. amountPerSecond: 10000000000000n, startDate, }]; ``` ## `erc20TransferAmount` Limits the transfer of ERC-20 tokens. Caveat enforcer contract: [`ERC20TransferAmountEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC20TransferAmountEnforcer.sol) ### Parameters | Name | Type | Required | Description | | -------------- | --------- | -------- | ----------------------------------------------------------------- | | `tokenAddress` | `Address` | Yes | The ERC-20 token contract address. | | `maxAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred by delegate. | ### Example ```typescript const caveats = [{ type: "erc20TransferAmount", tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", // 1 ERC-20 token - 18 decimals, in wei. maxAmount: 1000000000000000000n }]; ``` ## `erc721BalanceChange` Ensures that the recipient's ERC-721 token balance has changed within the allowed bounds — either increased by a minimum or decreased by a maximum specified amount. Caveat enforcer contract: [`ERC721BalanceChangeEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC721BalanceChangeEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ---- | ---- | -------- | ------------- | | `tokenAddress` | `Address` | Yes | The ERC-721 token contract addres. | | `recipient` | `Address` | Yes | The address on which the checks will be applied. | | `balance` | `bigint` | Yes | The amount by which the balance must be changed. | | `changeType` | `BalanceChangeType` | Yes | The balance change type for the ERC-721 token. Specifies whether the balance should have increased or decreased. Valid parameters are `BalanceChangeType.Increase` and `BalanceChangeType.Decrease`. | ### Example ```typescript const caveats = [{ type: "erc721BalanceChange", tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", recipient: "0x3fF528De37cd95b67845C1c55303e7685c72F319", balance: 1000000n, changeType: BalanceChangeType.Increase, }]; ``` ## `erc721Transfer` Restricts the execution to only allow ERC-721 token transfers, specifically the `transferFrom(from, to, tokenId)` function, for a specified token ID and contract. Caveat enforcer contract: [`ERC721TransferEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC721TransferEnforcer.sol) ### Parameters | Name | Type | Required | Description | | -------------- | --------- | -------- | ---------------------------------------------------------------------------- | | `tokenAddress` | `Address` | Yes | The ERC-721 token contract address. | | `tokenId` | `bigint` | Yes | The ID of the ERC-721 token that can be transferred by delegate. | ### Example ```typescript const caveats = [{ type: "erc721Transfer", tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", tokenId: 1n }]; ``` ## `exactCalldata` Verifies that the transaction calldata matches the expected calldata. For batch transactions, see [`exactCalldataBatch`](#exactcalldatabatch). Caveat enforcer contract: [`ExactCalldataEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ExactCalldataEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ----------------- | -------------------------------- | -------- | ----------------------------------------------------- | | `calldata` | `Hex` | Yes | The calldata that the delegate is allowed to call. | ### Example ```typescript const caveats = [{ type: "exactCalldata", calldata: "0x1234567890abcdef", }]; ``` ## `exactCalldataBatch` Verifies that the provided batch execution calldata matches the expected calldata for each individual execution in the batch. Caveat enforcer contract: [`ExactCalldataBatchEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ExactCalldataBatchEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ----------------- | ------------------------| -------- | ----------------------------------------------------- | | `executions` | `ExecutionStruct[]` | Yes | The list of executions that must be matched exactly in the batch. Each execution specifies a target address, value, and calldata. | ### Example ```typescript const executions = [ { target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", value: 1000000000000000000n, // 1 ETH callData: "0x", }, { target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", value: 0n, callData: "0x", }, ]; const caveats = [{ type: "exactCalldataBatch", executions, }]; ``` ## `exactExecution` Verifies that the provided execution matches the expected execution. For batch transactions, see [`exactExecutionBatch`](#exactexecutionbatch). Caveat enforcer contract: [`ExactExecutionEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ExactExecutionEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ----------------- | ------------------| -------- | ----------------------------------------------------- | | `execution` | `ExecutionStruct` | Yes | The execution that must be matched exactly. Specifies the target address, value, and calldata. | ### Example ```typescript const caveats = [{ type: "exactExecution", target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", value: 1000000000000000000n, callData: "0x", }]; ``` ## `exactExecutionBatch` Verifies that each execution in the batch matches the expected execution parameters - including target, value, and calldata. Caveat enforcer contract: [`ExactExecutionBatchEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ExactExecutionBatchEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ----------------- | ------------------------| -------- | ----------------------------------------------------- | | `executions` | `ExecutionStruct[]` | Yes | The list of executions that must be matched exactly in the batch. Each execution specifies a target address, value, and calldata. | ### Example ```typescript const executions = [ { target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", value: 1000000000000000000n, // 1 ETH callData: "0x", }, { target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", value: 0n, callData: "0x", }, ]; const caveats = [{ type: "exactExecutionBatch", executions, }]; ``` ## `id` Specifies an ID for multiple delegations. Once one of them is redeemed, the other delegations with the same ID are revoked. Caveat enforcer contract: [`IdEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/IdEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ----------| ------------------------| -------- | -------------------------------------------------------------------------------- | | `id` | `bigint` | `number` | Yes | An ID for the delegation. Only one delegation may be redeemed with any given ID. | ### Example ```typescript const caveats = [{ type: "id", id: 123456, }]; ``` ## `limitedCalls` Limits the number of times the delegate can perform executions on the delegator's behalf. Caveat enforcer contract: [`LimitedCallsEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/LimitedCallsEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ----------| ------------------------| -------- | ------------------------------------------------------------ | | `limit` | `number` | Yes | The maximum number of times this delegation may be redeemed. | ### Example ```typescript const caveats = [{ type: "limitedCalls", limit: 1, }]; ``` ## `multiTokenPeriod` Ensures that token transfers for multiple tokens stay within the specified limits for the defined periods. At the start of each new period, the allowed transfer amount for each token resets. Any unused transfer allowance from the previous period expires and does not carry over. When redeeming the delegation, the index of the relevant token configuration must be specified as the `args` of this caveat (encoded as `uint256` hex value). Caveat enforcer contract: [`MultiTokenPeriodEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/MultiTokenPeriodEnforcer.sol) ### Parameters The list of `TokenPeriodConfig` objects, where each object contains: | Name | Type | Required | Description | | ---------------- | --------- | -------- | ---------------------------------------------------------------- | | `token` | `Address` | Yes | The ERC-20 token contract address as a hex string. | | `periodAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred per period. | | `periodDuration` | `number` | Yes | The duration of each period in seconds. | | `startDate` | `number` | Yes | The timestamp when the first period begins in seconds. | ### Example ```typescript // Current time as start date. // Since startDate is in seconds, we need to convert milliseconds to seconds. const startDate = Math.floor(Date.now() / 1000); const tokenPeriodConfigs = [ { token: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", // 1 token with 18 decimals. periodAmount: 1000000000000000000n, // 1 day in seconds. periodDuration: 86400, startDate }, { // For native token use zeroAddress token: zeroAddress, // 0.01 ETH in wei. periodAmount: 10000000000000000n, // 1 hour in seconds. periodDuration: 3600, startDate } ] const caveats = [{ type: "multiTokenPeriod", tokenPeriodConfigs, }]; ``` ## `nativeBalanceChange` Ensures that the recipient's native token balance has changed within the allowed bounds — either increased by a minimum or decreased by a maximum specified amount. Caveat enforcer contract: [`NativeBalanceChangeEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NativeBalanceChangeEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ---- | ---- | -------- | ------------- | | `recipient` | `Address` | Yes | The address on which the checks will be applied. | | `balance` | `bigint` | Yes | The amount by which the balance must be changed. | | `changeType` | `BalanceChangeType` | Yes | The balance change type for the native token. Specifies whether the balance should have increased or decreased. Valid parameters are `BalanceChangeType.Increase` and `BalanceChangeType.Decrease`. | ### Example ```typescript const caveats = [{ type: "nativeBalanceChange", recipient: "0x3fF528De37cd95b67845C1c55303e7685c72F319", balance: 1000000n, changeType: BalanceChangeType.Increase, }]; ``` ## `nativeTokenPayment` Enforces payment in native token (for example, ETH) for the right to use the delegation. A permissions context allowing payment must be provided as the `args` when redeeming the delegation. Caveat enforcer contract: [`NativeTokenPaymentEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NativeTokenPaymentEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ---- | ---- | -------- | ------------- | | `recipient` | `Address` | Yes | The recipient address who receives the payment. | | `amount` | `bigint` | Yes | The amount that must be paid. | ### Example ```typescript const caveats = [{ type: "nativeTokenPayment", recipient: "0x3fF528De37cd95b67845C1c55303e7685c72F319", amount: 1000000n, }]; ``` ## `nativeTokenPeriodTransfer` Ensures that native token transfers remain within a predefined limit during a specified time window. At the start of each new period, the allowed transfer amount resets. Any unused transfer allowance from the previous period does not carry over and is forfeited. Caveat enforcer contract: [`NativeTokenPeriodTransferEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NativeTokenPeriodTransferEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ---------------- | --------- | -------- | ---------------------------------------------------------------- | | `periodAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred per period. | | `periodDuration` | `number` | Yes | The duration of each period in seconds. | | `startDate` | `number` | Yes | The timestamp when the first period begins in seconds. | ### Example ```typescript // Current time as start date. // Since startDate is in seconds, we need to convert milliseconds to seconds. const startDate = Math.floor(Date.now() / 1000); const caveats = [{ type: "nativeTokenPeriodTransfer", // 1 ETH in wei. periodAmount: 1000000000000000000n, // 1 day in seconds. periodDuration: 86400, startDate, }]; ``` ## `nativeTokenStreaming` Enforces a linear streaming limit for native tokens (for example, ETH). Nothing is available before the specified start timestamp. At the start timestamp, the specified initial amount becomes immediately available. After that, tokens accrue linearly at the specified rate, capped by the specified maximum. Caveat enforcer contract: [`NativeTokenStreamingEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NativeTokenStreamingEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ----------------- | --------- | -------- | --------------------------------------------------------- | | `initialAmount` | `bigint` | Yes | The initial amount that can be transferred at start time. | | `maxAmount` | `bigint` | Yes | The maximum total amount that can be unlocked. | | `amountPerSecond` | `bigint` | Yes | The rate at which tokens accrue per second. | | `startTime` | `number` | Yes | The start timestamp in seconds. | ### Example ```typescript // Current time as start date. // Since startDate is in seconds, we need to convert milliseconds to seconds. const startDate = Math.floor(Date.now() / 1000); const caveats = [{ type: "nativeTokenStreaming", // 0.01 ETH in wei. initialAmount: 10000000000000000, // 0.5 ETH in wei. maxAmount: 500000000000000000n // 0.00001 ETH in wei. amountPerSecond: 10000000000000n, startDate, }]; ``` ## `nativeTokenTransferAmount` Enforces an allowance of native currency (for example, ETH). Caveat enforcer contract: [`NativeTokenTransferAmountEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NativeTokenTransferAmountEnforcer.sol) ### Parameters | Name | Type | Required | Description | | -------------- | --------- | -------- | ----------------------------------------------------------------- | | `maxAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred by delegate. | ### Example ```typescript const caveats = [{ type: "nativeTokenTransferAmount", // 0.00001 ETH in wei. maxAmount: 10000000000000000n }]; ``` ## `nonce` Adds a nonce to a delegation, and revokes previous delegations by incrementing the current nonce by calling `incrementNonce(address _delegationManager)`. Caveat enforcer contract: [`NonceEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NonceEnforcer.sol) ### Parameters | Name | Type | Required | Description | | -------------- | --------- | -------- | ----------------------------------------------------------------- | | `nonce` | `Hex` | Yes | The nonce to allow bulk revocation of delegations. | ### Example ```typescript const caveats = [{ type: "nonce", nonce: "0x1" }]; ``` ## `ownershipTransfer` Restricts the execution to only allow ownership transfers, specifically the `transferOwnership(address _newOwner)` function, for a specified contract. Caveat enforcer contract: [`OwnershipTransferEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/OwnershipTransferEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ----------------- | --------- | -------- | -----------------------------------------------------------------------| | `contractAddress` | `Address` | Yes | The target contract address for which ownership transfers are allowed. | ### Example ```typescript const caveats = [{ type: "ownershipTransfer", contractAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92" }]; ``` ## `redeemer` Limits the addresses that can redeem the delegation. This caveat is designed to restrict smart contracts or EOAs lacking delegation support, and can be placed anywhere in the delegation chain to restrict the redeemer. :::note Delegator accounts with delegation functionalities can bypass these restrictions by delegating to other addresses. For example, Alice makes Bob the redeemer. This condition is enforced, but if Bob is a delegator he can create a separate delegation to Carol that allows her to redeem Alice's delegation through Bob. ::: Caveat enforcer contract: [`RedeemerEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/RedeemerEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ----------------- | ----------- | -------- | -----------------------------------------------------------------------| | `redeemers` | `Address[]` | Yes | The list of addresses that are allowed to redeem the delegation. | ### Example ```typescript const caveats = [{ type: "redeemer", redeemers: [ "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", "0x6be97c23596ECed7170fdFb28e8dA1Ca5cdc54C5" ], }]; ``` ## `specificActionERC20TransferBatch` Ensures validation of a batch consisting of exactly two transactions: 1. The first transaction must call a specific target contract with predefined calldata. 2. The second transaction must be an ERC-20 token transfer that matches specified parameters—including the ERC-20 token contract address, amount, and recipient. Caveat enforcer contract: [`SpecificActionERC20TransferBatchEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/SpecificActionERC20TransferBatchEnforcer.sol) ### Parameters | Name | Type | Required | Description | | -------------- | --------- | -------- | --------------------------------------------- | | `tokenAddress` | `Address` | Yes | The ERC-20 token contract address. | | `recipient` | `Address` | Yes | The address that will receive the tokens. | | `amount` | `bigint` | Yes | The amount of tokens to transfer. | | `target` | `Address` | Yes | The target address for the first transaction. | | `calldata` | `Hex` | Yes | The `calldata` for the first transaction. | ### Example ```typescript const caveats = [{ type: "specificActionERC20TransferBatch", tokenAddress: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", recipient: "0x027aeAFF3E5C33c4018FDD302c20a1B83aDCD96C", // 1 ERC-20 token - 18 decimals, in wei amount: 1000000000000000000n, target: "0xb49830091403f1Aa990859832767B39c25a8006B", calldata: "0x1234567890abcdef" }]; ``` ## `timestamp` Specifies a range of timestamps through which the delegation will be valid. Caveat enforcer contract: [`TimestampEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/TimestampEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ----------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------- | | `afterThreshold` | `number` | Yes | The timestamp after which the delegation is valid in seconds. Set the value to `0` to disable this threshold. | | `beforeThreshold` | `number` | Yes | The timestamp before which the delegation is valid in seconds. Set the value to `0` to disable this threshold. | ### Example ```typescript // We need to convert milliseconds to seconds. const currentTime = Math.floor(Date.now() / 1000); // 1 hour after current time. const afterThreshold = currentTime + 3600; // 1 day after afterThreshold const beforeThreshold = afterThreshold + 86400; const caveats = [{ type: "timestamp", afterThreshold, beforeThreshold, }]; ``` ## `valueLte` Limits the value of native tokens that the delegate can spend. Caveat enforcer contract: [`ValueLteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ValueLteEnforcer.sol) ### Parameters | Name | Type | Required | Description | | ----------------- | ----------- | -------- | -----------------------------------------------------------------------| | `maxValue` | `bigint` | Yes | The maximum value that may be specified when redeeming this delegation.| ### Example ```typescript const caveats = [{ type: "valueLte", // 0.01 ETH in wei. maxValue: 10000000000000000n }]; ``` --- ## Delegation scopes When [creating a delegation](../../guides/delegation/execute-on-smart-accounts-behalf.md), you can configure the following scopes to define the delegation's initial authority. Learn [how to use delegation scopes](../../guides/delegation/use-delegation-scopes/index.md). ## Spending limit scopes ### ERC-20 periodic scope Ensures a per-period limit for ERC-20 token transfers. At the start of each new period, the allowance resets. #### Parameters | Name | Type | Required | Description | | ---------------- | --------- | -------- | ---------------------------------------------------------------- | | `tokenAddress` | `Address` | Yes | The ERC-20 token contract address as a hex string. | | `periodAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred per period. | | `periodDuration` | `number` | Yes | The duration of each period in seconds. | | `startDate` | `number` | Yes | The timestamp when the first period begins in seconds. | #### Example ```typescript // Since current time is in seconds, convert milliseconds to seconds. const startDate = Math.floor(Date.now() / 1000); const delegation = createDelegation({ scope: { type: "erc20PeriodTransfer", tokenAddress: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", // 10 ERC-20 token with 6 decimals periodAmount: parseUnits("10", 6), periodDuration: 86400, startDate, }, // Address that is granting the delegation from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", // Address to which the delegation is being granted to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", // Alternatively you can use environment property of MetaMask smart account. environment: getSmartAccountsEnvironment(sepolia.id); }); ``` ### ERC-20 streaming scope Ensures a linear streaming transfer limit for ERC-20 tokens. Token transfers are blocked until the defined start timestamp. At the start, a specified initial amount is released, after which tokens accrue linearly at the configured rate, up to the maximum allowed amount. #### Parameters | Name | Type | Required | Description | | ----------------- | --------- | -------- | --------------------------------------------------------- | | `tokenAddress` | `Address` | Yes | The ERC-20 token contract address. | | `initialAmount` | `bigint` | Yes | The initial amount that can be transferred at start time. | | `maxAmount` | `bigint` | Yes | The maximum total amount that can be unlocked. | | `amountPerSecond` | `bigint` | Yes | The rate at which tokens accrue per second. | | `startTime` | `number` | Yes | The start timestamp in seconds. | #### Example ```typescript // Since current time is in seconds, convert milliseconds to seconds. const startTime = Math.floor(Date.now() / 1000); const delegation = createDelegation({ scope: { type: "erc20Streaming", tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", // 0.1 ERC-20 token with 6 decimals amountPerSecond: parseUnits("0.1", 6), // 1 ERC-20 token with 6 decimals initialAmount: parseUnits("1", 6), // 10 ERC-20 token with 6 decimals maxAmount: parseUnits("10", 6), startTime, }, // Address that is granting the delegation from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", // Address to which the delegation is being granted to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", // Alternatively you can use environment property of MetaMask smart account. environment: getSmartAccountsEnvironment(sepolia.id); }); ``` ### ERC-20 transfer scope Ensures that ERC-20 token transfers are limited to a predefined maximum amount. This scope is useful for setting simple, fixed transfer limits without any time-based or streaming conditions. #### Parameters | Name | Type | Required | Description | | -------------- | --------- | -------- | ----------------------------------------------------------------- | | `tokenAddress` | `Address` | Yes | The ERC-20 token contract address. | | `maxAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred by delegate. | #### Example ```typescript const delegation = createDelegation({ scope: { type: "erc20TransferAmount", tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", // 1 ERC-20 token with 6 decimals maxAmount: parseUnits("1", 6), }, // Address that is granting the delegation from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", // Address to which the delegation is being granted to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", // Alternatively you can use environment property of MetaMask smart account. environment: getSmartAccountsEnvironment(sepolia.id); }); ``` ### ERC-721 scope Limits the delegation to ERC-721 token (NFT) transfers only. #### Parameters | Name | Type | Required | Description | | -------------- | --------- | -------- | ---------------------------------------------------------------------------- | | `tokenAddress` | `Address` | Yes | The ERC-721 token contract address. | | `tokenId` | `bigint` | Yes | The ID of the ERC-721 token that can be transferred by delegate. | #### Example ```typescript const delegation = createDelegation({ scope: { type: "erc721Transfer", tokenAddress: "0x3fF528De37cd95b67845C1c55303e7685c72F319", tokenId: 1n, }, // Address that is granting the delegation from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", // Address to which the delegation is being granted to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", // Alternatively you can use environment property of MetaMask smart account. environment: getSmartAccountsEnvironment(sepolia.id); }); ``` ### Native token periodic scope Ensures a per-period limit for native token transfers. At the start of each new period, the allowance resets. #### Parameters | Name | Type | Required | Description | | ---------------- | --------- | -------- | ---------------------------------------------------------------- | | `periodAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred per period. | | `periodDuration` | `number` | Yes | The duration of each period in seconds. | | `startDate` | `number` | Yes | The timestamp when the first period begins in seconds. | | `allowedCalldata` | `AllowedCalldataBuilderConfig[]` | No | The list of calldata the delegate is allowed to call. It doesn't support multiple selectors. Each entry in the list represents a portion of calldata corresponding to the same function signature. You can include or exclude specific parameters to define what parts of the calldata are valid. Cannot be used together with `exactCalldata`. | | `exactCalldata` | `ExactCalldataBuilderConfig` | No | The calldata the delegate is allowed to call. The default is `0x` to disallow ERC-20 and ERC-721 token transfers. Cannot be used together with `allowedCalldata`. | #### Example ```typescript // Since current time is in seconds, convert milliseconds to seconds. const startDate = Math.floor(Date.now() / 1000); const delegation = createDelegation({ scope: { type: "nativeTokenPeriodTransfer", periodAmount: parseEther("0.01"), periodDuration: 86400, startDate, }, // Address that is granting the delegation. from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", // Address to which the delegation is being granted. to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", // Alternatively you can use environment property of MetaMask smart account. environment: getSmartAccountsEnvironment(sepolia.id); }); ``` ### Native token streaming scope Ensures a linear streaming transfer limit for native tokens. Token transfers are blocked until the defined start timestamp. At the start, a specified initial amount is released, after which tokens accrue linearly at the configured rate, up to the maximum allowed amount. #### Parameters | Name | Type | Required | Description | | ----------------- | --------- | -------- | --------------------------------------------------------- | | `initialAmount` | `bigint` | Yes | The initial amount that can be transferred at start time. | | `maxAmount` | `bigint` | Yes | The maximum total amount that can be unlocked. | | `amountPerSecond` | `bigint` | Yes | The rate at which tokens accrue per second. | | `startTime` | `number` | Yes | The start timestamp in seconds. | | `allowedCalldata` | `AllowedCalldataBuilderConfig[]` | No | The list of calldata the delegate is allowed to call. It doesn't support multiple selectors. Each entry in the list represents a portion of calldata corresponding to the same function signature. You can include or exclude specific parameters to define what parts of the calldata are valid. Cannot be used together with `exactCalldata`. | | `exactCalldata` | `ExactCalldataBuilderConfig` | No | The calldata the delegate is allowed to call. The default is `0x` to disallow ERC-20 and ERC-721 token transfers. Cannot be used together with `allowedCalldata`. | #### Example ```typescript // Since current time is in seconds, convert milliseconds to seconds. const startTime = Math.floor(Date.now() / 1000); const delegation = createDelegation({ scope: { type: "nativeTokenStreaming", amountPerSecond: parseEther("0.0001"), initialAmount: parseEther("0.01"), maxAmount: parseEther("0.1"), startTime, }, // Address that is granting the delegation. from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", // Address to which the delegation is being granted. to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", // Alternatively you can use environment property of MetaMask smart account. environment: getSmartAccountsEnvironment(sepolia.id); }); ``` ### Native token transfer scope Ensures that native token transfers are limited to a predefined maximum amount. This scope is useful for setting simple, fixed transfer limits without any time based or streaming conditions. #### Parameters | Name | Type | Required | Description | | ----------------- | --------- | -------- | ----------------------------------------------------------------- | | `maxAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred by delegate. | | `allowedCalldata` | `AllowedCalldataBuilderConfig[]` | No | The list of calldata the delegate is allowed to call. It doesn't support multiple selectors. Each entry in the list represents a portion of calldata corresponding to the same function signature. You can include or exclude specific parameters to define what parts of the calldata are valid. Cannot be used together with `exactCalldata`. | | `exactCalldata` | `ExactCalldataBuilderConfig` | No | The calldata the delegate is allowed to call. The default is `0x` to disallow ERC-20 and ERC-721 token transfers. Cannot be used together with `allowedCalldata`. | #### Example ```typescript const delegation = createDelegation({ scope: { type: "nativeTokenTransferAmount", // 0.001 ETH in wei format. maxAmount: parseEther("0.001"), }, // Address that is granting the delegation. from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", // Address to which the delegation is being granted. to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", // Alternatively you can use environment property of MetaMask smart account. environment: getSmartAccountsEnvironment(sepolia.id); }); ``` ## Function call scope Defines the specific methods, contract addresses, and calldata that are allowed for the delegation. #### Parameters | Name | Type | Required | Description | | ----------------- | -------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `targets` | `Address[]` | Yes | The list of addresses that the delegate is allowed to call. | | `selectors` | `MethodSelector[]` | Yes | The list of method selectors that the delegate is allowed to call. The selector value can be 4-byte hex string, ABI function signature, or ABI function object. | | `allowedCalldata` | `AllowedCalldataBuilderConfig[]` | No | The list of calldata the delegate is allowed to call. It doesn't support multiple selectors. Each entry in the list represents a portion of calldata corresponding to the same function signature. You can include or exclude specific parameters to define what parts of the calldata are valid. Cannot be used together with `exactCalldata`. | | `exactCalldata` | `ExactCalldataBuilderConfig` | No | The calldata the delegate is allowed to call. Cannot be used together with `allowedCalldata`. | #### Example This example sets the delegation scope to allow the delegate to call the `approve` function on the USDC token contract: ```typescript const delegation = createDelegation({ scope: { type: "functionCall", targets: ["0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"], // USDC address on Sepolia. selectors: ["approve(address, uint256)"] }, // Address that is granting the delegation. from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", // Address to which the delegation is being granted. to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", // Alternatively you can use environment property of MetaMask smart account. environment: getSmartAccountsEnvironment(sepolia.id); }); ``` ## Ownership transfer scope Restricts a delegation to ownership transfer calls only. #### Parameters | Name | Type | Required | Description | | ----------------- | --------- | -------- | -----------------------------------------------------------------------| | `contractAddress` | `Address` | Yes | The target contract address for which ownership transfers are allowed. | #### Example ```typescript const contractAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" const delegation = createDelegation({ scope: { type: "ownershipTransfer", contractAddress, }, // Address that is granting the delegation. from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", // Address to which the delegation is being granted. to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", // Alternatively you can use environment property of MetaMask smart account. environment: getSmartAccountsEnvironment(sepolia.id); }); ``` --- ## Delegation API reference The following API methods are related to creating and managing [delegations](../../concepts/delegation/index.md). ## `createCaveatBuilder` Builds an array of caveats. ### Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `environment` | `SmartAccountsEnvironment` | Yes | Environment to resolve the smart contracts for the current chain. | | `config` | `CaveatBuilderConfig` | No | Configuration for `CaveatBuilder`. | ### Example ```ts const caveats = createCaveatBuilder(delegatorSmartAccount.environment) ``` ```ts Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const publicClient = createPublicClient({ chain, transport: http(), }); const delegatorAccount = privateKeyToAccount("0x..."); export const delegatorSmartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [delegatorAccount.address, [], [], []], deploySalt: "0x", signer: { account: delegatorAccount }, }); ``` ### Allow empty caveats To create an empty caveat collection, set the `CaveatBuilderConfig.allowEmptyCaveats` to `true`. ```ts title="example.ts" // The config.ts is the same as in the previous example. const caveats = createCaveatBuilder(delegatorSmartAccount.environment, { // add-next-line allowEmptyCaveats: true, }); ``` ## `createDelegation` Creates a delegation with a specific delegate. ### Parameters | Name | Type | Required | Description | | ---- | ---- | -------- | ----------- | | `from` | `Hex` | Yes | The address that is granting the delegation. | | `to` | `Hex` | Yes | The address to which the delegation is being granted. | | `scope` | `ScopeConfig` | Yes | The scope of the delegation that defines the initial authority. | | `environment` | `SmartAccountsEnvironment` | Yes | The environment used by the toolkit to define contract addresses for interacting with the Delegation Framework contracts. | | `caveats` | `Caveats` | No | Caveats that further refine the authority granted by the `scope`. | | `parentDelegation` | `Delegation \| Hex` | No | The parent delegation or its corresponding hex to create a delegation chain. | | `salt` | `Hex` | No | The salt for generating the delegation hash. This helps prevent hash collisions when creating identical delegations. | ### Example ```typescript const delegation = createDelegation({ // Address that is granting the delegation from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", // Address to which the delegation is being granted to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", // Alternatively you can use environment property of MetaMask smart account. environment: getSmartAccountsEnvironment(sepolia.id), scope: { type: "nativeTokenTransferAmount", // 0.001 ETH in wei format. maxAmount: parseEther("0.001"), }, }); ``` ## `createOpenDelegation` Creates an open delegation that can be redeemed by any delegate. ### Parameters | Name | Type | Required | Description | | ---- | ---- | -------- | ----------- | | `from` | `Hex` | Yes | The address that is granting the delegation. | | `scope` | `ScopeConfig` | Yes | The scope of the delegation that defines the initial authority. | | `environment` | `SmartAccountsEnvironment` | Yes | The environment used by the toolkit to define contract addresses for interacting with the Delegation Framework contracts. | | `caveats` | `Caveats` | No | Caveats that further refine the authority granted by the `scope`. | | `parentDelegation` | `Delegation \| Hex` | No | The parent delegation or its corresponding hex to create a delegation chain. | | `salt` | `Hex` | No | The salt for generating the delegation hash. This helps prevent hash collisions when creating identical delegations. | ### Example ```typescript const delegation = createOpenDelegation({ // Address that is granting the delegation from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", // Alternatively you can use environment property of MetaMask smart account. environment: getSmartAccountsEnvironment(sepolia.id), scope: { type: "nativeTokenTransferAmount", // 0.001 ETH in wei format. maxAmount: parseEther("0.001"), }, }); ``` ## `createExecution` Creates an `ExecutionStruct` instance. ### Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `target` | `Address` | No | Address of the contract or recipient that the call is directed to. | | `value` | `bigint` | No | Value of native tokens to send along with the call in wei. | | `callData` | `Hex` | No | Encoded function data or payload to be executed on the target address. | ### Example ```ts // Creates an ExecutionStruct to transfer 0.01 ETH to // 0xe3C818389583fDD5cAC32f548140fE26BcEaE907 address. const execution = createExecution({ target: "0xe3C818389583fDD5cAC32f548140fE26BcEaE907", // 0.01 ETH in wei value: parseEther("0.01"), callData: "0x", }); ``` ## `decodeDelegations` Decodes an ABI-encoded hex string to an array of delegations. ### Parameters | Name | Type | Required | Description | | ---- | ---- | -------- | ----------- | | `encoded` | `Hex` | Yes | The ABI encoded hex string to decode. | ### Example ```ts const delegations = decodeDelegations("0x7f0db33d..c06aeeac"); ``` ## `deploySmartAccountsEnvironment` Deploys the Delegation Framework contracts to an EVM chain. ### Parameters | Name | Type | Required | Description | | ---- | ---- | -------- | ----------- | | `walletClient` | `WalletClient` | Yes | [Viem Wallet Client](https://viem.sh/docs/clients/wallet#wallet-client) to deploy the contracts. | | `publicClient` | `PublicClient` | Yes | [Viem Public Client](https://viem.sh/docs/clients/public) to interact with the given chain. | | `chain` | `Chain` | Yes | [Viem Chain](https://viem.sh/docs/chains/introduction) where you wish to deploy the Delegation Framework contracts. | | `deployedContracts` | `{ [contract: string]: Hex }` | No | Allows overriding specific contract addresses when calling the function. For example, if certain contracts have already been deployed on the target chain, their addresses can be provided directly to the function. | ### Example ```ts const environment = await deploySmartAccountsEnvironment( walletClient, publicClient, chain ); ``` ```ts // Your deployer wallet private key. const privateKey = "0x123.."; const account = privateKeyToAccount(privateKey); export const walletClient = createWalletClient({ account, chain, transport: http() }); export const publicClient = createPublicClient({ transport: http(), chain, }); ``` ### Inject deployed contracts Once the contracts are deployed, you can use them to override the delegator environment using `overrideDeployedEnvironment`. ```ts title="example.ts" overrideDeployedEnvironment, deploySmartAccountsEnvironment, } from "@metamask/smart-accounts-kit/utils"; const environment: SmartAccountsEnvironment = await deploySmartAccountsEnvironment( walletClient, publicClient, chain ); // add-start overrideDeployedEnvironment( chain.id, "1.3.0", environment, ); // add-end ``` ## `disableDelegation` Encodes the calldata for disabling a delegation. ### Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `delegation` | `Delegation` | Yes | The delegation to be disabled. | ### Example ```ts const disableDelegationData = DelegationManager.encode.disableDelegation({ delegation, }); ``` ```ts export const delegation = createDelegation({ from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", environment: getSmartAccountsEnvironment(sepolia.id), scope: { type: "nativeTokenTransferAmount", // 0.001 ETH in wei format. maxAmount: parseEther("0.001"), }, }); ``` ## `encodeDelegations` Encodes an array of delegations to an ABI-encoded hex string. ### Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `delegations` | `Delegation[]` | Yes | The delegation array to be encoded. | ### Example ```ts const encodedDelegations = encodeDelegations([delegation]); ``` ```ts export const delegation = createDelegation({ from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", environment: getSmartAccountsEnvironment(sepolia.id), scope: { type: "nativeTokenTransferAmount", // 0.001 ETH in wei format. maxAmount: parseEther("0.001"), }, }); ``` ## `getDelegationHashOffchain` Returns the delegation hash. ### Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `input` | `Delegation` | Yes | The delegation object to hash. | ### Example ```ts const delegationHash = getDelegationHashOffchain(delegation); ``` ```ts getSmartAccountsEnvironment, createDelegation, } from "@metamask/smart-accounts-kit"; const environment = getSmartAccountsEnvironment(sepolia.id); // The address to which the delegation is granted. It can be an EOA address, or // smart account address. const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; export const delegation = createDelegation({ to: delegate, // Address that is granting the delegation. from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", environment, scope: { type: "nativeTokenTransferAmount", // 0.001 ETH in wei format. maxAmount: parseEther("0.001"), }, }); ``` ## `getSmartAccountsEnvironment` Resolves the `SmartAccountsEnvironment` for a chain. ### Parameters | Name | Type | Required | Description | | ---- | ---- | -------- | ----------- | | `chainId` | `number` | Yes | The chain ID of the network for which the `SmartAccountsEnvironment` should be resolved. | | `version` | `SupportedVersion` | No | Specifies the version of the Delegation Framework contracts to use. If omitted, the latest supported version will be used by default. | ### Example ```ts const environment = getSmartAccountsEnvironment(sepolia.id) ``` ## `overrideDeployedEnvironment` Overrides or adds the `SmartAccountsEnvironment` for a chain and supported version. ### Parameters | Name | Type | Required | Description | | ---- | ---- | -------- | ----------- | | `chainId` | `number` | Yes | The chain ID of the network for which the `SmartAccountsEnvironment` should be overridden. | | `version` | `SupportedVersion` | Yes | The version of the Delegation Framework contracts to override for the specified chain. | | `environment` | `SmartAccountsEnvironment` | Yes | The environment containing contract addresses to override for the given chain and version. | ### Example ```ts overrideDeployedEnvironment( sepolia.id, "1.3.0", environment ); ``` ```ts export const environment: SmartAccountsEnvironment = { SimpleFactory: "0x124..", // ... implementations: { // ... }, }; ``` ## `redeemDelegations` Encodes calldata for redeeming delegations. This method supports batch redemption, allowing multiple delegations to be processed within a single transaction. ### Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `delegations` | `Delegation[][]` | Yes | A nested collection representing chains of delegations. Each inner collection contains a chain of delegations to be redeemed. | | `modes` | `ExecutionMode[]` | Yes | A collection specifying the [execution mode](../../concepts/delegation/index.md#execution-modes) for each corresponding delegation chain. Supported execution modes are `SingleDefault`, `SingleTry`, `BatchDefault`, and `BatchTry`. | | `executions` | `ExecutionStruct[][]` | Yes | A nested collection where each inner collection contains a list of `ExecutionStruct` objects associated with a specific delegation chain. | ### Example This example assumes you have a delegation signed by the delegator. ```ts const data = DelegationManager.encode.redeemDelegations({ delegations: [[ signedDelegation ]], modes: [ ExecutionMode.SingleDefault ], executions: [[ execution ]], }); ``` ## `signDelegation` Signs the delegation and returns the delegation signature. ### Parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `privateKey` | `Hex` | Yes | The private key to use for signing the delegation. | | `delegation` | `Omit` | Yes | The unsigned delegation object to sign. | | `chainId` | `number` | Yes | The chain ID on which the delegation manager is deployed. | | `delegationManager` | `0x${string}` | Yes | The address of the Delegation Manager. | | `name` | `string` | No | The name of the domain of the Delegation Manager. The default is `DelegationManager`. | | `version` | `string` | No | The version of the domain of the Delegation Manager. The default is `1`. | | `allowInsecureUnrestrictedDelegation` | `boolean` | No | Whether to allow insecure unrestricted delegation with no caveats. The default is `false`. | ### Example ```ts const signature = signDelegation({ privateKey, delegation, chainId: sepolia.id, delegationManager, }) ``` ```ts getSmartAccountsEnvironment, createDelegation, } from "@metamask/smart-accounts-kit"; const environment = getSmartAccountsEnvironment(sepolia.id); export const delegationManager = environment.DelegationManager; export const privateKey = `0x12141..`; const account = privateKeyToAccount(privateKey); // The address to which the delegation is granted. It can be an EOA address, or // smart account address. const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; export const delegation = createDelegation({ to: delegate, from: account.address, environment, scope: { type: "nativeTokenTransferAmount", // 0.001 ETH in wei format. maxAmount: parseEther("0.001"), }, }); ``` --- ## MetaMask Smart Accounts API reference The following API methods are related to creating, managing, and signing with [MetaMask Smart Accounts](../concepts/smart-accounts.md). ## `aggregateSignature` Aggregates multiple partial signatures into a single combined multisig signature. ### Parameters | Name | Type | Required | Description | | ---- | ---- | -------- | ----------- | | `signatures` | `PartialSignature[]` | Yes | Collection of partial signatures provided by signers, to be merged into an aggregated signature. | ### Example ```typescript bundlerClient, aliceSmartAccount, bobSmartAccount, aliceAccount, bobAccount, } from "./config.ts"; const userOperation = await bundlerClient.prepareUserOperation({ account: aliceSmartAccount, calls: [ { target: zeroAddress, value: 0n, data: "0x", } ] }); const aliceSignature = await aliceSmartAccount.signUserOperation(userOperation); const bobSignature = await bobSmartAccount.signUserOperation(userOperation); const aggregatedSignature = aggregateSignature({ signatures: [{ signer: aliceAccount.address, signature: aliceSignature, type: "ECDSA", }, { signer: bobAccount.address, signature: bobSignature, type: "ECDSA", }], }); ``` ```typescript Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const publicClient = createPublicClient({ chain, transport: http() }); const alicePrivateKey = generatePrivateKey(); const aliceAccount = privateKeyToAccount(alicePrivateKey); const bobPrivateKey = generatePrivateKey(); const bobAccount = privateKeyToAccount(bobPrivateKey) const signers = [ aliceAccount.address, bobAccount.address ]; const threshold = 2n export const aliceSmartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.MultiSig, deployParams: [signers, threshold], deploySalt: "0x", signer: [ { account: aliceAccount } ], }); export const bobSmartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.MultiSig, deployParams: [signers, threshold], deploySalt: "0x", signer: [ { account: bobAccount } ], }); export const bundlerClient = createBundlerClient({ client: publicClient, transport: http("https://public.pimlico.io/v2/rpc") }); ``` ## `encodeCalls` Encodes calls for execution by a MetaMask smart account. If there's a single call directly to the smart account, it returns the call data directly. For multiple calls or calls to other addresses, it creates executions and encodes them for the smart account's `execute` function. The execution mode is set to `SingleDefault` for a single call to other address, or `BatchDefault` for multiple calls. ### Parameters | Name | Type | Required | Description | | ---- | ---- | -------- | ----------- | | `calls` | `Call[]` | Yes | List of calls to be encoded. | ### Example ```ts const calls = [{ to: zeroAddress, data: "0x", value: 0n }]; const executeCallData = await smartAccount.encodeCalls(calls); ``` ```ts Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const publicClient = createPublicClient({ chain, transport: http(), }); const delegatorAccount = privateKeyToAccount("0x..."); export const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [delegatorAccount.address, [], [], []], deploySalt: "0x", signer: { account: delegatorAccount }, }); ``` ## `getFactoryArgs` Returns the factory address and factory data that can be used to deploy a smart account. ### Example ```ts const { factory, factoryData } = await smartAccount.getFactoryArgs(); ``` ```ts Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const publicClient = createPublicClient({ chain, transport: http(), }); const delegatorAccount = privateKeyToAccount("0x..."); export const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [delegatorAccount.address, [], [], []], deploySalt: "0x", signer: { account: delegatorAccount }, }); ``` ## `getNonce` Returns the nonce for a smart account. ### Example ```ts const nonce = await smartAccount.getNonce(); ``` ```ts Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const publicClient = createPublicClient({ chain, transport: http(), }); const delegatorAccount = privateKeyToAccount("0x..."); export const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [delegatorAccount.address, [], [], []], deploySalt: "0x", signer: { account: delegatorAccount }, }); ``` ## `signDelegation` Signs the delegation and returns the delegation signature. ### Parameters | Name | Type | Required | Description | | ---- | ---- | -------- | ----------- | | `delegation` | `Omit` | Yes | The unsigned delegation object to sign. | | `chainId` | `number` | No | The chain ID on which the Delegation Manager is deployed. | ### Example ```ts // The address to which the delegation is granted. It can be an EOA address, or // smart account address. const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; const delegation = createDelegation({ to: delegate, from: account.address, environment: delegatorSmartAccount.environment, scope: { type: "nativeTokenTransferAmount", // 0.001 ETH in wei format. maxAmount: 1000000000000000n, }, }); const signature = delegatorSmartAccount.signDelegation({ delegation }); ``` ```ts Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const publicClient = createPublicClient({ chain, transport: http(), }); const delegatorAccount = privateKeyToAccount("0x..."); export const delegatorSmartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [delegatorAccount.address, [], [], []], deploySalt: "0x", signer: { account: delegatorAccount }, }); ``` ## `signMessage` Generates the [EIP-191](https://eips.ethereum.org/EIPS/eip-191) signature using the `MetaMaskSmartAccount` signer. The Smart Accounts Kit uses Viem under the hood to provide this functionality. ### Parameters See the [Viem `signMessage` parameters](https://viem.sh/account-abstraction/accounts/smart/signMessage). ### Example ```ts const signature = smartAccount.signMessage({ message: 'hello world', }) ``` ```ts Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const publicClient = createPublicClient({ chain, transport: http(), }); const account = privateKeyToAccount("0x..."); export const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", signer: { account }, }); ``` ## `signTypedData` Generates the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) signature using the `MetaMaskSmartAccount` signer. The Smart Accounts Kit uses Viem under the hood to provide this functionality. ### Parameters See the [Viem `signTypedData` parameters](https://viem.sh/account-abstraction/accounts/smart/signTypedData). ### Example ```ts const signature = smartAccount.signTypedData({ domain, types, primaryType: "Mail", message: { from: { name: "Cow", wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", }, to: { name: "Bob", wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", }, contents: "Hello, Bob!", }, }) ``` ```ts Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const publicClient = createPublicClient({ chain, transport: http(), }); const account = privateKeyToAccount("0x..."); export const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", signer: { account }, }); ``` ## `signUserOperation` Signs a user operation with the `MetaMaskSmartAccount` signer. The Delegation Toolkit uses Viem under the hood to provide this functionality. ### Parameters See the [Viem `signUserOperation` parameters](https://viem.sh/account-abstraction/accounts/smart/signUserOperation#parameters). ### Example ```ts const userOpSignature = smartAccount.signUserOperation({ callData: "0xdeadbeef", callGasLimit: 141653n, maxFeePerGas: 15000000000n, maxPriorityFeePerGas: 2000000000n, nonce: 0n, preVerificationGas: 53438n, sender: "0xE911628bF8428C23f179a07b081325cAe376DE1f", verificationGasLimit: 259350n, signature: "0x", }); ``` ```ts Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const publicClient = createPublicClient({ chain, transport: http(), }); const account = privateKeyToAccount("0x..."); export const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", signer: { account }, }); ``` ## `toMetaMaskSmartAccount` Creates a `MetaMaskSmartAccount` instance. ### Parameters | Name | Type | Required | Description | | ---- |-----------------------------------------------------|--------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `client` | `Client` | Yes | Viem Client to retrieve smart account data. | | `implementation` | `TImplementation` | Yes | Implementation type for the smart account. Can be Hybrid, Multisig, or Stateless7702. | | `signer` | `SignerConfigByImplementation ` | Yes | Signers for the smart account. Can be a Viem Account, Viem Wallet Client, or a WebAuthnAccount. Web3AuthnAccounts are only supported for Hybrid implementations. | | `environment` | `SmartAccountsEnvironment` | No | Environment to resolve the smart contracts. | | `deployParams` | `DeployParams` | Required if `address` is not provided | The parameters that will be used to deploy the smart account and generate its deterministic address. | | `deploySalt` | `Hex` | Required if `address` is not provided | The salt that will be used to deploy the smart account. | | `address` | `Address` | Required if `deployParams` and `deploySalt` are not provided, or if the implementation is `Stateless7702`. | The address of the smart account. If an address is provided, the smart account will not be deployed. This should be used if you intend to interact with an existing smart account. | ### Hybrid implementation #### `deployParams` All Hybrid deploy parameters are required: | Name | Type | Description | | ---- | ---- | ----------- | | `owner` | `Hex` | The owner's account address. The owner can be the zero address, indicating that there is no owner configured. | | `p256KeyIds` | `Hex[]` | An array of key identifiers for passkey signers. | | `p256XValues` | `bigint[]` | An array of public key x-values for passkey signers. | | `p256YValues` | `bigint[]` | An array of public key y-values for passkey signers. | #### Example ```ts Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", signer: { account: account }, }); ``` ```ts export const account = privateKeyToAccount("0x..."); export const publicClient = createPublicClient({ chain, transport: http(), }); ``` ### Multisig implementation #### `deployParams` All Multisig deploy parameters are required: | Name | Type | Description | | ---- | ---- | ----------- | | `signers` | `Hex[]` | An array of EOA signer addresses. | | `threshold` | `bigint` | The number of signers required to execute a transaction. | #### Example ```ts publicClient, aliceAccount, bobAccount } from "./config.ts"; Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const signers = [ aliceAccount.address, bobAccount.address ]; const threshold = 2n const aliceSmartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.MultiSig, deployParams: [signers, threshold], deploySalt: "0x", signer: [ { account: aliceAccount } ], }); ``` ```ts export const publicClient = createPublicClient({ chain, transport: http() }); const alicePrivateKey = generatePrivateKey(); export const aliceAccount = privateKeyToAccount(alicePrivateKey); const bobPrivateKey = generatePrivateKey(); export const bobAccount = privateKeyToAccount(bobPrivateKey); ``` ### Stateless7702 implementation example ```ts Implementation, toMetaMaskSmartAccount, } from "@metamask/smart-accounts-kit"; const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Stateless7702, address: account.address, signer: { account }, }); ``` ```ts export const account = privateKeyToAccount("0x..."); export const publicClient = createPublicClient({ chain, transport: http(), }); ```