diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e259d8a19bf..db3baabc673 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -17,6 +17,9 @@ # Snaps documentation /snaps/ @MetaMask/tech-writers @MetaMask/snaps +# Delegation Toolkit documentation +/delegation-toolkit/ @MetaMask/tech-writers @MetaMask/delegation @MetaMask/delegation-devrel + # SDK documentation /sdk/ @MetaMask/tech-writers @MetaMask/sdk-devs @MetaMask/sdk-devrel diff --git a/delegation-toolkit/concepts/_category_.json b/delegation-toolkit/concepts/_category_.json new file mode 100644 index 00000000000..122abce7702 --- /dev/null +++ b/delegation-toolkit/concepts/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Concepts", + "position": 3, + "link": { + "type": "generated-index", + "slug": "/concepts", + "title": "Delegation Toolkit concepts" + } +} diff --git a/delegation-toolkit/concepts/caveat-enforcers.md b/delegation-toolkit/concepts/caveat-enforcers.md new file mode 100644 index 00000000000..a3061bc2210 --- /dev/null +++ b/delegation-toolkit/concepts/caveat-enforcers.md @@ -0,0 +1,185 @@ +--- +description: Learn about caveat enforcers and how they restrict delegations. +sidebar_position: 4 +--- + +# Caveat enforcers + +The MetaMask Delegation Toolkit 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](delegation.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; + +import { ModeCode } from "../utils/Types.sol"; + +/** + * 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`](../how-to/create-delegation/restrict-delegation.md) interface in the MetaMask Delegation Toolkit. + +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/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/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/caveats.md#nativetokenpayment) before + [`NativeBalanceChangeEnforcer`](../reference/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 Delegation Toolkit provides [many out-of-the-box caveat enforcers](../reference/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 more complex scenarios, you can also [create custom caveat enforcers](../how-to/create-delegation/create-custom-caveat-enforcer.md) by implementing the `ICaveatEnforcer` interface. + +## Attenuating authority with redelegations + +When [creating chains of delegations](../how-to/create-delegation/index.md#create-a-redelegation), 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. diff --git a/delegation-toolkit/concepts/delegation.md b/delegation-toolkit/concepts/delegation.md new file mode 100644 index 00000000000..e7176eff491 --- /dev/null +++ b/delegation-toolkit/concepts/delegation.md @@ -0,0 +1,140 @@ +--- +description: Learn about delegation, the delegation lifecycle, and the Delegation Framework. +sidebar_position: 2 +--- + +# Delegation + +*Delegation* is the ability for a [smart account](smart-accounts.md) to grant permission to another smart account +or externally owned account (EOA) to perform specific executions on their behalf, under defined rules and restrictions. +The account that grants the permission is called the *delegator account*, while the account that receives the permission +is called the *delegate account*. + +The MetaMask Delegation Toolkit includes the following delegation features: + +- **Caveats** - 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. + +- **Chain of delegations** - Users can redelegate permissions that have been delegated to them, creating a chain of delegations across trusted parties. + +Delegations are created using the `Delegation` type, which is specified as follows: + +```typescript +export type Delegation = { + delegate: Hex; // The address to which the delegation is being granted. + delegator: Hex; // The address that is granting the delegation. + authority: Hex; // Hash of the parent delegation, or the constant ROOT_AUTHORITY. + caveats: Caveat[]; // Caveats that restrict the authority being granted. + salt: Hex; // Used to avoid hash collisions between identical delegations. + signature: Hex; // Signature from the delegator account. +}; +``` + +## Delegation lifecycle + +The delegation lifecycle is as follows: + +1. **Delegation creation** - A delegation is initialized, and the delegator account signs it. + +2. **Caveat enforcement** - The caveats applied to the delegation specify conditions under which + the delegation can be redeemed. + +3. **Delegation storage** - The delegation can be stored, enabling retrieval for future redemption. + + :::note + [Storing and retrieving delegations](../experimental/store-retrieve-delegations.md) using the toolkit's + `DelegationStorageClient` is an experimental feature. + ::: + +4. **Delegation redemption** - The delegate (the account being granted the permission) redeems the + delegation through an [ERC-4337 user operation](smart-accounts.md#account-abstraction-erc-4337), + which verifies that the delegated authority is valid in order to perform the execution. + +See [how to create a delegation](../how-to/create-delegation/index.md) to get started with the +delegation lifecycle. + +## Delegation Framework + +The MetaMask Delegation Toolkit 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** - There are [multiple delegator account implementations](smart-accounts.md#smart-account-implementation-types), + with the main difference being the signature scheme 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 a delegation is redeemed, 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 `permissionContexts`, `modes`, and + `executionCallDatas` match, or throws `BatchDataLengthMismatch`. + 2. Decodes and validates the delegation, checking that the caller (`msg.sender`) is the delegate + and that there are no empty signatures, or throws `InvalidDelegate`. + 3. Verifies delegation signatures, ensuring validity using `ECDSA` (for EOAs) or + `isValidSignature` (for contracts), or throws `InvalidSignature`. + 4. Validates the delegation chain's authority and ensures delegations are not disabled. + 5. Executes the `beforeHook` for each `caveat` 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 redemption flow + +This diagram illustrates how a delegation is created and subsequently redeemed on 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': 25, + 'width': 200 + } + } +}%% + +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 +``` diff --git a/delegation-toolkit/concepts/environment.md b/delegation-toolkit/concepts/environment.md new file mode 100644 index 00000000000..ec989fd0343 --- /dev/null +++ b/delegation-toolkit/concepts/environment.md @@ -0,0 +1,228 @@ +--- +description: Learn about the delegator environment object `DeleGatorEnvironment` and how to use it. +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Delegator environment + +The `DeleGatorEnvironment` object is a component of the MetaMask Delegation Toolkit that defines the contract addresses necessary for interacting with the [Delegation Framework](delegation.md#delegation-framework) on a specific network. + +The delegator environment 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 delegator environment + +When you create a [MetaMask smart account](smart-accounts.md), the Delegation 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 +import { DeleGatorEnvironment } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +const environment: DeleGatorEnvironment = delegatorSmartAccount.environment; +``` + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +const delegatorAccount = privateKeyToAccount("0x..."); + +const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegatorAccount.address, [], [], []], + deploySalt: "0x", + signatory: { 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 `getDelegatorEnvironment` function to resolve the environment. +This function is especially useful if your delegator is not a smart account when +[creating a redelegation](../how-to/create-delegation/index.md#create-a-redelegation). + +```typescript +import { + getDeleGatorEnvironment, + DeleGatorEnvironment, +} from "@metamask/delegation-toolkit"; + +// Resolves the DeleGatorEnvironment for Linea Sepolia +const environment: DeleGatorEnvironment = getDelegatorEnvironment(59141); +``` + +## Deploy custom delegator environment + +You can deploy the contracts using any method, but the toolkit provides a convenient `deployDelegatorEnvironment` 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.html), [Wallet Client](https://viem.sh/docs/clients/wallet.html), and [Chain](https://viem.sh/docs/glossary/types#chain) +to deploy the contracts and resolve the `DeleGatorEnvironment`. + +Your wallet must have sufficient native token balance to deploy the contracts. + + + + +```typescript +import { walletClient, publicClient } from "./config.ts"; +import { lineaSepolia as chain } from "viem/chains"; + +const environment = await deployDeleGatorEnvironment( + walletClient, + publicClient, + chain +); +``` + + + + +```typescript +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient, createPublicClient } from "viem"; + +// 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 `deployDelegatorEnvironment`. +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. +import { walletClient, publicClient } from "./config.ts"; +import { lineaSepolia as chain } from "viem/chains"; + +const environment = await deployDeleGatorEnvironment( + walletClient, + publicClient, + chain, + // add-start ++ { ++ EntryPoint: "0x0000000071727De22E5E9d8BAf0edAc6f37da032" ++ } + // add-end +); +``` + +Once the contracts are deployed, you can use them to override the delegator environment. + +## Override delegator environment + +To override the delegator environment, the toolkit provides a `overrideDeployedEnvironment` function to resolve +`DeleGatorEnvironment` with specified contracts for the given chain and contract version. + +```typescript +// The config.ts is the same as in the previous example. +import { walletClient, publicClient } from "./config.ts"; +import { lineaSepolia as chain } from "viem/chains"; +import { + DeleGatorEnvironment, + overrideDeployedEnvironment, + deployDeleGatorEnvironment +} from "@metamask/delegation-toolkit"; + +const environment: DeleGatorEnvironment = await deployDeleGatorEnvironment( + 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 `DelegatorEnvironment` instance with the required contract addresses, and pass it to the function. + +```typescript +// remove-start +- import { walletClient, publicClient } from "./config.ts"; +- import { lineaSepolia as chain } from "viem/chains"; +// remove-end +import { + DeleGatorEnvironment, + overrideDeployedEnvironment, + // remove-next-line +- deployDeleGatorEnvironment +} from "@metamask/delegation-toolkit"; + +// remove-start +- const environment: DeleGatorEnvironment = await deployDeleGatorEnvironment( +- walletClient, +- publicClient, +- chain +- ); +// remove-end + +// add-start ++ const evniroment: DeleGatorEnvironment = { ++ 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. +::: diff --git a/delegation-toolkit/concepts/smart-accounts.md b/delegation-toolkit/concepts/smart-accounts.md new file mode 100644 index 00000000000..4e1cb3a8668 --- /dev/null +++ b/delegation-toolkit/concepts/smart-accounts.md @@ -0,0 +1,88 @@ +--- +description: Learn about MetaMask smart accounts. +sidebar_position: 1 +--- + +# Smart accounts + +The MetaMask Delegation Toolkit enables you to create and manage MetaMask *smart accounts*. +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. + +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 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 MetaMask Delegation Toolkit supports two types of smart accounts, each offering unique features and use cases. +See [Configure accounts and signers](../how-to/create-smart-account/configure-accounts-signers.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 P256 (passkey) signers. +You can configure any of these signers as the signatory, 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 for valid signatures, allowing for enhanced security and flexibility in account management. +The signatory must have at least as many signers include as the threshold is configured for the account. + +This type is referenced in the toolkit as `Implementation.Multisig`. + +## Smart account flow + +The MetaMask smart account 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 signatory. + +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 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.md). diff --git a/delegation-toolkit/experimental/_category_.json b/delegation-toolkit/experimental/_category_.json new file mode 100644 index 00000000000..5661f467b80 --- /dev/null +++ b/delegation-toolkit/experimental/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Experimental", + "position": 5, + "link": { + "type": "generated-index", + "slug": "/experimental", + "title": "Delegation Toolkit experimental features" + } +} diff --git a/delegation-toolkit/experimental/erc-7710-redeem-delegations.md b/delegation-toolkit/experimental/erc-7710-redeem-delegations.md new file mode 100644 index 00000000000..676d6b89442 --- /dev/null +++ b/delegation-toolkit/experimental/erc-7710-redeem-delegations.md @@ -0,0 +1,234 @@ +--- +description: Learn how to redeem ERC-7710 delegations with a smart contract account or an externally owned account (EOA). +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# ERC-7710: Redeem delegations + +:::caution Experimental +This is an experimental feature and may change in future releases. +::: + +[ERC-7710](https://eip.tools/eip/7710) introduces a standard way for smart accounts to delegate capabilities to other +smart accounts or externally owned accounts (EOAs). + +The MetaMask Delegation Toolkit provides two experimental functions, `erc7710BundlerActions()` and `erc7710WalletActions()`, that let +a caller redeem delegations granted by MetaMask's permissions system. + +## Extract relevant data + +Refer to [ERC-7715: Request permissions](erc-7715-request-permissions.md) for information on how to request user permissions. +Once the permission has been granted, extract the relevant data from the response. +For example: + +```typescript +// Response received from the ERC-7715 wallet_grantPermissions request. +const permissionsResponse = [{ + chainId: "0xe715", + account: "0xD6f56C2B10b1e02D841E4a97c60Afe914E884DBd", + expiry: 1234567890, + permission: { + type: "native-token-stream", + data: { + amountPerSecond: "0x1", + maxAmount: "0x2", + initialAmount: undefined, + startTime: 2, + }, + }, + context: "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d" + signer: { + type: "account", + data: { + account: "0x07bfc7230D5BD2544059816D88A895BB000Abe00" + } + }, + signerMeta: { + delegationManager: "0xDC7e12b41E5e61BfCc7F56AAFB7B93288F61e841" + }, + accountMetadata: [{ + factory: "0x65E726b404149fE37F4b291c81Dc6eddd44763A7", + factoryData: "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b" + }] +}]; + +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +// accountMeta is only present when the smart account is not deployed. +const accountMetadata = permissionsResponse[0].accountMeta; +``` + +This data encodes the authority that lets the delegate redeem the permission. + +### Security considerations for `accountMeta` + +When a user grants a permission, they can provide `accountMeta` which is an array of `factory` and `factoryData` values. +These calls must be executed before redeeming the permission (this is handled for you in `sendUserOperationWithDelegation`). + +Because each `accountMeta` is an arbitrary call specified by the granter, it is important that these are executed carefully. +We recommend taking the following precautions: + +- **Only grant permissions to session accounts** - When requesting permissions, use an account that is only used for that single purpose, and does not contain tokens. +This way, any `accountMeta` executed can't perform any damaging actions. + +- **Only execute `accountMeta` against trusted factory addresses** - Ensure that only `accountMeta` targeting a known factory address is executed. +The bundler action `sendUserOperationWithDelegation` only executes `accountMeta` that targets the `SimpleFactory` address for the current Delegation Framework. +If you redeem delegations in any other way, it is your responsibility to validate trusted factory addresses. + +## Redeem the permission + +Redeem a delegation with a [smart account](#redeem-with-a-smart-account) or an [externally owned account (EOA)](#redeem-with-an-eoa). + +### Redeem with a smart account + +To redeem a delegation with a smart account, create a [`MetaMaskSmartAccount`](../how-to/create-smart-account/index.md#create-a-metamasksmartaccount) +and a [Viem Bundler Client](https://viem.sh/account-abstraction/clients/bundler). + +After setting up your Bundler Client, you can extend its functionality with `erc7710BundlerActions` actions to support ERC-7710. Once extended, use `sendUserOperationWithDelegation` to redeem the permission. + + + + +```typescript +import { sessionAccount, bundlerClient, publicClient } from "./config.ts"; + +// These properties must be extracted from the permission response. +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +const accountMetadata = permissionsResponse[0].accountMeta; + +// 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 + accountMetadata, +}); +``` + + + + +```typescript +import { createPublicClient, http, createBundlerClient } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { erc7710BundlerActions } from "@metamask/delegation-toolkit/experimental"; +import { toMetaMaskSmartAccount, Implementation } from "@metamask/delegation-toolkit"; + +export const publicClient = createPublicClient({ + chain: chain, + transport: http(), +}); + +// Your session account for requesting and redeeming should be same. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + + +export const sessionAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { 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()); +``` + + + +:::note +`sendUserOperationWithDelegation` is similar to the `sendUserOperation` function, but does not accept `callData` directly. +::: + +### Redeem with an EOA + +To redeem a delegation with an EOA, create a [Viem Wallet Client](https://viem.sh/docs/clients/wallet). + +After creating your Wallet Client, you can extend its functionality with `erc7710WalletActions` actions to support ERC-7710. Once extended, use `sendTransactionWithDelegation` to redeem the permission. + + + + +```typescript +import { walletClient, publicClient } from "./config.ts"; + +// These properties must be extracted from the permission response. +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +const accountMetadata = permissionsResponse[0].accountMeta; + +if (accountMetadata?.length !== 0) { + // If the granted permission contains accountMetadata, this must be executed before attempting to + // redeem the delegation. + + // This transaction will deploy the delegator account. + const hash = walletClient.sendTransaction({ + to: accountMetadata.factory, + data: accountMetadata.factoryData, + }); + + // You should wait for transaction to be successfully executed. + // You can use the TransactionReceipt.status to verify the state. + await publicClient.waitForTransactionReceipt( { hash }); +} + +const hash = walletClient.sendTransactionWithDelegation({ + chain, + to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + value: 1n, + permissionsContext, + delegationManager +}); +``` + + + + +```typescript +import { http, createPublicClient, createWalletClient } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { erc7710WalletActions } from "@metamask/delegation-toolkit/experimental"; + +export const publicClient = createPublicClient({ + chain, + transport: http() +}); + +// Your session account for requesting and redeeming should be same. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + +const walletClient = createWalletClient({ + account, + transport: http(), + chain, +}).extend(erc7710WalletActions()); +``` + + diff --git a/delegation-toolkit/experimental/erc-7715-request-permissions.md b/delegation-toolkit/experimental/erc-7715-request-permissions.md new file mode 100644 index 00000000000..9f0ea03c64e --- /dev/null +++ b/delegation-toolkit/experimental/erc-7715-request-permissions.md @@ -0,0 +1,183 @@ +--- +description: Learn how to request ERC-7715 permissions. +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# ERC-7715: Request permissions + +:::caution Experimental +This is an experimental feature. +It requires MetaMask Flask 12.14.2 or later, and may change in future releases. +::: + +[ERC-7715](https://eip.tools/eip/7715) introduces a standard way for dapps to request permissions from a wallet to execute +transactions on a user's behalf. + +The MetaMask Delegation Toolkit provides the experimental actions for ERC-7715 that lets a caller request permissions from MetaMask's permissions system. + +## Request permissions + +To request permissions, extend your [Viem Wallet Client](https://viem.sh/docs/clients/wallet) with `erc7715ProviderActions` actions. +You'll need a session account to request the permission, which can be either a smart account or an externally owned account (EOA). +This example uses a smart account: + + + + +```typescript +import { sepolia as chain } from "viem/chains"; +import { sessionAccount, walletClient } from "./config.ts"; + +const expiry = Math.floor(Date.now() / 1000 + 604_800); // 1 week from now. +const currentTime = Math.floor(Date.now() / 1000); // now + +const grantedPermissions = await walletClient.grantPermissions([{ + chainId: chain.id, + expiry, + signer: { + type: "account", + data: { + address: sessionAccount.address, + }, + }, + permission: { + type: "native-token-stream", + data: { + initialAmount: 1n, // 1 wei + amountPerSecond: 1n, // 1 wei per second + maxAmount: 10n, // 10 wei + startTime: currentTime, + justification: "Payment for a week long subscription", + }, + }, +}]); +``` + + + + + +```typescript +import { createWalletClient, custom, createPublicClient, http } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { erc7715ProviderActions } from "@metamask/delegation-toolkit/experimental"; +import { toMetaMaskSmartAccount, Implementation } from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain: chain, + transport: http(), +}); + +// The private key of the session owner. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + +export const sessionAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); + +export const walletClient = createWalletClient({ + transport: custom(window.ethereum), +}).extend(erc7715ProviderActions()); +``` + + + +
+ ERC-7715 request permission sample +
+ Here's what your ERC-7715 native token streaming allowance request looks like: + + ```ts + [{ + chainId: "0xaa36a7", + expiry: 1745041429, + permission: { + type: "native-token-stream", + data: { + amountPerSecond: "0x1", + maxAmount: "0x1", + initialAmount: "0xa", + startTime: 1744955029, + justification: "Payment for a week long subscription", + }, + }, + signer: { + type: "account", + data: { + address: "0x1234...", + }, + }, + }] + ``` + + Learn more about the [ERC-7715 permission schema](https://eip.tools/eip/7715). +
+
+ + +Additionally, you can allow users to modify the requested permission by setting the `isAdjustmentAllowed` parameter to `true` in the request. + +```typescript +import { sepolia as chain } from "viem/chains"; +// The config.ts is the same as in the previous example. +import { sessionAccount, walletClient } from "./config.ts"; + +const expiry = Math.floor(Date.now() / 1000 + 604_800); // 1 week from now. +const currentTime = Math.floor(Date.now() / 1000); // now + +const grantedPermissions = await walletClient.grantPermissions([{ + chainId: chain.id, + expiry, + signer: { + type: "account", + data: { + address: sessionAccount.address, + }, + }, + permission: { + type: "native-token-stream", + // add-next-line ++ isAdjustmentAllowed: true, + data: { + initialAmount: 1n, // 1 wei + amountPerSecond: 1n, // 1 wei per second + maxAmount: 10n, // 10 wei + startTime: currentTime, + justification: "Payment for a week long subscription", + }, + }, +}]); +``` + +:::note +Users have full control over the permissions they grant—depending on the permission you request, they may choose to grant more limited permissions than requested. +You should always verify the granted permissions and adjust your dapp's behavior accordingly. +::: + +## Security considerations for `accountMeta` + +When a user grants a permission, they can provide [`accountMeta`](erc-7710-redeem-delegations.md#extract-relevant-data) which is an array of `factory` and `factoryData` values. +These calls must be executed before redeeming the permission (this is handled for you in `sendUserOperationWithDelegation`). + +Because each `accountMeta` is an arbitrary call specified by the granter, it is important that these are executed carefully. +We recommend taking the following precautions: + +- **Only grant permissions to session accounts** - When requesting permissions, use an account that is only used for that single purpose, and does not contain tokens. + This way, any `accountMeta` executed can't perform any damaging actions. + +- **Only execute `accountMeta` against trusted factory addresses** - Ensure that only `accountMeta` targeting a known factory address is executed. + The bundler action `sendUserOperationWithDelegation` only executes `accountMeta` that targets the `SimpleFactory` address for the current Delegation Framework. + If you redeem delegations in any other way, it is your responsibility to validate trusted factory addresses. + +## Next steps + +You can redeem the granted permission using the experimental [ERC-7710 `erc7710WalletActions()`](erc-7710-redeem-delegations.md). diff --git a/delegation-toolkit/experimental/store-retrieve-delegations.md b/delegation-toolkit/experimental/store-retrieve-delegations.md new file mode 100644 index 00000000000..3b6cde29fd6 --- /dev/null +++ b/delegation-toolkit/experimental/store-retrieve-delegations.md @@ -0,0 +1,172 @@ +--- +description: Store and retrieve delegations using the `DelegationStorageClient`. +sidebar_position: 1 +toc_max_heading_level: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Store and retrieve delegations + +:::caution Experimental +This is an experimental feature and may change in future releases. +::: + +You can use methods provided by the `DelegationStorageClient` of the MetaMask Delegation Toolkit to store and retrieve +[delegations](../concepts/delegation.md). + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](../how-to/configure.md) +- Ensure you have an API key and API key ID to interact with the `DelegationStorageClient`. + If you need to gain access, email hellogators@consensys.net. + +## Configure the storage client + +Create the `DelegationStorageClient` instance, and configure it using your API key and API key ID. + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + +## Store a delegation + +To store a delegation, use the `storeDelegation` method of the `DelegationStorageClient`. This method takes one parameter: + +1. `delegation` - A `Delegation` object representing the delegation to be stored. + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; + +const delegationHash = await delegationStorageClient.storeDelegation(delegation); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + + +## Retrieve a delegation chain + +To retrieve a delegation chain, use the `getDelegationChain` method of the `DelegationStorageClient`. This method takes one parameter: + +1. `leafDelegationOrDelegationHash` - Either a `Delegation` object or the delegation hash as a hex string. + +:::note +A delegation can be a root delegation, where its `authority` is `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`. It can also be a child of another delegation, where its `authority` is the hash of its parent delegation. This method returns the delegation referenced by `leafDelegationOrDelegationHash` and any ancestors. +::: + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; +import { getDelegationHashOffchain } from "@metamask/delegation-toolkit"; + +// Assuming you have the leaf delegation +const delegationHash = getDelegationHashOffchain(leafDelegation); + +const delegationChain: Delegation[] = await delegationStorageClient.getDelegationChain( + delegationHash +); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + + +## Retrieve delegations for a specific account + +To retrieve delegations stored for a specific account, use the `fetchDelegations` method of the `DelegationStorageClient`. This method allows you to fetch delegations where the specified account is either the delegator or the delegate. +It takes two parameters: + +1. `account` - The address of the account for which you want to retrieve delegations. +2. `filter` - The nature of the delegations. Possible values are: + - `DelegationStoreFilter.Given` - For delegations where the specified `account` is the `delegator`. + - `DelegationStoreFilter.Received` - For delegations where the specified `account` is the `delegate`. + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; + +const address = "0x027aeAFF3E5C33c4018FDD302c20a1B83aDCD96C" + +// Fetch the delegations given by address. +const grantedDelegations = await delegationStorageClient.fetchDelegations( + address, + DelegationStoreFilter.Given, +); + +// Fetch the delegations received by the address. +const receivedDelegations = await delegationStore.fetchDelegations( + address, + DelegationStoreFilter.Received, +); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + diff --git a/delegation-toolkit/get-started/_category_.json b/delegation-toolkit/get-started/_category_.json new file mode 100644 index 00000000000..4a23260d67d --- /dev/null +++ b/delegation-toolkit/get-started/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Get started", + "position": 2, + "link": { + "type": "generated-index", + "slug": "/get-started", + "title": "Get started with the Delegation Toolkit" + } +} diff --git a/delegation-toolkit/get-started/cli-quickstart.md b/delegation-toolkit/get-started/cli-quickstart.md new file mode 100644 index 00000000000..18fef1bcdfd --- /dev/null +++ b/delegation-toolkit/get-started/cli-quickstart.md @@ -0,0 +1,93 @@ +--- +description: Get started with the MetaMask Delegation Toolkit using the `create-gator-app` CLI. +sidebar_position: 3 +sidebar_label: CLI quickstart +--- + +# Delegation Toolkit CLI quickstart + +Use the `create-gator-app` interactive CLI to bootstrap a project with the MetaMask Delegation Toolkit 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 `create-gator-app` package: + +```bash +npx 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) +❯ Basic Delegator app with NextJS + Experimental: Basic Gator app to try out ERC7715 Permissions +? 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 `create-gator-app` CLI. | +| `-h` or `--help` | Display the available options. | +| `--skip-install` | Skip the installation of dependencies. | +| `--add-web3auth` | Add Web3Auth Embedded Wallet as a signer for the delegator account.
Supported templates: | +| `--add-llm-rules` | Add LLM rules for your IDE. Supported templates: | + +## Examples + +### Web3Auth configuration + +To create a project that uses [Web3Auth](https://web3auth.io/docs/) Embedded Wallet as the signer for your delegator account, use the `--add-web3auth` option with `create-gator-app`: + +```bash +npx 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 +``` + +### LLM rules + +To create a project with LLM rules tailored to your preferred IDE, use the `--add-llm-rules` option with `create-gator-app`: + +```bash +npx create-gator-app --add-llm-rules +``` + +You'll be prompted to select your IDE. +Currently, only Cursor and Windsurf are supported. + +```bash +? Which IDE's LLM rules would you like to copy? (Use arrow keys) + Cursor + Windsurf +❯ Both +``` + +## Supported templates + +| Template | Next.js | Vite React | +|----------------------------------------------------|---------|------------| +| Basic dapp with delegation and redeem flow | ✅ | ✅ | +| Experimental: Basic dapp with ERC-7715 permissions | ✅ | | diff --git a/delegation-toolkit/get-started/install.md b/delegation-toolkit/get-started/install.md new file mode 100644 index 00000000000..060d67f0ecd --- /dev/null +++ b/delegation-toolkit/get-started/install.md @@ -0,0 +1,48 @@ +--- +sidebar_label: Install and set up +description: Learn how to install and set up the MetaMask Delegation Toolkit. +sidebar_position: 1 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Install and set up the Delegation Toolkit + +This page provides instructions to install and set up the MetaMask Delegation Toolkit. + +## 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](../how-to/create-delegation/create-custom-caveat-enforcer.md)), install + [Foundry](https://book.getfoundry.sh/getting-started/installation). + +## Steps + +### 1. Install the toolkit + +Install the MetaMask Delegation Toolkit dependencies: + +```bash npm2yarn +npm install @metamask/delegation-toolkit +``` + +### 2. (Optional) Install the contracts + +If you plan to extend the Delegation Framework smart contracts (for example, to +[create a custom caveat enforcer](../how-to/create-delegation/create-custom-caveat-enforcer.md)), 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 MetaMask Delegation Toolkit. +Check out the [Delegation Toolkit quickstart](quickstart.md) to walk through a simple example. \ No newline at end of file diff --git a/delegation-toolkit/get-started/llm-context.md b/delegation-toolkit/get-started/llm-context.md new file mode 100644 index 00000000000..022593c03d6 --- /dev/null +++ b/delegation-toolkit/get-started/llm-context.md @@ -0,0 +1,15 @@ +--- +sidebar_label: LLM context +description: Context about the MetaMask Delegation Toolkit that can be provided to an LLM. +sidebar_position: 4 +sidebar_class_name: hidden +--- + +# Delegation Toolkit LLM context + +This website contains [`LLMs.txt`](/llms.txt) and [`LLMs-full.txt`](/llms-full.txt) files that are intended for use by large language models (LLMs). +They provide information about the content and structure of the MetaMask Delegation Toolkit documentation, +to facilitate better indexing, summarization, and understanding by LLMs. + +You can add these files to an LLM-based tool like [ChatGPT](https://chatgpt.com/) or [Cursor](https://docs.cursor.com/context/@-symbols/@-docs), +to provide detailed context about the Delegation Toolkit. diff --git a/delegation-toolkit/get-started/quickstart.md b/delegation-toolkit/get-started/quickstart.md new file mode 100644 index 00000000000..4141f6fd5c4 --- /dev/null +++ b/delegation-toolkit/get-started/quickstart.md @@ -0,0 +1,172 @@ +--- +description: Get started quickly with the MetaMask Delegation Toolkit. +sidebar_position: 2 +sidebar_label: Quickstart +--- + +# Delegation Toolkit quickstart + +This page demonstrates how to get started quickly with the MetaMask Delegation Toolkit, +by creating a delegator account and completing the delegation lifecycle (creating, signing, and redeeming a delegation). + +## Prerequisites + +[Install and set up the Delegation Toolkit.](install.md) + +## Steps + +### 1. 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 delegator account query the signer's account state and interact with smart contracts. + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const publicClient = createPublicClient({ + chain, + transport: http(), +}); +``` + +### 2. 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 +import { createBundlerClient } from "viem/account-abstraction"; + +const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http("https://your-bundler-rpc.com"), +}); +``` + +### 3. Create a delegator account + +[Create a delegator smart account](../how-to/create-smart-account/index.md) to set up a delegation. + +This example configures a [Hybrid](../how-to/create-smart-account/configure-accounts-signers.md#configure-a-hybrid-smart-account) delegator account: + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; + +const delegatorAccount = privateKeyToAccount("0x..."); + +const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegatorAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegatorAccount }, +}); +``` + +### 4. Create a delegate account + +Create a delegate account to receive the delegation. +The delegate can be either a smart account or an externally owned account (EOA). + +This example uses a smart account: + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; + +const delegateAccount = privateKeyToAccount("0x..."); + +const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + +### 5. Create a delegation + +[Create a root delegation](../how-to/create-delegation/index.md#create-a-root-delegation) from the +delegator account to the delegate account. + +This example passes an empty `caveats` array, which means the delegate can perform any action on the delegator's behalf. +We recommend [restricting the delegation](../how-to/create-delegation/restrict-delegation.md) by adding caveat enforcers. + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +const delegation = createDelegation({ + to: delegateSmartAccount.address, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + +### 6. Sign the delegation + +[Sign the delegation](../how-to/create-delegation/index.md#sign-a-delegation) using the `signDelegation` method from `MetaMaskSmartAccount`. +Alternatively, you can use the Delegation Toolkit's `signDelegation` utility. +The signed delegation will be used later to perform actions on behalf of the delegator. + +```typescript +const signature = await delegatorSmartAccount.signDelegation({ + delegation +}); + +const signedDelegation = { + ...delegation, + signature, +}; +``` + +### 7. Redeem the delegation + +The delegate account can now [redeem the delegation](../how-to/redeem-delegation.md). +The redeem transaction is sent to the `DelegationManager` contract, which validates the delegation and +executes actions on the delegator's behalf. + +To prepare the calldata for the redeem transaction, use the `redeemDelegation` utility function from the Delegation Toolkit. + +```typescript +import { + createExecution, + DelegationFramework, + SINGLE_DEFAULT_MODE, +} from "@metamask/delegation-toolkit"; +import { zeroAddress } from "viem"; + +const delegations = [ signedDelegation ]; + +const executions = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ SINGLE_DEFAULT_MODE ], + executions: [ executions ] +}); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: delegateSmartAccount.address, + data: redeemDelegationCalldata + } + ], + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, +}); +``` diff --git a/delegation-toolkit/how-to/_category_.json b/delegation-toolkit/how-to/_category_.json new file mode 100644 index 00000000000..2b6df81f83d --- /dev/null +++ b/delegation-toolkit/how-to/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "How to", + "position": 4, + "link": { + "type": "generated-index", + "slug": "/how-to", + "title": "Delegation Toolkit how-to guides" + } +} diff --git a/delegation-toolkit/how-to/configure.md b/delegation-toolkit/how-to/configure.md new file mode 100644 index 00000000000..c839d370e7d --- /dev/null +++ b/delegation-toolkit/how-to/configure.md @@ -0,0 +1,61 @@ +--- +description: Learn how to configure the MetaMask Delegation Toolkit using Viem. +sidebar_position: 1 +sidebar_label: Configure the toolkit +--- + +# Configure the Delegation Toolkit + +The MetaMask Delegation Toolkit enables you to easily integrate [smart accounts](../concepts/smart-accounts.md) into your dapp, +enabling a more flexible, secure, and frictionless experience for your users. + +The toolkit is highly configurable, allowing you to tailor it to your project's specific needs. It includes support for custom signers, multiple signatory schemes, custom paymasters and bundlers, and more. + +:::note +The MetaMask Delegation Toolkit provides custom middleware for [Pimlico's](https://docs.pimlico.io/) gas fee resolver, paymaster, and bundler. Additional options will be made available soon. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit](../get-started/install.md). +- Optionally, complete the [Delegation Toolkit quickstart](../get-started/quickstart.md) to + familiarize yourself with the toolkit's capabilities. + +## Viem's Account Abstraction API + +The toolkit uses Viem's Account Abstraction API. This provides a robust and flexible foundation for creating and managing smart contract accounts. +See Viem's [Smart Account documentation](https://viem.sh/account-abstraction/accounts/smart) for more information on the API's features, methods, and best practices. + + +## Configure Viem bundler and paymaster clients + +To use the bundler and paymaster clients with the toolkit, create instances of these clients and configure them as follows: + +```typescript +import { + createPaymasterClient, + createBundlerClient, +} from "viem/account-abstraction"; +import { http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +// 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, +}); +``` + +:::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 sufficient funds to pay for gas fees directly. +::: + diff --git a/delegation-toolkit/how-to/create-delegation/create-custom-caveat-enforcer.md b/delegation-toolkit/how-to/create-delegation/create-custom-caveat-enforcer.md new file mode 100644 index 00000000000..4518fb40d4b --- /dev/null +++ b/delegation-toolkit/how-to/create-delegation/create-custom-caveat-enforcer.md @@ -0,0 +1,149 @@ +--- +description: Learn how to create, deploy, and apply a custom caveat enforcer +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a custom caveat enforcer + +When [restricting a delegation](restrict-delegation.md), the MetaMask Delegation Toolkit provides some [out-of-the-box caveat enforcers](../../reference/caveats.md) +that cover common use cases. +For more granular or custom control, you can follow the instructions on this page to create custom caveat enforcers from scratch. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../get-started/install.md) +- [Configure the Delegation Toolkit.](../configure.md) + +## Steps + +### 1. Create the caveat enforcer + +Create a contract that extends the +[`ICaveatEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/interfaces/ICaveatEnforcer.sol) +interface. + +For example, the following is a simple caveat enforcer that only allows a delegation to be redeemed after a specific timestamp. + +```solidity title="AfterTimestampEnforcer.sol" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import { CaveatEnforcer } from "@delegator/src/enforcers/CaveatEnforcer.sol"; +import { ModeCode } from "../utils/Types.sol"; + +contract AfterTimestampEnforcer is CaveatEnforcer { + /** + * @notice The delegation may only be redeemed after the specified timestamp - validAfter in seconds. + * @param _terms The validAfter, timestamp in seconds after which the delegation may be redeemed. + * @param _delegationHash - The hash of the delegation being operated on. + */ + function beforeHook( + bytes calldata _terms, + bytes calldata, + ModeCode, + bytes calldata, + bytes32 _delegationHash, + address, + address _redeemer + ) + public + override + { + // Enforces the conditions that should hold before a transaction is performed. + // This function MUST revert if the conditions are not met. + // Get the current timestamp + uint256 timestamp = block.timestamp; + + uint256 validAfter = uint256(bytes32(_terms)); + + require(timestamp > validAfter, "AfterTimestampEnforcer:cannot-redeem-too-early"); + } +} +``` + +### 2. Deploy the caveat enforcer + +Deploy your custom caveat enforcer to obtain its contract address. +For example, you can [deploy your smart contract using Forge](https://book.getfoundry.sh/forge/deploying). + +### 3. Apply the caveat enforcer + +When creating a delegation, add the `Caveat` for the custom caveat to the `CaveatBuilder`. +Learn more about [applying caveats to a delegation](restrict-delegation.md). + +The following example uses the custom `AfterTimestampEnforcer.sol` caveat enforcer to create a delegation granting +an allowance of 1,000,000 wei that can only be spent after one hour from when the delegation is created. + +:::warning Important +Depending on the use case, it may be necessary to add additional caveats to restrict the delegation further. +::: + + + + +```typescript +import { + createCaveatBuilder, + createDelegation, +} from "@metamask/delegation-toolkit"; +import { toHex } from "viem"; +import { delegatorSmartAccount } from "./config.ts"; + +const environment = delegatorSmartAccount.enviroment; + +// Replace this with the address where the AfterTimestampEnforcer.sol contract is deployed. +const afterTimestampEnforcer = "0x22Ae4c4919C3aB4B5FC309713Bf707569B74876F"; + +const caveatBuilder = createCaveatBuilder(environment); + +const tenAM = 10 * 60 * 60; // 10:00 AM as seconds since midnight. + +const caveats = caveatBuilder + .addCaveat("nativeTokenTransferAmount", 1_000_000) + .addCaveat({ + enforcer: afterTimestampEnforcer, + terms: toHex(tenAm) + }); + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats +}); +``` + + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + diff --git a/delegation-toolkit/how-to/create-delegation/index.md b/delegation-toolkit/how-to/create-delegation/index.md new file mode 100644 index 00000000000..e39ccf8de8a --- /dev/null +++ b/delegation-toolkit/how-to/create-delegation/index.md @@ -0,0 +1,349 @@ +--- +description: Learn how to create different types of delegations, and how to sign a delegation. +sidebar_position: 6 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a delegation + +The MetaMask Delegation Toolkit enables you to create [delegations](../../concepts/delegation.md) +from a delegator account to a delegate account. + +:::note +Delegations are compatible with [ERC-7710](https://eip.tools/eip/7710) and [ERC-7715](https://ethereum-magicians.org/t/erc-7715-grant-permissions-from-wallets/20100), to support a standardized minimal interface. +[Requesting ERC-7715 permissions](../../experimental/erc-7715-request-permissions.md) and [redeeming ERC-7710 delegations](../../experimental/erc-7710-redeem-delegations.md) +are experimental features. +::: + +:::warning +The examples on this page demonstrate delegations without any restrictions. +Unrestricted delegations grant complete control over the account to the delegate, which can pose significant security risks. +It is crucial to add caveats to limit the delegated authority. +Learn how to [restrict a delegation](./restrict-delegation.md) using caveat enforcers. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../get-started/install.md) +- [Configure the Delegation Toolkit.](../configure.md) +- [Create a smart account.](../create-smart-account/index.md) + +## Create a root delegation + +A *root delegation* is a delegation that doesn't derive its authority from another delegation. +It is when a delegator delegates its own authority away, as opposed to a [redelegation](#create-a-redelegation). +Create a root delegation as follows: + + + + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.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: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create an 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. +You must create open root delegations carefully, to ensure that they are not misused. +Create an open root delegation by setting the delegate property to the special address +`0x0000000000000000000000000000000000000a11` (available via the constant `ANY_BENEFICIARY`). + + + + +```typescript +import { createOpenDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +const openRootDelegation = createOpenDelegation({ + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create a redelegation + +A recipient of a delegation (the delegate), can *redelegate* that authority to a third party, potentially applying additional [restrictions](restrict-delegation.md). +Create a redelegation as follows: + + + + +```typescript +import { + createDelegation, + getDelegationHashOffchain + } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +// The address is used as the root delegator. While creating the redelegation, +// the root delegate address will be the delegator address. +const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +// The authority is the (typed data) hash of the delegation from which the authority is derived. +const parentDelegationHash = getDelegationHashOffchain(delegation); + +// The address is used as the delegate address while creating the redelegation. +const leafDelegate = "0xb4821Ab7d5942Bd2533387592068a12608B4a52C" + +const leafDelegation = createDelegation({ + to: leafDelegate, + from: delegate, + // You can also choose to pass the parent delegation object, and let function + // handle the rest. + parentDelegation: parentDelegationHash, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create an open redelegation + +An *open redelegation* is a [redelegation](#create-a-redelegation) that doesn't specify a delegate. +This means that any account can redeem the redelegation. +As with [open root delegations](#create-an-open-root-delegation), you must create open redelegations carefully, +to ensure that they are not misused. +Create an open redelegation as follows: + + + + +```typescript +import { + createDelegation, + createOpenDelegation, + getDelegationHashOffchain + } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +// The address is used as the root delegator. While creating the redelegation, +// the root delegate address will be the delegator address. +const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +// The authority is the (typed data) hash of the delegation from which the authority is derived. +const parentDelegationHash = getDelegationHashOffchain(delegation); + +const leafDelegation = createOpenDelegation({ + from: delegate, + // You can also choose to pass the parent delegation object, and let the function + // handle the rest. + parentDelegation: parentDelegationHash, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Sign a delegation + +A delegation must be signed by the delegator to be valid for redemption. The `MetaMaskSmartAccount` supports signing the delegation using [EIP-712 Typed Data](https://eips.ethereum.org/EIPS/eip-712) via the `signDelegation` method. +Sign a delegation as follows: + + + + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.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: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +const signature = await delegatorSmartAccount.signDelegation({ delegation }); + +const signedDelegation = { + ...delegation, + signature +}; +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); + +``` + + + \ No newline at end of file diff --git a/delegation-toolkit/how-to/create-delegation/restrict-delegation.md b/delegation-toolkit/how-to/create-delegation/restrict-delegation.md new file mode 100644 index 00000000000..7e1ff127310 --- /dev/null +++ b/delegation-toolkit/how-to/create-delegation/restrict-delegation.md @@ -0,0 +1,109 @@ +--- +description: Learn how to restrict a delegation using caveat enforcers, and the available caveat types. +sidebar_position: 1 +toc_max_heading_level: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Restrict a delegation + +Use [caveat enforcers](../../concepts/caveat-enforcers.md) to apply specific rules and restrictions +to a delegation, ensuring that delegated executions are only performed under predefined circumstances. + +A delegation has a `caveats` property, which is an array of `Caveat` objects. +Each caveat is specified as follows: + +```typescript +export type Caveat = { + enforcer: Hex; // The address of the caveat enforcer contract. + terms: Hex; // Data passed to the caveat enforcer, describing how the redemption should be validated. + args: Hex; // Data that may be specified by the redeemer when redeeming the delegation (only used in limited cases). +}; +``` + +The MetaMask Delegation Toolkit provides a `CaveatBuilder` interface, which offers an intuitive way to define the `caveats` array. +Use the `CaveatBuilder` to easily ensure that your delegations grant only the necessary authority. + +## Create the caveat builder + +To create the caveat builder, call the `createCaveatBuilder()` function, passing an instance of `DeleGatorEnvironment`. +The environment can be accessed from the `MetaMaskSmartAccount`, as in this example: + +```typescript +const environment = delegatorSmartAccount.environment; + +const caveatBuilder = createCaveatBuilder(environment); +``` + +:::note +By default, the `CaveatBuilder` does not allow empty caveats. To allow the `CaveatBuilder` to build an empty caveats array, provide the following configuration: + +```typescript +const caveatBuilder = createCaveatBuilder(environment, { allowEmptyCaveats: true }); +``` +::: + +## Add caveats to the builder + +Add caveats to the builder using the `addCaveat` method, specifying the [caveat type](../../reference/caveats.md) and its parameters. You can chain multiple calls to `addCaveat` as in the following example: + +```typescript +const caveats = caveatBuilder + // allowedTargets accepts an array of addresses. + // This caveat restricts the caller to only use the delegation to interact with the specified address. + .addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92"]) + // allowedMethods accepts an array of methods. + // This caveat restricts the caller to only use the delegation to invoke the specified methods. + .addCaveat("allowedMethods", [ + "approve(address,uint256)", + "transfer(address,uint256)" + ]) + // limitedCalls accepts a number. + // This caveat restricts the caller to only use the delegation one time. + .addCaveat("limitedCalls", 1) + .build(); +``` + +
+ Important considerations when using caveat enforcers +
+ + - Delegations without caveats are entirely permissive. + It is crucial to add appropriate caveats to restrict the delegated authority sufficiently. + Failing to do so could result in unintended access or actions. + - Caveat enforcers safeguard the execution process but do not guarantee a final state post-redemption. + Always combine caveat enforcers thoughtfully to create comprehensive protection. + - When using multiple caveat enforcers that modify external contract states, the order matters. + For example, if you include both [`NativeBalanceChangeEnforcer`](../../reference/caveats.md#nativebalancechange) to ensure a balance has increased and + [`NativeTokenPaymentEnforcer`](../../reference/caveats.md#nativetokenpayment) to deduct from that balance, + executing `NativeTokenPaymentEnforcer` first might cause `NativeBalanceChangeEnforcer` to fail validation. + Consider the sequence of enforcers carefully when creating delegations with interdependent caveats. + +
+
+ +For convenience, you can also pass the `CaveatBuilder` directly to the various helper methods for creating a delegation. For example: + +```typescript +const caveats = caveatBuilder + // allowedTargets accepts an array of addresses. + .addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92"]) + // allowedMethods accepts an array of methods. + .addCaveat("allowedMethods", [ + "approve(address,uint256)", + "transfer(address,uint256)" + ]) + // limitedCalls accepts a number. + .addCaveat("limitedCalls", 1); + +const delegation = createDelegation({ + to: delegate, + from: delegator, + caveats +}); +``` + +For more granular or custom control, you can also [create custom caveat enforcers](create-custom-caveat-enforcer.md) +and add them to the caveat builder. diff --git a/delegation-toolkit/how-to/create-smart-account/configure-accounts-signers.md b/delegation-toolkit/how-to/create-smart-account/configure-accounts-signers.md new file mode 100644 index 00000000000..d37800179d3 --- /dev/null +++ b/delegation-toolkit/how-to/create-smart-account/configure-accounts-signers.md @@ -0,0 +1,303 @@ +--- +sidebar_label: Configure accounts and signers +description: Learn how to configure different types of delegator accounts and signers using Viem. +sidebar_position: 1 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Configure smart accounts and signers + +The MetaMask Delegation Toolkit supports different [smart account types](../../concepts/smart-accounts.md#smart-account-implementation-types), +each with its own configuration and support for different signing mechanisms. +You can create flexible and secure delegator accounts tailored to your specific needs. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../get-started/install.md) +- [Configure the Delegation Toolkit.](../configure.md) +- [Create a smart account.](index.md) + +## Configure a Hybrid smart account + +The [Hybrid smart account](../../concepts/smart-accounts.md#hybrid-smart-account) supports both an EOA "owner" and any number of P256 (passkey) signers. + +To configure a Hybrid smart account, provide the following parameters: + +- `owner`: The owner's account address as a hex string. + The owner can be the zero address, indicating that there is no owner configured. +- `p256KeyIds`: An array of key identifiers for P256 signers as hex strings. +- `p256XValues`: An array of public key x-values for P256 signers as `bigint`s. +- `p256YValues`: An array of public key y-values for P256 signers as `bigint`s. +- `signatory`: A signer that will sign on behalf of the smart account. + +:::note +You can set all `p256` parameters to empty arrays to configure no WebAuthn signer. +However, we recommend configuring at least one signer for account recoverability. +::: + +For a Hybrid smart account, you can configure the following types of signatories: + +### Configure an account signatory + +This example creates a signatory from a private key using Viem's [`privateKeyToAccount`](https://viem.sh/docs/accounts/local/privateKeyToAccount) function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const signatory = { account }; +``` + + + + +### Configure a Wallet Client signatory + +This example creates a [Viem Wallet Client](https://viem.sh/docs/clients/wallet) as the signatory, +using Viem's `createWalletClient` function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient } from "viem"; + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +const walletClient = createWalletClient({ + account, + chain, + transport: http() +}) + +export const signatory = { walletClient }; +``` + + + + +### Configure a WebAuthn (passkey) signatory + +This example creates a [Viem WebAuthn Account](https://viem.sh/account-abstraction/accounts/webauthn) as the signatory, +using Viem's `toWebAuthnAccount` function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { createCredential, parsePublicKey } from "webauthn-p256"; +import { toWebAuthnAccount } from "viem/account-abstraction"; +import { toHex } from "viem"; + +const credential = await createCredential({ name: "Your Delegator Passkey" }); +const webAuthnAccount = toWebAuthnAccount({ credential }); +const keyId = toHex("my-key-id"); + +const signatory = { webAuthnAccount, keyId }; +``` + + + + + +## Configure a Multisig smart account + +The [Multisig smart account](../../concepts/smart-accounts.md#multisig-smart-account) supports multiple EOA signers with a configurable threshold for execution. + +To configure a Multisig smart account, provide the following parameters: + +- `signers`: An array of EOA signer addresses as hex strings. +- `threshold`: The number of signers required to execute a transaction, as a `bigint`. +- `signatory`: An array of signatories that will sign on behalf of the smart account. + +### Configure signatories + +For a Multisig smart account, you can use a combination of account signatories and Wallet Client signatories. +For example: + + + + +```typescript +import { publicClient } from "./client.ts"; +import { account, walletClient } from "./signers.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const signers = [ account.address, walletClient.address ]; +const signatory = [ { account }, { walletClient } ]; +const threshold = 2n + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.MultiSig, + deployParams: [signers, threshold], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient } from "viem"; + +// 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. +::: diff --git a/delegation-toolkit/how-to/create-smart-account/index.md b/delegation-toolkit/how-to/create-smart-account/index.md new file mode 100644 index 00000000000..596db4a6bfe --- /dev/null +++ b/delegation-toolkit/how-to/create-smart-account/index.md @@ -0,0 +1,82 @@ +--- +description: Learn how to create a delegator account using Viem. +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a smart account + +The MetaMask Delegation Toolkit is embedded, meaning that the end user can instantly interact with a dapp without wallet authorization, confirmations, or corporate logos. +Enable users to create a [smart account](../concepts/smart-accounts) directly in your dapp. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../get-started/install.md) +- [Configure the Delegation Toolkit.](../configure.md) + +## Create a `MetaMaskSmartAccount` + +The following is an example of creating a smart account using Viem Core SDK. +Viem Core SDK provides low-level interfaces to offer flexibility and control over the smart +account creation lifecycle. + +In the example, the Viem [`privateKeyToAccount`](https://viem.sh/docs/accounts/privateKey.html) +function creates an externally owned account as the owner of the smart account. + + + + +```typescript +import { publicClient, owner } from "./config.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const deploySalt = "0x"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner.address, [], [], []], + deploySalt, + signatory: { account: owner }, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); + +const privateKey = generatePrivateKey(); +export const owner = privateKeyToAccount(privateKey); +``` + + + + + +This example creates the `MetaMaskSmartAccount`, which can perform several functions: + +- In conjunction with [Viem Account Abstraction clients](../configure.md), deploy the smart account + and [send user operations](../send-user-operation.md). +- [Sign delegations](../create-delegation/index.md) that can be used to grant specific rights and permissions to other accounts. + Smart accounts that sign delegations are called *delegator accounts*. + +:::note +The example above uses a Hybrid smart account, which is configurable to have an EOA "owner" and any number of P256 (passkey) signers. +You can also [configure other smart account types](configure-accounts-signers.md). +::: diff --git a/delegation-toolkit/how-to/generate-multisig-signature.md b/delegation-toolkit/how-to/generate-multisig-signature.md new file mode 100644 index 00000000000..107e1634a98 --- /dev/null +++ b/delegation-toolkit/how-to/generate-multisig-signature.md @@ -0,0 +1,123 @@ +--- +description: Learn how to generate a Multisig signature. +sidebar_position: 5 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Generate a multisig signature + +The MetaMask Delegation Toolkit supports [Multisig smart accounts](../concepts/smart-accounts.md#multisig-smart-account), +allowing you to add multiple externally owned account (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` function to combine them +into a single aggregated signature. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a Multisig smart account.](create-smart-account/configure-accounts-signers.md#configure-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 +import { + bundlerClient, + aliceSmartAccount, + bobSmartAccount, + aliceAccount, + bobAccount, +} from "./config.ts"; +import { aggregateSignature } from "@metamask/delegation-toolkit"; + +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 +import { createPublicClient, http } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { createBundlerClient } from "viem/account-abstraction"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +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", + signatory: [ { account: aliceAccount } ], +}); + +export const bobSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.MultiSig, + deployParams: [signers, threshold], + deploySalt: "0x", + signatory: [ { account: bobAccount } ], +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http("https://public.pimlico.io/v2/rpc") +}); +``` + + + + + diff --git a/delegation-toolkit/how-to/redeem-delegation.md b/delegation-toolkit/how-to/redeem-delegation.md new file mode 100644 index 00000000000..f30f840a628 --- /dev/null +++ b/delegation-toolkit/how-to/redeem-delegation.md @@ -0,0 +1,349 @@ +--- +description: Learn how to redeem a delegation with a smart contract account (SCA) or an externally owned account (EOA). +sidebar_position: 7 +toc_max_heading_level: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Redeem a delegation + +A delegate can redeem a delegation by submitting either a user operation or a regular transaction, +depending on whether the delegate is a smart account or externally owned account (EOA). + +The redeem transaction is sent to the `DelegationManager` contract, which validates the delegation and executes actions on the delegator's behalf. +To prepare the calldata for the redeem transaction, use the `redeemDelegation` utility function. +The function supports batch redemption, allowing multiple delegations to be processed within a single transaction. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator smart account.](create-smart-account/index.md) +- [Create a delegation.](create-delegation/index.md) + +## Redeem a delegation + +Redeem a delegation with a [smart account](#redeem-with-a-smart-account) or an [externally owned account (EOA)](#redeem-with-an-eoa). + +### Redeem with a smart account + +The following example demonstrates how to submit a user operation to redeem a delegation. +It assumes you have a delegation signed by the delegator, and that the delegate is a smart account. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { bundlerClient, pimlicoClient } from "./client.ts"; +import { delegateSmartAccount } from "./account.ts"; + +const delegations: Delegation[] = [ signedDelegation ]; + +// SINGLE_DEFAULT_MODE is the default execution mode. +const mode: ExecutionMode = SINGLE_DEFAULT_MODE; + +// For SINGLE execution modes, the executions array must be length 1. +const executions: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const { fast: fee } = await pimlicoClient.getUserOperationGasPrice(); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: "", + data: redeemDelegationCalldata + } + ], + ...fee +}); +``` + + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +improt { publicClient } from "./client.ts" + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { createPimlicoClient } from "permissionless/clients/pimlico"; + +// You can get the API Key from the Pimlico dashboard. +const pimlicoUrl = "https://api.pimlico.io/v2/59141/rpc"; + +export const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http(pimlicoUrl), +}); + +export const pimlicoClient = createPimlicoClient({ + transport: http(pimlicoUrl), +}); +``` + + + + +### Redeem with an EOA + +The following example demonstrates how to submit a transaction to redeem a delegation. It assumes you have a delegation signed by the delegator, and that the delegate is an EOA. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { lineaSepolia as chain } from "viem/chains"; +import { delegateWalletClient } from "./account.ts"; + +const delegations: Delegation[] = [ signedDelegation ]; + +// SINGLE_DEFAULT_MODE is the default execution mode. +const mode: ExecutionMode = SINGLE_DEFAULT_MODE; + +// For SINGLE execution modes, the executions array must be length 1. +// Modify the executions to fit your use case. +const executions: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const transactionHash = await walletClient.sendTransaction({ + to: getDeleGatorEnvironment(chain.id).DelegationManager, + data: redeemDelegationCalldata, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { createWalletClient, http } from "viem"; + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateWalletClient = createWalletClient({ + account: delegateAccount, + chain, + transport: http(), +}) +``` + + + + +## Redeem multiple delegations + +You can redeem multiple delegations in a single user operation, each delegation independent of the others. +Each element in the `delegationsArray` must have a corresponding element in the `executionsArray` and `modes`. + +The following example assumes you already have multiple signed delegations and that the delegate is a smart account. +The preparation of the calldata is the same when [using an EOA as the delegate](#redeem-with-an-eoa); +the primary difference is that an EOA submits a regular transaction instead of a user operation. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { bundlerClient, pimlicoClient } from "./client.ts"; +import { delegateSmartAccount } from "./account.ts"; + +const delegationsArray: Delegation[][] = [ + [ signedDelegation1 ] + [ signedDelegation2 ] + [ signedDelegation3 ] +]; + +const modes: ExecutionMode = [ + SINGLE_DEFAULT_MODE, + SINGLE_DEFAULT_MODE, + SINGLE_DEFAULT_MODE +]; + +const execution: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +// Modify the executions to fit your use case. For simplicity, we've +// included a basic example. The execution array can contain +// multiple different executions. +const executionsArray: ExecutionStruct:[][] = [ + execution, + execution, + execution +]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const { fast: fee } = await pimlicoClient.getUserOperationGasPrice(); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: "", + data: redeemDelegationCalldata + } + ], + ...fee +}); +``` + + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +import { publicClient } from "./client.ts"; + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { createPimlicoClient } from "permissionless/clients/pimlico"; + +// You can get the API Key from the Pimlico dashboard. +const pimlicoUrl = "https://api.pimlico.io/v2/59141/rpc"; + +export const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http(pimlicoUrl), +}); + +export const pimlicoClient = createPimlicoClient({ + transport: http(pimlicoUrl), +}); +``` + + + + +## Execution modes + +The Delegation Toolkit supports several execution modes based on [ERC-7579](https://erc7579.com/). +See the [ERC implementation](https://github.com/erc7579/erc7579-implementation/blob/main/src/lib/ModeLib.sol) for more details about the execution modes. + +The supported execution modes are `SINGLE_DEFAULT_MODE`, `SINGLE_TRY_MODE`, `BATCH_DEFAULT_MODE`, and `BATCH_TRY_MODE`. + +### `SINGLE` execution modes + +In `SINGLE` execution modes, only a single delegation chain and a single execution can be provided. This mode processes delegations sequentially: + +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. + +### `BATCH` execution modes + +In `BATCH` execution modes, multiple delegation chains and multiple executions can be provided. This mode executes delegations in an interleaved way: + +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. + +### `DEFAULT` modes + +In `DEFAULT` modes, if a revert occurs during redemption, the entire user operation reverts at that point. + +### `TRY` modes + +In `TRY` modes, if a revert occurs during redemption, execution of the user operation continues. diff --git a/delegation-toolkit/how-to/send-user-operation.md b/delegation-toolkit/how-to/send-user-operation.md new file mode 100644 index 00000000000..99ba5c6064a --- /dev/null +++ b/delegation-toolkit/how-to/send-user-operation.md @@ -0,0 +1,180 @@ +--- +description: Learn how to send an ERC-4337 user operation using Viem. +sidebar_position: 4 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# 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 Delegation Toolkit 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 smart contract account that has not been deployed, the toolkit configures the user operation to automatically deploy the account. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a smart account.](create-smart-account/index.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 +import { bundlerClient, smartAccount } from "./config.ts"; +import { parseEther } from "viem"; + +// 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 +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { createBundlerClient } from "viem/account-abstraction"; +import { lineaSepolia as chain } from "viem/chains"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + + +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", + signatory: { account }, +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http("https://public.pimlico.io/v2/1/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). + +```typescript title="example.ts" +// add-next-line ++ import { createPimlicoClient } from "permissionless/clients/pimlico"; +import { parseEther } from "viem"; +import { bundlerClient, smartAccount } from "./config.ts" // The config.ts is the same as in the previous example. + +// remove-start +- const maxFeePerGas = 1n; +- const maxPriorityFeePerGas = 1n; +// remove-end + +// add-start ++ const pimlicoClient = createPimlicoClient({ ++ transport: http("https://api.pimlico.io/v2/59141/rpc"), // 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" +import { createPimlicoClient } from "permissionless/clients/pimlico"; +import { bundlerClient, smartAccount } from "./config.ts" // The config.ts is the same as in the previous example. + +const pimlicoClient = createPimlicoClient({ + transport: http("https://api.pimlico.io/v2/59141/rpc"), // 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 +``` diff --git a/delegation-toolkit/index.md b/delegation-toolkit/index.md new file mode 100644 index 00000000000..4f5f232255b --- /dev/null +++ b/delegation-toolkit/index.md @@ -0,0 +1,70 @@ +--- +title: Introduction to the MetaMask Delegation Toolkit +sidebar_label: Introduction +description: High-level overview of the Delegation Toolkit, its benefits, and where to start in the documentation. +sidebar_position: 1 +--- + +import CardList from "@site/src/components/CardList" + +# MetaMask Delegation Toolkit + +The MetaMask Delegation Toolkit is a [Viem](https://viem.sh/)-based collection of tools for integrating +embedded smart contract wallets, known as [MetaMask smart accounts](concepts/smart-accounts.md), +into dapps. Developers can create and manage MetaMask smart accounts that delegate specific +permissions, such as spending limits or time-based access, to other accounts. + +At the core of the toolkit is the [Delegation Framework](concepts/delegation.md#delegation-framework), a +set of open-source, audited smart contracts that manage the delegation lifecycle. + +Permissions are enforced through [caveats](concepts/caveat-enforcers.md), which are rule-based +constraints that define the conditions of a delegation. The toolkit includes +[built-in caveat enforcers](reference/caveats.md) for common +use cases. It also supports [custom caveat enforcers](how-to/create-delegation/create-custom-caveat-enforcer.md) +for advanced scenarios. + +## Why use the toolkit? + +The toolkit enables developers to create frictionless new experiences based on granular permission +sharing and trust. The toolkit offers a suite of contracts, libraries, and services designed for +maximum composability, allowing developers to build and extend their dapps with ease. + +The toolkit enables: + +- **Instant user onboarding.** Provide frictionless onboarding with no browser extension, mobile + app, or seed phrase required. + +- **New web3 experiences.** Unlock new experiences such as peer-to-peer social + coordination using incentive trees, or recurring subscription payments that don't require users + to connect to the dapp. + +- **Uninterrupted user experiences.** Keep users immersed in the dapp by embedding the wallet + experience and reassigning gas costs to where they make sense. + +## Where do I start? + +Check out the following sections to get started with the MetaMask Delegation Toolkit: + + + +## Questions? + +If you have questions, email hellogators@consensys.net. diff --git a/delegation-toolkit/reference/_category_.json b/delegation-toolkit/reference/_category_.json new file mode 100644 index 00000000000..7695e71b725 --- /dev/null +++ b/delegation-toolkit/reference/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Reference", + "position": 6, + "link": { + "type": "generated-index", + "slug": "/reference", + "title": "Delegation Toolkit reference" + } + } + \ No newline at end of file diff --git a/delegation-toolkit/reference/api/_category_.json b/delegation-toolkit/reference/api/_category_.json new file mode 100644 index 00000000000..29f2417814a --- /dev/null +++ b/delegation-toolkit/reference/api/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "Delegation Toolkit API", + "position": 2, + "link": { + "type": "generated-index", + "slug": "/reference/api", + "title": "Delegation Toolkit API reference" + } + } + \ No newline at end of file diff --git a/delegation-toolkit/reference/api/delegation.md b/delegation-toolkit/reference/api/delegation.md new file mode 100644 index 00000000000..70f990a6eb1 --- /dev/null +++ b/delegation-toolkit/reference/api/delegation.md @@ -0,0 +1,364 @@ +--- +description: Delegation-related API methods reference. +sidebar_label: Delegation +sidebar_position: 1 +toc_max_heading_level: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Delegation API reference + +The following API methods are related to creating and managing [delegations](../../concepts/delegation.md). + +## `createCaveatBuilder` + +Builds an array of caveats. + +### Parameters + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ----------- | +| `environment` | `DeleGatorEnvironment` | Yes | Environment to resolve the smart contracts for the current chain. | +| `config` | `CaveatBuilderConfig` | No | Configuration for `CaveatBuilder`. | + +### Example + + + + +```ts +import { createCaveatBuilder } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +const caveats = createCaveatBuilder(delegatorSmartAccount.environment) +``` + + + + +```ts +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { createWalletClient, createPublicClient, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; + +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", + signatory: { account: delegatorAccount }, +}); +``` + + + + +### Allow empty caveats + +To create an empty caveat collection, set the `CaveatBuilderConfig.allowEmptyCaveats` to `true`. + +```ts title="example.ts" +import { createCaveatBuilder } from "@metamask/delegation-toolkit"; +// The config.ts is the same as in the previous example. +import { delegatorSmartAccount } from "./config.ts"; + +const caveats = createCaveatBuilder(delegatorSmartAccount.environment, { + // add-next-line + allowEmptyCaveats: true +}) +``` + +## `createExecution` + +Creates an `ExecutionStruct` instance. + +### Parameters + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ----------- | +| `target` | `Hex` | 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 +import { createExecution } from "@metamask/delegation-toolkit"; + +// Creates an ExecutionStruct to transfer 0.01 ETH to +// 0xe3C818389583fDD5cAC32f548140fE26BcEaE907 address. +const caveats = createExecution( + "0xe3C818389583fDD5cAC32f548140fE26BcEaE907", + // 0.01 ETH in wei + 10000000000000000n, + "0x", +); +``` + +## `deployDeleGatorEnvironment` + +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 +import { deployDeleGatorEnvironment } from "@metamask/delegation-toolkit"; +import { walletClient, publicClient } from "./config.ts"; +import { lineaSepolia as chain } from "viem/chains"; + +const environment = await deployDeleGatorEnvironment( + walletClient, + publicClient, + chain +); +``` + + + + +```ts +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient, createPublicClient } from "viem"; + +// 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" +import { walletClient, publicClient } from "./config.ts"; +import { lineaSepolia as chain } from "viem/chains"; +import { + DeleGatorEnvironment, + overrideDeployedEnvironment, + deployDeleGatorEnvironment +} from "@metamask/delegation-toolkit"; + +const environment: DeleGatorEnvironment = await deployDeleGatorEnvironment( + walletClient, + publicClient, + chain +); + +// add-start +overrideDeployedEnvironment( + chain.id, + "1.3.0", + environment, +); +// add-end +``` + +## `getDeleGatorEnvironment` + +Resolves the `DeleGatorEnvironment` for a chain. + +### Parameters + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ----------- | +| `chainId` | `number` | Yes | The chain ID of the network for which the `DeleGatorEnvironment` 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 +import { getDeleGatorEnvironment } from "@metamask/delegation-toolkit"; +import { sepolia } from "viem/chains"; + +const environment = getDeleGatorEnvironment(sepolia.id) +``` + +## `overrideDeployedEnvironment` + +Overrides or adds the `DeleGatorEnvironment` for a chain and supported version. + +### Parameters + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ----------- | +| `chainId` | `number` | Yes | The chain ID of the network for which the `DeleGatorEnvironment` should be overridden. | +| `version` | `SupportedVersion` | Yes | The version of the Delegation Framework contracts to override for the specified chain. | +| `environment` | `DeleGatorEnvironment` | Yes | The environment containing contract addresses to override for the given chain and version. | + +### Example + + + + +```ts +import { environment } from "./environment.ts"; +import { getDeleGatorEnvironment } from "@metamask/delegation-toolkit"; +import { sepolia } from "viem/chains"; + +overrideDeployedEnvironment( + sepolia.id, + "1.3.0", + environment +); +``` + + + + +```ts +import { DeleGatorEnvironment } from "@metamask/delegation-toolkit"; + +export const environment: DeleGatorEnvironment = { + SimpleFactory: "0x124..", + // ... + implementations: { + // ... + }, +}; +``` + + + + +## `redeemDelegation` + +Encodes calldata for redeeming delegations. + +### 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 for each corresponding delegation chain. | +| `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 +import { + createExecution, + DelegationFramework, +} from "@metamask/delegation-toolkit"; + +const execution = createExecution(); +const data = DelegationFramework.encode.redeemDelegations({ + delegations: [[ signedDelegation ]], + modes: [ SINGLE_DEFAULT_MODE ], + executions: [[ execution ]], +}); +``` + +## `signDelegation` + +Signs the delegation and returns the delegation signature. + +### Parameters + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ----------- | +| `signer` | `WalletClient` | Yes | [Viem Wallet Client](https://viem.sh/docs/clients/wallet#wallet-client) to sign 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`. | + +### Example + + + + +```ts +import { signDelegation } from "@metamask/delegation-toolkit"; +import { walletClient, delegation, delegationManager } from "./config.ts"; +import { sepolia } from "viem/chains"; + +const signature = signDelegation({ + signer: walletClient, + delegation, + chainId: sepolia.id, + delegationManager, +}) +``` + + + + +```ts +import { + getDeleGatorEnvironment, + createDelegation, +} from "@metamask/delegation-toolkit"; +import { createWalletClient } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { sepolia } from "viem/chains"; + +export const delegationManager = getDeleGatorEnvironment( + sepolia.id +).DelegationManager; + +const account = privateKeyToAccount(delegateWallet as `0x${string}`); + +export const walletClient = createWalletClient({ + account, + transport: http(), + chain: sepolia, +}); + +// 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, + // Empty caveats array - we recommend adding appropriate restrictions. + caveats: [], +}); +``` + + + diff --git a/delegation-toolkit/reference/api/experimental-actions/_category_.json b/delegation-toolkit/reference/api/experimental-actions/_category_.json new file mode 100644 index 00000000000..3cc396c79c1 --- /dev/null +++ b/delegation-toolkit/reference/api/experimental-actions/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Experimental actions", + "position": 3, + "link": { + "type": "generated-index", + "slug": "/reference/api/experimental-actions", + "title": "Delegation Toolkit experimental actions API reference" + } +} \ No newline at end of file diff --git a/delegation-toolkit/reference/api/experimental-actions/bundler-client.md b/delegation-toolkit/reference/api/experimental-actions/bundler-client.md new file mode 100644 index 00000000000..cdb90492940 --- /dev/null +++ b/delegation-toolkit/reference/api/experimental-actions/bundler-client.md @@ -0,0 +1,108 @@ +--- +description: Bundler Client API methods reference. +sidebar_label: Bundler Client +sidebar_position: 1 +toc_max_heading_level: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Bundler Client API reference + +The following API methods are related to the [Viem Bundler Client](https://viem.sh/account-abstraction/clients/bundler). + +## `sendUserOperationWithDelegation` + +Sends a user operation to redeem delegated 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` | `0x${string}` | Yes | The address of Delegation Manager. | +| `permissionsContext` | `0x${string}` | Yes | Encoded calldata for redeeming delegations. If you're not using ERC-7715, you can use the [`redeemDelegation`](../delegation.md#redeemdelegation) utility function to generate the calldata manually. | + +### Example + + + + +```ts +import { sessionAccount, bundlerClient, publicClient } from "./config.ts"; + +// These properties must be extracted from the permission response. +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +const accountMetadata = permissionsResponse[0].accountMeta; + +// 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 + accountMetadata, +}); +``` + + + + +```ts +import { createPublicClient, http, createBundlerClient } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { erc7710BundlerActions } from "@metamask/delegation-toolkit/experimental"; +import { toMetaMaskSmartAccount, Implementation } from "@metamask/delegation-toolkit"; + +export const publicClient = createPublicClient({ + chain: chain, + transport: http(), +}); + +// Your session account for requesting and redeeming should be same. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + +export const sessionAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { 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()); +``` + + + diff --git a/delegation-toolkit/reference/api/experimental-actions/wallet-client.md b/delegation-toolkit/reference/api/experimental-actions/wallet-client.md new file mode 100644 index 00000000000..622ddee14d0 --- /dev/null +++ b/delegation-toolkit/reference/api/experimental-actions/wallet-client.md @@ -0,0 +1,170 @@ +--- +description: Wallet Client API methods reference. +sidebar_label: Wallet Client +sidebar_position: 2 +toc_max_heading_level: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Wallet Client API reference + +The following API methods are related to the [Viem Wallet Client](https://viem.sh/docs/clients/wallet). + +## `grantPermissions` + +Requests permissions from the MetaMask extension account according to the [ERC-7715](https://eips.ethereum.org/EIPS/eip-7715) specifications. + +:::info +To use `grantPermissions`, 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` | `Permission` | Yes | The permission to grant to the user. | +| `signer` | `Signer` | Yes | The account to which the permission will be assigned. | +| `isAdjustmentAllowed` | `boolean` | No | Whether the user is allowed to modify the requested permission. The default is `true`. | + +### Example + + + + +```ts +import { sepolia as chain } from "viem/chains"; +import { walletClient } from "./config.ts"; + +const expiry = Math.floor(Date.now() / 1000 + 604_800); // 1 week from now. +const currentTime = Math.floor(Date.now() / 1000); // now + +// Address of the wallet that will manage the session. It can +// be either a smart account or an externally owned account (EOA) +const sessionAccount = "0x1431.."; + +const grantedPermissions = await walletClient.grantPermissions([{ + chainId: chain.id, + expiry, + signer: { + type: "account", + data: { + address: sessionAccount, + }, + }, + permission: { + type: "native-token-stream", + data: { + initialAmount: 1n, // 1 wei + amountPerSecond: 1n, // 1 wei per second + maxAmount: 10n, // 10 wei + startTime: currentTime, + justification: "Payment for a week long subscription", + }, + }, +}]); +``` + + + + +```ts +import { createWalletClient, custom } from "viem"; +import { erc7715ProviderActions } from "@metamask/delegation-toolkit/experimental"; + +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` | `0x${string}` | Yes | The address of the Delegation Manager. | +| `permissionsContext` | `0x${string}` | Yes | Encoded calldata for redeeming delegations. If you're not using ERC-7715, you can use the [`redeemDelegation`](../delegation.md#redeemdelegation) utility function to generate the calldata manually. | + +### Example + + + + +```ts +import { walletClient, publicClient } from "./config.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 accountMetadata = permissionsResponse[0].accountMeta; + +if (accountMetadata?.length !== 0) { + // If the granted permission contains accountMetadata, this must be executed before attempting to + // redeem the delegation. + + // This transaction will deploy the delegator account. + const hash = walletClient.sendTransaction({ + to: accountMetadata.factory, + data: accountMetadata.factoryData, + }); + + // You should wait for transaction to be successfully executed. + // You can use the TransactionReceipt.status to verify the state. + await publicClient.waitForTransactionReceipt( { hash }); +} + +const hash = walletClient.sendTransactionWithDelegation({ + chain, + to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + value: 1n, + permissionsContext, + delegationManager +}); +``` + + + + +```ts +import { http, createPublicClient, createWalletClient } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { erc7710WalletActions } from "@metamask/delegation-toolkit/experimental"; + +export const publicClient = createPublicClient({ + chain, + transport: http() +}); + +// Your session account for requesting and redeeming should be same. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + +const walletClient = createWalletClient({ + account, + transport: http(), + chain, +}).extend(erc7710WalletActions()); +``` + + + diff --git a/delegation-toolkit/reference/api/smart-account.md b/delegation-toolkit/reference/api/smart-account.md new file mode 100644 index 00000000000..530183db9f3 --- /dev/null +++ b/delegation-toolkit/reference/api/smart-account.md @@ -0,0 +1,470 @@ +--- +description: Smart account-related API methods reference. +sidebar_label: Smart account +sidebar_position: 2 +toc_max_heading_level: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Smart account API reference + +The following API methods are related to creating, managing, and signing with [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 +import { + bundlerClient, + aliceSmartAccount, + bobSmartAccount, + aliceAccount, + bobAccount, +} from "./config.ts"; +import { aggregateSignature } from "@metamask/delegation-toolkit"; + +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 +import { createPublicClient, http } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { createBundlerClient } from "viem/account-abstraction"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +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", + signatory: [ { account: aliceAccount } ], +}); + +export const bobSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.MultiSig, + deployParams: [signers, threshold], + deploySalt: "0x", + signatory: [ { account: bobAccount } ], +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http("https://public.pimlico.io/v2/rpc") +}); +``` + + + + +## `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 +import { createDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.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, + // Empty caveats array - we recommend adding appropriate restrictions. + caveats: [], +}); + +const signature = delegatorSmartAccount.signDelegation({ delegation }); +``` + + + + +```ts +import { createPublicClient, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +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", + signatory: { account: delegatorAccount }, +}); +``` + + + + +## `signMessage` + +Generates the [EIP-191](https://eips.ethereum.org/EIPS/eip-191) signature +using the `MetaMaskSmartAccount` signatory. The Delegation Toolkit +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 +import { smartAccount } from "./config.ts"; + +const signature = smartAccount.signMessage({ + message: 'hello world', +}) +``` + + + + +```ts +import { createPublicClient, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +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", + signatory: { account }, +}); +``` + + + + +## `signTypedData` + +Generates the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) signature +using the `MetaMaskSmartAccount` signatory. The Delegation Toolkit +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 +import { smartAccount } from "./config.ts"; + +const signature = smartAccount.signTypedData({ + domain, + types, + primaryType: "Mail", + message: { + from: { + name: "Cow", + wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + to: { + name: "Bob", + wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + contents: "Hello, Bob!", + }, +}) +``` + + + + +```ts +import { createPublicClient, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +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", + signatory: { account }, +}); +``` + + + + +## `signUserOperation` + +Signs a user operation with the `MetaMaskSmartAccount` signatory. 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 +import { smartAccount } from "./config.ts"; + +const userOpSignature = smartAccount.signUserOperation({ + callData: "0xdeadbeef", + callGasLimit: 141653n, + maxFeePerGas: 15000000000n, + maxPriorityFeePerGas: 2000000000n, + nonce: 0n, + preVerificationGas: 53438n, + sender: "0xE911628bF8428C23f179a07b081325cAe376DE1f", + verificationGasLimit: 259350n, + signature: "0x", + }); +``` + + + + +```ts +import { createPublicClient, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +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", + signatory: { 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 or Multisig. | +| `signatory` | `SignatoryConfigByImplementation ` | 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` | `DeleGatorEnvironment` | 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 | 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 example + + + + +```ts +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { publicClient, account } from "./config.ts"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account: account }, +}); +``` + + + + +```ts +import { createPublicClient, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; + +export const account = privateKeyToAccount("0x..."); +export const publicClient = createPublicClient({ + chain, + transport: http(), +}); +``` + + + + +### Multisig implementation example + + + + +```ts +import { + publicClient, + aliceAccount, + bobAccount +} from "./config.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const signers = [ aliceAccount.address, bobAccount.address ]; +const threshold = 2n + +const aliceSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.MultiSig, + deployParams: [signers, threshold], + deploySalt: "0x", + signatory: [ { account: aliceAccount } ], +}); +``` + + + + +```ts +import { createPublicClient, http } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; + +export const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const alicePrivateKey = generatePrivateKey(); +export const aliceAccount = privateKeyToAccount(alicePrivateKey); + +const bobPrivateKey = generatePrivateKey(); +export const bobAccount = privateKeyToAccount(bobPrivateKey); +``` + + + diff --git a/delegation-toolkit/reference/caveats.md b/delegation-toolkit/reference/caveats.md new file mode 100644 index 00000000000..14d5e081c38 --- /dev/null +++ b/delegation-toolkit/reference/caveats.md @@ -0,0 +1,756 @@ +--- +description: Caveat enforcers reference. +sidebar_position: 1 +toc_max_heading_level: 2 +--- + +# Caveats + +When [restricting a delegation](../how-to/create-delegation/restrict-delegation.md), you can specify the following caveat types in the `CaveatBuilder`. + +## `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 + +1. Index in the calldata byte array (including the 4-byte method selector) where the expected calldata starts +2. Expected calldata as a hex string + +### Example + +```typescript +caveatBuilder.addCaveat("allowedCalldata", + 4, + encodeAbiParameters([ + { type: "string" }, + { type: "uint256" } + ], [ + "Hello Gator", + 12345n + ]) +); +``` + +:::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 + +1. An array of methods as 4-byte hex strings, ABI function signatures, or `ABIFunction` objects + +### Example + +```typescript +caveatBuilder.addCaveat("allowedMethods", [ + "0xa9059cbb", + "transfer(address,uint256)", + { + 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 + +1. An array of addresses as hex strings + +### Example + +```typescript +caveatBuilder.addCaveat("allowedTargets", [ + "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 + +1. The expected `args` as a hex string + +### Example + +```typescript +caveatBuilder.addCaveat("argsEqualityCheck", + "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 + +1. After threshold block number as a `bigint` +2. Before threshold block number as a `bigint` + +You can specify `0n` to indicate that there is no limitation on a threshold. + +### Example + +```typescript +caveatBuilder.addCaveat("blocknumber", + 19426587n, + 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 + +1. A contract address as a hex string +2. The salt to use with the contract, as a hex string +3. The bytecode of the contract as a hex string + +### Example + +```typescript +caveatBuilder.addCaveat("deployed", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x0e3e8e2381fde0e8515ed47ec9caec8ba2bc12603bc2b36133fa3e3fa4d88587", + "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: [`ERC1155BalanceBalanceEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC1155BalanceBalanceEnforcer.sol) + +### Parameters + +1. An ERC-1155 contract address as a hex string +2. The recipient's address as a hex string +3. The ID of the ERC-1155 token as a bigint +4. The amount by which the balance must have changed as a `bigint` +5. 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 +caveatBuilder.addCaveat("erc1155BalanceChange", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1n, + 1_000_000n, + 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 + +1. An ERC-20 contract address as a hex string +2. The recipient's address as a hex string +3. The amount by which the balance must have changed as a `bigint` +4. 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 +caveatBuilder.addCaveat("erc20BalanceChange", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n, + 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 + +1. The address of the ERC-20 token contract. +2. The maximum amount of tokens that can be transferred per period, in wei. +3. The duration of each period in seconds. +4. The timestamp when the first period begins. + +### Example + +```typescript +caveatBuilder.addCaveat("erc20PeriodTransfer", + "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", // Address of the ERC-20 token + 1000000000000000000n, // 1 ERC-20 token - 18 decimals, in wei + 86400, // 1 day in seconds + 1743763600, // April 4th, 2025, at 00:00:00 UTC +); +``` + +## `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 + +1. An ERC-20 contract address as a hex string +2. Initial amount available at start time as a `bigint` +3. Maximum total amount that can be unlocked as a `bigint` +4. Rate at which tokens accrue per second as a `bigint` +5. Start timestamp as a number + +### Example + +```typescript +caveatBuilder.addCaveat("erc20Streaming", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n, + 10_000_000n, + 100n, + 1703980800 +); +``` + +## `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 + +1. An ERC-20 contract address as a hex string +2. Amount as a `bigint` + +### Example + +```typescript +caveatBuilder.addCaveat("erc20TransferAmount", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n +); +``` + +## `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 + +1. An ERC-721 contract address as a hex string +2. The recipient's address as a hex string +3. The amount by which the balance must have changed as a `bigint` +4. 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 +caveatBuilder.addCaveat("erc721BalanceChange", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n, + 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 + +1. The permitted ERC-721 contract address as a hex string +2. The permitted ID of the ERC-721 token as a `bigint` + +### Example + +```typescript +caveatBuilder.addCaveat("erc721Transfer", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 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 + +1. A hex value for calldata. + +### Example + +```typescript +caveatBuilder.addCaveat("exactCalldata", + "0x1234567890abcdef" // Calldata to be matched +); +``` + +## `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 + +1. Array of expected `ExecutionStruct`, each containing target address, value, and calldata. + +### Example + +```typescript +const executions = [ + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + callData: "0x", + }, + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 0n, + callData: "0x", + }, +]; + +caveatBuilder.addCaveat("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 + +1. `ExecutionStruct` to be expected. + +### Example + +```typescript +caveatBuilder.addCaveat("exactExecution", { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + 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 + +1. Array of expected `ExecutionStruct`, each containing target address, value, and calldata. + +### Example + +```typescript +const executions = [ + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + callData: "0x", + }, + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 0n, + callData: "0x", + }, +]; + +caveatBuilder.addCaveat("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 + +1. An ID as a number + +### Example + +```typescript +caveatBuilder.addCaveat("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 + +1. A count as a number + +### Example + +```typescript +caveatBuilder.addCaveat("limitedCalls", + 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 + +An array of token period configuration objects, where each object contains: +- `token`: The address of the token contract as a hex string. +- `periodAmount`: The maximum amount of tokens that can be transferred per period, in wei. +- `periodDuration`: The duration of each period in seconds. +- `startDate`: A Unix epoch timestamp (in seconds) representing when the first period begins. + +### Example + +```typescript +caveatBuilder.addCaveat("multiTokenPeriod", [ + { + token: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", // First token contract + periodAmount: 1000000000000000000n, // 1 token with 18 decimals + periodDuration: 86400, // 1 day in seconds + startDate: 1743763600, // April 4th, 2025, at 00:00:00 UTC + }, + { + token: "0x6be97c23596ECed7170fdFb28e8dA1Ca5cdc54C5", // Second token contract + periodAmount: 500000000n, // 0.5 tokens with 9 decimals + periodDuration: 3600, // 1 hour in seconds + startDate: 1743763600, // April 4th, 2025, at 00:00:00 UTC + } +]); +``` + +## `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 + +1. The recipient's address as a hex string +2. The amount by which the balance must have changed as a `bigint` +3. 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 +caveatBuilder.addCaveat("nativeBalanceChange", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n, + 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 + +1. The recipient's address as a hex string +2. The amount that must be paid as a `bigint` + +### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenPayment", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n +); +``` + +## `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 + +1. The maximum amount of tokens that can be transferred per period, in wei. +2. The duration of each period in seconds. +3. The timestamp when the first period begins. + +### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenPeriodTransfer", + 1000000000000000000n, // 1 ETH in wei + 86400, // 1 day in seconds + 1743763600, // April 4th, 2025, at 00:00:00 UTC +) +``` + +## `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 + +1. Initial amount available at start time as a `bigint` +2. Maximum total amount that can be unlocked as a `bigint` +3. Rate at which tokens accrue per second as a `bigint` +4. Start timestamp as a number + +### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenStreaming", + 1_000_000n, + 10_000_000n, + 100n, + 1703980800 +); +``` + +## `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 + +1. The allowance as a `bigint` + +### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenTransferAmount", + 1_000_000n +); +``` + +## `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 + +1. A nonce as a hex string + +### Example + +```typescript +caveatBuilder.addCaveat("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 + +1. The target contract address as a hex string + +### Example + +```typescript +caveatBuilder.addCaveat("ownershipTransfer", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92" +); +``` + +## `redeemer` + +Limits the addresses that can redeem the delegation. +This caveat is designed for restricting 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 + +1. An array of addresses as hex strings + +### Example + +```typescript +caveatBuilder.addCaveat("redeemer", + [ + "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 + +1. The address of the ERC-20 token contract. +2. The address that will receive the tokens. +3. The amount of tokens to transfer, in wei. +4. The target address for the first transaction. +5. The calldata for the first transaction. + +### Example + +```typescript +caveatBuilder.addCaveat("specificActionERC20TransferBatch", + "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da" // Address of ERC-20 token contract + "0x027aeAFF3E5C33c4018FDD302c20a1B83aDCD96C", // Address that will receive the tokens + 1000000000000000000n, // 1 ERC-20 token - 18 decimals, in wei + "0xb49830091403f1Aa990859832767B39c25a8006B", // Target address for first transaction + "0x1234567890abcdef" // Calldata to be matched for first transaction +) +``` + +## `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 + +1. After threshold timestamp as a number +2. Before threshold timestamp as a number + +You can specify `0` to indicate that there is no limitation on a threshold. + +### Example + +```typescript +caveatBuilder.addCaveat("timestamp", + 499165200, + 1445412480 +); +``` + +## `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 + +1. A value as a `bigint` + +### Example + +```typescript +caveatBuilder.addCaveat("valueLte", + 1_000_000_000_000_000_000n // 1 ETH in wei +); +``` diff --git a/docs/whats-new.md b/docs/whats-new.md index 4e7d066d0d3..60bb84ff173 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -9,6 +9,11 @@ The latest major MetaMask documentation updates are listed by the month they wer For a comprehensive list of recent product changes, visit the "Release Notes" section at the bottom of the [MetaMask developer page](https://metamask.io/developer/). +## June 2025 + +- Added [Delegation Toolkit](/delegation-toolkit) to MetaMask documentation. + ([#2045](https://github.com/MetaMask/metamask-docs/pull/2045)) + ## May 2025 - Documented Multichain API [guide](/wallet/how-to/manage-networks/use-multichain), @@ -18,6 +23,8 @@ of the [MetaMask developer page](https://metamask.io/developer/). ([#1940](https://github.com/MetaMask/metamask-docs/pull/1940)) - Documented [MetaMask SDK + Web3Auth SDK integration](/sdk/quickstart/javascript-web3auth). ([#2029](https://github.com/MetaMask/metamask-docs/pull/2029)) +- Added [Delegation Toolkit](/delegation-toolkit) to MetaMask documentation. + ([#2045](https://github.com/MetaMask/metamask-docs/pull/2045)) - Documented [how to use the Snaps sandbox](/snaps/how-to/test-a-snap/#test-in-the-sandbox). ([#2030](https://github.com/MetaMask/metamask-docs/pull/2030)) - Documented how to use the SDK CLI to set up a [JavaScript + Wagmi](/sdk/quickstart/javascript-wagmi) or [Dynamic SDK](/sdk/quickstart/javascript-dynamic) project. diff --git a/docusaurus.config.js b/docusaurus.config.js index 13fa5d1cc35..87ce6abb005 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -110,6 +110,33 @@ const config = { }, }, ], + [ + '@docusaurus/plugin-content-docs', + { + id: 'gator', + path: 'delegation-toolkit', + routeBasePath: 'delegation-toolkit', + editUrl: 'https://github.com/MetaMask/metamask-docs/edit/main/', + sidebarPath: require.resolve('./gator-sidebar.js'), + breadcrumbs: false, + sidebarCollapsed: false, + includeCurrentVersion: true, + // Set to the latest release. + lastVersion: "0.11.0", + versions: { + // Defaults to the ./docs folder. + // Using "development" instead of "next" as path. + current: { + label: "development", + path: "development", + }, + // The latest release. + "0.11.0": { + label: "latest (0.11.0)", + }, + }, + }, + ], [ '@docusaurus/plugin-content-docs', { @@ -184,7 +211,19 @@ const config = { themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ - metadata: [{ name: 'og:image', content: '/img/metamaskog.jpeg' }], + metadata: [ + { + name: 'og:image', + content: '/img/metamaskog.jpeg' + }, + { + name: "keywords", + content: "MetaMask, SDK, Wallet, API, Dapp, App, Connect, Delegation, Toolkit, Documentation, Smart, Account, Snaps, Infura, Services, Dashboard", + }, + ], + colorMode: { + respectPrefersColorScheme: true, + }, navbar: { title: ' │ ‎ Documentation', logo: { @@ -202,6 +241,10 @@ const config = { to: 'wallet', label: 'Wallet API', }, + { + to: 'delegation-toolkit', + label: 'Delegation Toolkit', + }, { to: 'snaps', label: 'Snaps', @@ -226,11 +269,6 @@ const config = { label: "What's new?", position: 'right', }, - { - href: 'https://support.metamask.io/', - label: 'User support', - position: 'right', - }, { type: 'custom-navbarWallet', position: 'right', @@ -266,9 +304,13 @@ const config = { to: '/sdk', }, { - label: 'Wallet', + label: 'Wallet API', to: '/wallet', }, + { + label: 'Delegation Toolkit', + to: '/delegation-toolkit', + }, { label: 'Snaps', to: '/snaps', @@ -311,6 +353,14 @@ const config = { { title: 'Community', items: [ + { + label: 'Faucet', + to: '/developer-tools/faucet', + }, + { + label: 'MetaMask Developer', + href: 'https://developer.metamask.io/login', + }, { label: 'Consensys Discord', href: 'https://discord.gg/consensys', diff --git a/gator-sidebar.js b/gator-sidebar.js new file mode 100644 index 00000000000..a4e0f72b40e --- /dev/null +++ b/gator-sidebar.js @@ -0,0 +1,10 @@ +const sidebars = { + gatorSidebar: [ + { + type: "autogenerated", + dirName: ".", + }, + ], +}; + +module.exports = sidebars; diff --git a/gator_versioned_docs/version-0.10.0/changelog/0.10.0.md b/gator_versioned_docs/version-0.10.0/changelog/0.10.0.md new file mode 100644 index 00000000000..6642a1edce6 --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/changelog/0.10.0.md @@ -0,0 +1,105 @@ +--- +sidebar_label: 0.10.0 +sidebar_position: 1 +description: MetaMask Delegation Toolkit v0.10.0 changelog +--- + +# What's new in v0.10.0? + +:::warning Breaking changes +The ⚠️ symbol denotes potentially breaking API changes. +As per the [semantic versioning specification](https://semver.org/#spec-item-4), from v1.0.0 onwards, +breaking changes will be released only in major version bumps. +::: + +## Enhancements + +### ⚠️ ERC-7715 permission request +- Removed `required` parameter. +- Added the `isAdjustmentAllowed` parameter, allowing user to modify the permission request as needed. + +```typescript +const permissions = { + chainId: '0x7a69', + address: bob.address, + expiry: 1234567890, + permission: { + type: 'native-token-stream', + data: { + amountPerSecond: '0x1', + maxAmount: '0x2', + initialAmount: undefined, + startTime: 2, + justification: 'Test justification', + }, + }, + // remove-next-line +- required: false + // add-next-line ++ isAdjustmentAllowed: true, + signer: { + type: 'account', + data: { + address: alice.address, + }, + }, +} +``` + +## Contract addresses + +The following are the contract addresses for the +[Delegation Framework version 1.3.0](https://github.com/MetaMask/delegation-framework/blob/v1.3.0/documents/Deployments.md), +as used by this version of the toolkit. + +### Delegation Framework + +| Contract | Address | +|----------|---------| +| EntryPoint | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | +| SimpleFactory | `0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c` | +| DelegationManager | `0x739309deED0Ae184E66a427ACa432aE1D91d022e` | +| MultiSigDeleGatorImpl | `0xB4ab520FF1761f7b6dc221fEaCaf79367629Ed12` | +| HybridDeleGatorImpl | `0xf4E57F579ad8169D0d4Da7AedF71AC3f83e8D2b4` | + +### Caveat enforcers + +| Enforcer | Address | +|----------|---------| +| AllowedCalldataEnforcer | `0x1021300501f6aDc446d4e506053F55a8a63cB1d7` | +| AllowedMethodsEnforcer | `0x371f95c92Be3A916B824A2aE086Ed6db7A6193Fb` | +| AllowedTargetsEnforcer | `0x91e043a13c61f9ddC02BDfe38dCA02A7F5b7Cc88` | +| ArgsEqualityCheckEnforcer | `0xACEC09a804020B307eFF00df9AAfb1Cf656DF9Cf` | +| BlockNumberEnforcer | `0x955C7732562c6Dc4760dF749440f3ab28F46F608` | +| DeployedEnforcer | `0xd2c8c04E2070c13CCB97FEAa25D1915676AAC191` | +| ERC20BalanceGteEnforcer | `0x262A37B51798c44F1BEAd1A076703E4488887b78` | +| ERC20TransferAmountEnforcer | `0x272E92835B12F014353E8754808C67682e9dddFA` | +| ERC721BalanceGteEnforcer | `0xd0960BC7324235DE4FAFe0f2eDCff64313220CC8` | +| ERC721TransferEnforcer | `0x82621E65240f67D8F60a920F709127743A8D20A9` | +| ERC1155BalanceGteEnforcer | `0x01a84C60B0B5c3EbB504fDa60a8236eB7e2D6655` | +| IdEnforcer | `0xd6403989C2cc145102c2AE76E70D1317947ef587` | +| LimitedCallsEnforcer | `0xA45dd3D90447640eB76085637132a74E18b310E3` | +| NonceEnforcer | `0x1ba53a54eDa7021E08065C1C1943bCE91e0FceA3` | +| TimestampEnforcer | `0xABc2591a40db08eD7045D91A29B3DBC33082DB54` | +| ValueLteEnforcer | `0x9C458b17Cd6570e322Ee9a4180b309dAFD08e24C` | +| NativeTokenTransferAmountEnforcer | `0xcfD1BD7922D123Caa194597BF7A0073899a284Df` | +| NativeBalanceGteEnforcer | `0xDb5BAF405159f47Ab70DD424021ef114A450E101` | +| NativeTokenPaymentEnforcer | `0x6e21bABB3779bc694DC3A4DCeB35C1ecC1d9087b` | +| OwnershipTransferEnforcer | `0x5f263261676d24Dd146545F22E485708900B2B83` | +| RedeemerEnforcer | `0x596CC67C2Da64ED51E27B6d61f46e3F687E9182d` | + +### Supported mainnet networks +- Ethereum +- Polygon +- Binance Smart Chain +- Optimism +- Arbitrum +- Linea +- Base +- Gnosis Chain + +### Supported testnet networks +- Ethereum Sepolia +- Linea Sepolia +- Base Sepolia +- MegaEth \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.0/changelog/0.9.0.md b/gator_versioned_docs/version-0.10.0/changelog/0.9.0.md new file mode 100644 index 00000000000..cc6ebee8a60 --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/changelog/0.9.0.md @@ -0,0 +1,108 @@ +--- +sidebar_label: 0.9.0 +sidebar_position: 2 +description: MetaMask Delegation Toolkit v0.9.0 changelog +--- + +# What's new in v0.9.0? + +:::warning Breaking changes +The ⚠️ symbol denotes potentially breaking API changes. +As per the [semantic versioning specification](https://semver.org/#spec-item-4), from v1.0.0 onwards, +breaking changes will be released only in major version bumps. +::: + +## Breaking Changes + +### ⚠️ Package references + +- Moves the package from `@metamask-private` to `@metamask` organization. +- Renames the `delegator-core-viem` entrypoint to `delegation-toolkit`. + +```typescript +// remove-next-line +- import { toMetaMaskSmartAccount } from "@metamask-private/delegator-core-viem"; +// add-next-line ++ import { toMetaMaskSmartAccount } from "@metamask/delegation-toolkit"; +``` + +### ⚠️ SimpleFactory address + +Updates the `SimpleFactory` contract address. Please note, this will result in a change to the smart account address for `MetaMaskSmartAccount`. + +```typescript +// Delegation enviroment for 1.3.0 +export const deployment_1_3_0 = { +//... +// remove-next-line +- SimpleFactory: '0x6ff518884f21168c30c58CB21184D6AdBC18Ad90', +// add-next-line ++ SimpleFactory: '0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c', +//... +} + +``` + +## Enhancements + +### ⚠️ Package installation + +Package installation no longer requires an authentication token, as it has transitioned out of private alpha. 🎉 + +## Contract addresses + +The following are the contract addresses for the +[Delegation Framework version 1.3.0](https://github.com/MetaMask/delegation-framework/blob/v1.3.0/documents/Deployments.md), +as used by this version of the toolkit. + +### Delegation Framework + +| Contract | Address | +|----------|---------| +| EntryPoint | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | +| SimpleFactory | `0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c` | +| DelegationManager | `0x739309deED0Ae184E66a427ACa432aE1D91d022e` | +| MultiSigDeleGatorImpl | `0xB4ab520FF1761f7b6dc221fEaCaf79367629Ed12` | +| HybridDeleGatorImpl | `0xf4E57F579ad8169D0d4Da7AedF71AC3f83e8D2b4` | + +### Caveat enforcers + +| Enforcer | Address | +|----------|---------| +| AllowedCalldataEnforcer | `0x1021300501f6aDc446d4e506053F55a8a63cB1d7` | +| AllowedMethodsEnforcer | `0x371f95c92Be3A916B824A2aE086Ed6db7A6193Fb` | +| AllowedTargetsEnforcer | `0x91e043a13c61f9ddC02BDfe38dCA02A7F5b7Cc88` | +| ArgsEqualityCheckEnforcer | `0xACEC09a804020B307eFF00df9AAfb1Cf656DF9Cf` | +| BlockNumberEnforcer | `0x955C7732562c6Dc4760dF749440f3ab28F46F608` | +| DeployedEnforcer | `0xd2c8c04E2070c13CCB97FEAa25D1915676AAC191` | +| ERC20BalanceGteEnforcer | `0x262A37B51798c44F1BEAd1A076703E4488887b78` | +| ERC20TransferAmountEnforcer | `0x272E92835B12F014353E8754808C67682e9dddFA` | +| ERC721BalanceGteEnforcer | `0xd0960BC7324235DE4FAFe0f2eDCff64313220CC8` | +| ERC721TransferEnforcer | `0x82621E65240f67D8F60a920F709127743A8D20A9` | +| ERC1155BalanceGteEnforcer | `0x01a84C60B0B5c3EbB504fDa60a8236eB7e2D6655` | +| IdEnforcer | `0xd6403989C2cc145102c2AE76E70D1317947ef587` | +| LimitedCallsEnforcer | `0xA45dd3D90447640eB76085637132a74E18b310E3` | +| NonceEnforcer | `0x1ba53a54eDa7021E08065C1C1943bCE91e0FceA3` | +| TimestampEnforcer | `0xABc2591a40db08eD7045D91A29B3DBC33082DB54` | +| ValueLteEnforcer | `0x9C458b17Cd6570e322Ee9a4180b309dAFD08e24C` | +| NativeTokenTransferAmountEnforcer | `0xcfD1BD7922D123Caa194597BF7A0073899a284Df` | +| NativeBalanceGteEnforcer | `0xDb5BAF405159f47Ab70DD424021ef114A450E101` | +| NativeTokenPaymentEnforcer | `0x6e21bABB3779bc694DC3A4DCeB35C1ecC1d9087b` | +| OwnershipTransferEnforcer | `0x5f263261676d24Dd146545F22E485708900B2B83` | +| RedeemerEnforcer | `0x596CC67C2Da64ED51E27B6d61f46e3F687E9182d` | + +### Supported mainnet networks +- Ethereum +- Polygon +- Binance Smart Chain +- Optimism +- Arbitrum +- Linea +- Base +- Gnosis Chain + +### Supported testnet networks +- Ethereum Sepolia +- Linea Sepolia +- Base Sepolia +- MegaEth \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.0/changelog/_category_.json b/gator_versioned_docs/version-0.10.0/changelog/_category_.json new file mode 100644 index 00000000000..f33608bfdf1 --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/changelog/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Changelog" +} diff --git a/gator_versioned_docs/version-0.10.0/concepts/_category_.json b/gator_versioned_docs/version-0.10.0/concepts/_category_.json new file mode 100644 index 00000000000..122abce7702 --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/concepts/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Concepts", + "position": 3, + "link": { + "type": "generated-index", + "slug": "/concepts", + "title": "Delegation Toolkit concepts" + } +} diff --git a/gator_versioned_docs/version-0.10.0/concepts/caveat-enforcers.md b/gator_versioned_docs/version-0.10.0/concepts/caveat-enforcers.md new file mode 100644 index 00000000000..b394cb4870d --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/concepts/caveat-enforcers.md @@ -0,0 +1,185 @@ +--- +description: Learn about caveat enforcers and how they restrict delegations. +sidebar_position: 4 +--- + +# Caveat enforcers + +The MetaMask Delegation Toolkit 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](delegation.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; + +import { ModeCode } from "../utils/Types.sol"; + +/** + * 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`](../how-to/create-delegation/restrict-delegation.md) interface in the MetaMask Delegation Toolkit. + +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](../how-to/create-delegation/restrict-delegation.md#caveat-types) 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`](../how-to/create-delegation/restrict-delegation.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`](../how-to/create-delegation/restrict-delegation.md#nativetokenpayment) before + [`NativeBalanceGteEnforcer`](../how-to/create-delegation/restrict-delegation.md#nativebalancegte) 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 Delegation Toolkit provides [many out-of-the-box caveat enforcers](../how-to/create-delegation/restrict-delegation.md#caveat-types) +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 more complex scenarios, you can also [create custom caveat enforcers](../how-to/create-delegation/create-custom-caveat-enforcer.md) by implementing the `ICaveatEnforcer` interface. + +## Attenuating authority with redelegations + +When [creating chains of delegations](../how-to/create-delegation/index.md#create-a-redelegation), 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. diff --git a/gator_versioned_docs/version-0.10.0/concepts/delegation.md b/gator_versioned_docs/version-0.10.0/concepts/delegation.md new file mode 100644 index 00000000000..6c07b28bb2d --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/concepts/delegation.md @@ -0,0 +1,90 @@ +--- +description: Learn about delegation, the delegation lifecycle, and the Delegation Framework. +sidebar_position: 2 +--- + +# Delegation + +*Delegation* is the ability for a [delegator account](delegator-accounts.md) to grant permission to another smart contract account (SCA) +or externally owned account (EOA) to perform specific executions on the delegator's behalf, under defined rules and restrictions. + +The MetaMask Delegation Toolkit includes the following delegation features: + +- **Caveats** - 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. + +- **Chain of delegations** - Users can redelegate permissions that have been delegated to them, creating a chain of delegations across trusted parties. + +Delegations are created using the `Delegation` type, which is specified as follows: + +```typescript +export type Delegation = { + delegate: Hex; // The address to which the delegation is being granted. + delegator: Hex; // The address that is granting the delegation. + authority: Hex; // Hash of the parent delegation, or the constant ROOT_AUTHORITY. + caveats: Caveat[]; // Caveats that restrict the authority being granted. + salt: Hex; // Used to avoid hash collisions between identical delegations. + signature: Hex; // Signature from the delegator account. +}; +``` + +## Delegation lifecycle + +The delegation lifecycle is as follows: + +1. **Delegation creation** - A delegation is initialized, and the delegator account signs it. + +2. **Caveat enforcement** - The caveats applied to the delegation specify conditions under which + the delegation can be redeemed. + +3. **Delegation storage** - The delegation can be stored, enabling retrieval for future redemption. + + :::note + [Storing and retrieving delegations](../experimental/store-retrieve-delegations.md) using the toolkit's + `DelegationStorageClient` is an experimental feature. + ::: + +4. **Delegation redemption** - The delegate (the account being granted the permission) redeems the + delegation through an [ERC-4337 user operation](delegator-accounts.md#account-abstraction-erc-4337), + which verifies that the delegated authority is valid in order to perform the execution. + +See [how to create a delegation](../how-to/create-delegation/index.md) to get started with the +delegation lifecycle. + +## Delegation Framework + +The MetaMask Delegation Toolkit 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** - There are [multiple delegator account implementations](delegator-accounts.md#delegator-account-types), + with the main difference being the signature scheme 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 a delegation is redeemed, 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 `permissionContexts`, `modes`, and + `executionCallDatas` match, or throws `BatchDataLengthMismatch`. + 2. Decodes and validates the delegation, checking that the caller (`msg.sender`) is the delegate + and that there are no empty signatures, or throws `InvalidDelegate`. + 3. Verifies delegation signatures, ensuring validity using `ECDSA` (for EOAs) or + `isValidSignature` (for contracts), or throws `InvalidSignature`. + 4. Validates the delegation chain's authority and ensures delegations are not disabled. + 5. Executes the `beforeHook` for each `caveat` 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. diff --git a/gator_versioned_docs/version-0.10.0/concepts/delegator-accounts.md b/gator_versioned_docs/version-0.10.0/concepts/delegator-accounts.md new file mode 100644 index 00000000000..60448c2b7d6 --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/concepts/delegator-accounts.md @@ -0,0 +1,79 @@ +--- +description: Learn about account abstraction, the delegator account flow, and account types. +sidebar_position: 1 +--- + +# Delegator accounts + +The MetaMask Delegation Toolkit enables you to create and manage *delegator accounts*. +Delegator accounts are [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) smart contract accounts (SCAs) +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 delegator +accounts use smart contracts to govern account logic. + +## 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 SCAs containing arbitrary verification logic. +ERC-4337 enables SCAs 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 SCA 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 SCAs to facilitate gas abstraction. + +## Delegator account flow + +The MetaMask delegator account flow is as follows: + +1. **Account setup** - A user creates an SCA by deploying a smart contract, and initializing it with + ownership and security settings. + The user can customize the SCA 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 signatory. + +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 account types + +The MetaMask Delegation Toolkit supports two types of delegator accounts, each offering unique features and use cases. +See [Configure accounts and signers](../how-to/configure-delegator-accounts-signers.md) to learn how to use these different account types. + +### Hybrid Delegator + +The Hybrid Delegator is a flexible implementation that supports both an externally owned account (EOA) "owner" and any number of P256 (passkey) signers. +You can configure any of these signers as the signatory, and use them to sign on behalf of the delegator. + +This type is referenced in the toolkit as `Implementation.Hybrid`. + +### Multisig Delegator + +The Multisig Delegator is an implementation that supports multiple signers with a configurable threshold for valid signatures, allowing for enhanced security and flexibility in account management. The signatory must have at least as many signers include as the threshold is configured for the account. + +This type is referenced in the Toolkit as `Implementation.Multisig`. diff --git a/gator_versioned_docs/version-0.10.0/concepts/environment.md b/gator_versioned_docs/version-0.10.0/concepts/environment.md new file mode 100644 index 00000000000..a28b6dfef2e --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/concepts/environment.md @@ -0,0 +1,70 @@ +--- +description: Learn about the delegator environment object and how to use it. +sidebar_position: 3 +--- + +# Delegator environment + +The delegator environment object, `DeleGatorEnvironment`, is a component of the MetaMask Delegation Toolkit that defines the contract addresses +necessary for interacting with the [Delegation Framework](delegation.md#delegation-framework) on a specific network. + +The delegator environment 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. + +## Resolving the environment for a `DeleGatorClient` + +When you create a `DeleGatorClient`, the Delegation Toolkit automatically resolves the environment based on the version it requires, and the `chainId` configured in the `DeleGatorClient`. If no environment is found, an error is thrown. + +## Deploying and using a custom delegator environment + +You can deploy the contracts using any method, but the toolkit provides a `deployDeleGatorEnvironment` function for convenience. +This function requires a Viem [Public Client](https://viem.sh/docs/clients/public.html), [Wallet Client](https://viem.sh/docs/clients/wallet.html), and [Chain](https://viem.sh/docs/glossary/types#chain), +and resolves to the `DeleGatorEnvironment` object: + +```typescript +const environment = await deployDeleGatorEnvironment(walletClient, publicClient, chain); +``` + +You can also override any contracts when calling `deployDeleGatorEnvironment` (for example, if you have already deployed the `EntryPoint` contract to your local Anvil node): + +```typescript +const environment = await deployDeleGatorEnvironment(walletClient, publicClient, chain, { + EntryPoint: "0x0000000071727De22E5E9d8BAf0edAc6f37da032" +}); +``` + +You can now use this `environment` object when creating a `DeleGatorClient`: + +```typescript +const client = createDeleGatorClient({ + transport, + chain, + account, + environment +}); +``` + +## Overriding a configured environment + +To simplify code when interacting with a `DeleGatorClient`, and to enable using the same code for production and development environments, +you can override the `DeleGatorEnvironment` that is resolved internally for a given chain ID and contract version: + +```typescript +const environment = await deployDeleGatorEnvironment(walletClient, publicClient, chain); + +overrideDeployedEnvironment( + chainId, + "1.1.0", + environment, +); +``` + +With this code, your environment object is used when you create a `DeleGatorClient` with the specified `chainId`. + +:::note +Make sure to specify the Framework version required by the toolkit. +See the [changelog](../changelog/0.10.0) of the toolkit version you are using for its required Framework version. +::: diff --git a/gator_versioned_docs/version-0.10.0/experimental/_category_.json b/gator_versioned_docs/version-0.10.0/experimental/_category_.json new file mode 100644 index 00000000000..2592b227a06 --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/experimental/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Experimental", + "position": 5, + "link": { + "type": "generated-index", + "slug": "/experimental", + "title": "Delegation Toolkit experimental features" + } +} \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.0/experimental/erc-7710-redeem-delegations.md b/gator_versioned_docs/version-0.10.0/experimental/erc-7710-redeem-delegations.md new file mode 100644 index 00000000000..85325565000 --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/experimental/erc-7710-redeem-delegations.md @@ -0,0 +1,237 @@ +--- +description: Learn how to redeem ERC-7710 delegations with a smart contract account or an externally owned account (EOA). +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# ERC-7710: Redeem delegations + +:::caution Experimental +This is an experimental feature and may change in future releases. +::: + +[ERC-7710](https://eip.tools/eip/7710) introduces a standard way for smart contract accounts (SCAs) to delegate capabilities to other +SCAs or externally owned accounts (EOAs). + +The MetaMask Delegation Toolkit provides two experimental functions, `erc7710BundlerActions()` and `erc7710WalletActions()`, that let +a caller redeem delegations granted by MetaMask's permissions system. + +## Extract relevant data + +Refer to [ERC-7715: Request permissions](erc-7715-request-permissions.md) for information on how to request user permissions. +Once the permission has been granted, extract the relevant data from the response. +For example: + +```typescript +// Response received from the ERC-7715 wallet_grantPermissions request. +const permissionsResponse = [{ + chainId: "0xe715", + account: "0xD6f56C2B10b1e02D841E4a97c60Afe914E884DBd", + expiry: 1234567890, + permission: { + type: "native-token-stream", + data: { + amountPerSecond: "0x1", + maxAmount: "0x2", + initialAmount: undefined, + startTime: 2, + }, + }, + context: "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d" + signer: { + type: "account", + data: { + account: "0x07bfc7230D5BD2544059816D88A895BB000Abe00" + } + }, + signerMeta: { + delegationManager: "0xDC7e12b41E5e61BfCc7F56AAFB7B93288F61e841" + }, + accountMetadata: [{ + factory: "0x65E726b404149fE37F4b291c81Dc6eddd44763A7", + factoryData: "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b" + }] +}]; + +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +// accountMeta is only present when the smart contract account is not deployed. +const accountMetadata = permissionsResponse[0].accountMeta; +``` + +This data encodes the authority that lets the delegate redeem the permission. + +### Security considerations for `accountMeta` + +When a user grants a permission, they can provide `accountMeta` which is an array of `factory` and `factoryData` values. +These calls must be executed before redeeming the permission (this is handled for you in `sendUserOperationWithDelegation`). + +Because each `accountMeta` is an arbitrary call specified by the granter, it is important that these are executed carefully. +We recommend taking the following precautions: + +- **Only grant permissions to session accounts** - When requesting permissions, use an account that is only used for that single purpose, and does not contain tokens. +This way, any `accountMeta` executed can't perform any damaging actions. + +- **Only execute `accountMeta` against trusted factory addresses** - Ensure that only `accountMeta` targeting a known factory address is executed. +The bundler action `sendUserOperationWithDelegation` only executes `accountMeta` that targets the `SimpleFactory` address for the current Delegation Framework. +If you redeem delegations in any other way, it is your responsibility to validate trusted factory addresses. + +## Redeem the permission + +Redeem a delegation using one of two methods. Choose the method based on your account type: + +- If redeeming with an SCA, call `sendUserOperationWithDelegation`. +- If redeeming with an EOA, call `sendTransactionWithDelegation`. + +### Redeem with an SCA + +To redeem a delegation with a smart contract account, create a [`MetaMaskSmartAccount`](../how-to/create-delegator-account.md#create-a-metamasksmartaccount) +and a [Viem Bundler Client](https://viem.sh/account-abstraction/clients/bundler). + +After setting up your Bundler Client, you can extend its functionality with `erc7710BundlerActions` actions to support ERC-7710. Once extended, use `sendUserOperationWithDelegation` to redeem the permission. + + + + +```typescript +import { sessionAccount, bundlerClient, publicClient } from "./config.ts"; + +// These properties must be extracted from the permission response. +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +const accountMetadata = permissionsResponse[0].accountMeta; + +// 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 + accountMetadata, +}); +``` + + + + +```typescript +import { createPublicClient, http, createBundlerClient } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { erc7710BundlerActions } from "@metamask/delegation-toolkit/experimental"; +import { toMetaMaskSmartAccount, Implementation } from "@metamask/delegation-toolkit"; + +export const publicClient = createPublicClient({ + chain: chain, + transport: http(), +}); + +// Your session account for requesting and redeeming should be same. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + + +export const sessionAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { 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()); +``` + + + +:::note +`sendUserOperationWithDelegation` is similar to the `sendUserOperation` function, but does not accept `callData` directly. +::: + +### Redeem with an EOA + +To redeem a delegation with an EOA, create a [Viem Wallet Client](https://viem.sh/docs/clients/wallet). + +After creating your Wallet Client, you can extend its functionality with `erc7710WalletActions` actions to support ERC-7710. Once extended, use `sendTransactionWithDelegation` to redeem the permission. + + + + +```typescript +import { walletClient, publicClient } from "./config.ts"; + +// These properties must be extracted from the permission response. +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +const accountMetadata = permissionsResponse[0].accountMeta; + +if (accountMetadata?.length !== 0) { + // If the granted permission contains accountMetadata, this must be executed before attempting to + // redeem the delegation. + + // This transaction will deploy the delegator account. + const hash = walletClient.sendTransaction({ + to: accountMetadata.factory, + data: accountMetadata.factoryData, + }); + + // You should wait for transaction to be successfully executed. + // You can use the TransactionReceipt.status to verify the state. + await publicClient.waitForTransactionReceipt( { hash }); +} + +const hash = walletClient.sendTransactionWithDelegation({ + chain, + to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + value: 1n, + permissionsContext, + delegationManager +}); +``` + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { erc7710WalletActions } from "@metamask/delegation-toolkit/experimental"; + +export const publicClient = createPublicClient({ + chain, + transport: http() +}); + +// Your session account for requesting and redeeming should be same. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + +const walletClient = createWalletClient({ + account, + transport: http(), + chain, +}).extend(erc7710WalletActions()); +``` + + diff --git a/gator_versioned_docs/version-0.10.0/experimental/erc-7715-request-permissions.md b/gator_versioned_docs/version-0.10.0/experimental/erc-7715-request-permissions.md new file mode 100644 index 00000000000..8d605b4a76a --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/experimental/erc-7715-request-permissions.md @@ -0,0 +1,182 @@ +--- +description: Learn how to request ERC-7715 permissions. +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# ERC-7715: Request permissions + +:::caution Experimental +This is an experimental feature. +It requires MetaMask Flask 12.14.2 or later, and may change in future releases. +::: + +[ERC-7715](https://eip.tools/eip/7715) introduces a standard way for dapps to request permissions from a wallet to execute +transactions on a user's behalf. + +The MetaMask Delegation Toolkit provides the experimental actions for ERC-7715 that lets a caller request permissions from MetaMask's permissions system. + +## Request permissions + +To request permissions, extend your [Viem Wallet Client](https://viem.sh/docs/clients/wallet) with `erc7715ProviderActions` actions. +You'll need a session account to request the permission, which can be either an externally owned account (EOA) or a smart contract account (SCA). +This example uses an SCA: + + + + +```typescript +import { sepolia as chain } from "viem/chains"; +import { sessionAccount, walletClient } from "./config.ts"; + +const expiry = Math.floor(Date.now() / 1000 + 604_800); // 1 week from now. +const currentTime = Math.floor(Date.now() / 1000); // now + +const grantedPermissions = await walletClient.grantPermissions([{ + chainId: chain.id, + expiry, + signer: { + type: "account", + data: { + address: sessionAccount.address, + }, + }, + permission: { + type: "native-token-stream", + data: { + initialAmount: 1n, // 1 wei + amountPerSecond: 1n, // 1 wei per second + maxAmount: 10n, // 10 wei + startTime: currentTime, + justification: "Payment for a week long subscription", + }, + }, +}]); +``` + + + + + +```typescript +import { createWalletClient, custom, createPublicClient, http } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { erc7715ProviderActions } from "@metamask/delegation-toolkit/experimental"; +import { toMetaMaskSmartAccount, Implementation } from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain: chain, + transport: http(), +}); + +// The private key of the session owner. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + +export const sessionAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); + +export const walletClient = createWalletClient({ + transport: custom(window.ethereum), +}).extend(erc7715ProviderActions()); +``` + + + +
+ ERC-7715 request permission sample + + Here's what your ERC-7715 native token streaming allowance request looks like: + + ```ts + [{ + chainId: "0xaa36a7", + expiry: 1745041429, + permission: { + type: "native-token-stream", + data: { + amountPerSecond: "0x1", + maxAmount: "0x1", + initialAmount: "0xa", + startTime: 1744955029, + justification: "Payment for a week long subscription", + }, + }, + signer: { + type: "account", + data: { + address: "0x1234...", + }, + }, + }] + ``` + + Learn more about the [ERC-7715 permission schema](https://eip.tools/eip/7715). +
+ + +Additionally, you can allow users to modify the requested permission by setting the `isAdjustmentAllowed` parameter to `true` in the request. + +```typescript +import { sepolia as chain } from "viem/chains"; +// The config.ts is the same as in the previous example. +import { sessionAccount, walletClient } from "./config.ts"; + +const expiry = Math.floor(Date.now() / 1000 + 604_800); // 1 week from now. +const currentTime = Math.floor(Date.now() / 1000); // now + +const grantedPermissions = await walletClient.grantPermissions([{ + chainId: chain.id, + expiry, + signer: { + type: "account", + data: { + address: sessionAccount.address, + }, + }, + permission: { + type: "native-token-stream", + // add-next-line ++ isAdjustmentAllowed: true, + data: { + initialAmount: 1n, // 1 wei + amountPerSecond: 1n, // 1 wei per second + maxAmount: 10n, // 10 wei + startTime: currentTime, + justification: "Payment for a week long subscription", + }, + }, +}]); +``` + +:::note +Users have full control over the permissions they grant—depending on the permission you request, they may choose to grant more limited permissions than requested. +You should always verify the granted permissions and adjust your dapp's behavior accordingly. +::: + +## Security considerations for `accountMeta` + +When a user grants a permission, they can provide [`accountMeta`](erc-7710-redeem-delegations.md#extract-relevant-data) which is an array of `factory` and `factoryData` values. +These calls must be executed before redeeming the permission (this is handled for you in `sendUserOperationWithDelegation`). + +Because each `accountMeta` is an arbitrary call specified by the granter, it is important that these are executed carefully. +We recommend taking the following precautions: + +- **Only grant permissions to session accounts** - When requesting permissions, use an account that is only used for that single purpose, and does not contain tokens. + This way, any `accountMeta` executed can't perform any damaging actions. + +- **Only execute `accountMeta` against trusted factory addresses** - Ensure that only `accountMeta` targeting a known factory address is executed. + The bundler action `sendUserOperationWithDelegation` only executes `accountMeta` that targets the `SimpleFactory` address for the current Delegation Framework. + If you redeem delegations in any other way, it is your responsibility to validate trusted factory addresses. + +## Next steps + +You can redeem the granted permission using the experimental [ERC-7710 `erc7710WalletActions()`](erc-7710-redeem-delegations.md). diff --git a/gator_versioned_docs/version-0.10.0/experimental/store-retrieve-delegations.md b/gator_versioned_docs/version-0.10.0/experimental/store-retrieve-delegations.md new file mode 100644 index 00000000000..3b6cde29fd6 --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/experimental/store-retrieve-delegations.md @@ -0,0 +1,172 @@ +--- +description: Store and retrieve delegations using the `DelegationStorageClient`. +sidebar_position: 1 +toc_max_heading_level: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Store and retrieve delegations + +:::caution Experimental +This is an experimental feature and may change in future releases. +::: + +You can use methods provided by the `DelegationStorageClient` of the MetaMask Delegation Toolkit to store and retrieve +[delegations](../concepts/delegation.md). + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](../how-to/configure.md) +- Ensure you have an API key and API key ID to interact with the `DelegationStorageClient`. + If you need to gain access, email hellogators@consensys.net. + +## Configure the storage client + +Create the `DelegationStorageClient` instance, and configure it using your API key and API key ID. + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + +## Store a delegation + +To store a delegation, use the `storeDelegation` method of the `DelegationStorageClient`. This method takes one parameter: + +1. `delegation` - A `Delegation` object representing the delegation to be stored. + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; + +const delegationHash = await delegationStorageClient.storeDelegation(delegation); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + + +## Retrieve a delegation chain + +To retrieve a delegation chain, use the `getDelegationChain` method of the `DelegationStorageClient`. This method takes one parameter: + +1. `leafDelegationOrDelegationHash` - Either a `Delegation` object or the delegation hash as a hex string. + +:::note +A delegation can be a root delegation, where its `authority` is `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`. It can also be a child of another delegation, where its `authority` is the hash of its parent delegation. This method returns the delegation referenced by `leafDelegationOrDelegationHash` and any ancestors. +::: + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; +import { getDelegationHashOffchain } from "@metamask/delegation-toolkit"; + +// Assuming you have the leaf delegation +const delegationHash = getDelegationHashOffchain(leafDelegation); + +const delegationChain: Delegation[] = await delegationStorageClient.getDelegationChain( + delegationHash +); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + + +## Retrieve delegations for a specific account + +To retrieve delegations stored for a specific account, use the `fetchDelegations` method of the `DelegationStorageClient`. This method allows you to fetch delegations where the specified account is either the delegator or the delegate. +It takes two parameters: + +1. `account` - The address of the account for which you want to retrieve delegations. +2. `filter` - The nature of the delegations. Possible values are: + - `DelegationStoreFilter.Given` - For delegations where the specified `account` is the `delegator`. + - `DelegationStoreFilter.Received` - For delegations where the specified `account` is the `delegate`. + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; + +const address = "0x027aeAFF3E5C33c4018FDD302c20a1B83aDCD96C" + +// Fetch the delegations given by address. +const grantedDelegations = await delegationStorageClient.fetchDelegations( + address, + DelegationStoreFilter.Given, +); + +// Fetch the delegations received by the address. +const receivedDelegations = await delegationStore.fetchDelegations( + address, + DelegationStoreFilter.Received, +); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + diff --git a/gator_versioned_docs/version-0.10.0/get-started/_category_.json b/gator_versioned_docs/version-0.10.0/get-started/_category_.json new file mode 100644 index 00000000000..4a23260d67d --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/get-started/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Get started", + "position": 2, + "link": { + "type": "generated-index", + "slug": "/get-started", + "title": "Get started with the Delegation Toolkit" + } +} diff --git a/gator_versioned_docs/version-0.10.0/get-started/install.md b/gator_versioned_docs/version-0.10.0/get-started/install.md new file mode 100644 index 00000000000..060d67f0ecd --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/get-started/install.md @@ -0,0 +1,48 @@ +--- +sidebar_label: Install and set up +description: Learn how to install and set up the MetaMask Delegation Toolkit. +sidebar_position: 1 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Install and set up the Delegation Toolkit + +This page provides instructions to install and set up the MetaMask Delegation Toolkit. + +## 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](../how-to/create-delegation/create-custom-caveat-enforcer.md)), install + [Foundry](https://book.getfoundry.sh/getting-started/installation). + +## Steps + +### 1. Install the toolkit + +Install the MetaMask Delegation Toolkit dependencies: + +```bash npm2yarn +npm install @metamask/delegation-toolkit +``` + +### 2. (Optional) Install the contracts + +If you plan to extend the Delegation Framework smart contracts (for example, to +[create a custom caveat enforcer](../how-to/create-delegation/create-custom-caveat-enforcer.md)), 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 MetaMask Delegation Toolkit. +Check out the [Delegation Toolkit quickstart](quickstart.md) to walk through a simple example. \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.0/get-started/quickstart.md b/gator_versioned_docs/version-0.10.0/get-started/quickstart.md new file mode 100644 index 00000000000..41b7d5462cc --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/get-started/quickstart.md @@ -0,0 +1,173 @@ +--- +description: Get started quickly with the MetaMask Delegation Toolkit. +sidebar_position: 2 +sidebar_label: Quickstart +--- + +# Delegation Toolkit quickstart + +This page demonstrates how to get started quickly with the MetaMask Delegation Toolkit, +by creating a delegator account and completing the delegation lifecycle (creating, signing, and redeeming a delegation). + +## Prerequisites + +[Install and set up the Delegation Toolkit.](install.md) + +## Steps + +### 1. 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 delegator account query the signer's account state and interact with smart contracts. + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const publicClient = createPublicClient({ + chain, + transport: http(), +}); +``` + +### 2. 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 +import { createBundlerClient } from "viem/account-abstraction"; + +const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http("https://your-bundler-rpc.com"), +}); +``` + +### 3. Create a delegator account + +[Create a delegator account](../how-to/create-delegator-account.md) to set up a delegation. +The delegator must be a smart account. + +This example configures a [Hybrid Delegator](../how-to/configure-delegator-accounts-signers.md#configure-a-hybrid-delegator): + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; + +const delegatorAccount = privateKeyToAccount("0x..."); + +const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegatorAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegatorAccount }, +}); +``` + +### 4. Create a delegate account + +Create a delegate account to receive the delegation. +The delegate can be either a smart contract account (SCA) or an externally owned account (EOA). + +This example uses an SCA: + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; + +const delegateAccount = privateKeyToAccount("0x..."); + +const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + +### 5. Create a delegation + +[Create a root delegation](../how-to/create-delegation/index.md#create-a-root-delegation) from the +delegator account to the delegate account. + +This example passes an empty `caveats` array, which means the delegate can perform any action on the delegator's behalf. +We recommend [restricting the delegation](../how-to/create-delegation/restrict-delegation.md) by adding caveat enforcers. + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +const delegation = createDelegation({ + to: delegateSmartAccount.address, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + +### 6. Sign the delegation + +[Sign the delegation](../how-to/create-delegation/index.md#sign-a-delegation) using the `signDelegation` method from `MetaMaskSmartAccount`. +Alternatively, you can use the Delegation Toolkit's `signDelegation` utility. +The signed delegation will be used later to perform actions on behalf of the delegator. + +```typescript +const signature = await delegatorSmartAccount.signDelegation({ + delegation +}); + +const signedDelegation = { + ...delegation, + signature, +}; +``` + +### 7. Redeem the delegation + +The delegate account can now [redeem the delegation](../how-to/redeem-delegation.md). +The redeem transaction is sent to the `DelegationManager` contract, which validates the delegation and +executes actions on the delegator's behalf. + +To prepare the call data for the redeem transaction, use the `redeemDelegation` utility function from the Delegation Toolkit. + +```typescript +import { + createExecution, + DelegationFramework, + SINGLE_DEFAULT_MODE, +} from "@metamask/delegation-toolkit"; +import { zeroAddress } from "viem"; + +const delegations = [ signedDelegation ]; + +const executions = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ SINGLE_DEFAULT_MODE ], + executions: [ executions ] +}); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: delegateSmartAccount.address, + data: redeemDelegationCalldata + } + ], + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, +}); +``` diff --git a/gator_versioned_docs/version-0.10.0/how-to/_category_.json b/gator_versioned_docs/version-0.10.0/how-to/_category_.json new file mode 100644 index 00000000000..2b6df81f83d --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/how-to/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "How to", + "position": 4, + "link": { + "type": "generated-index", + "slug": "/how-to", + "title": "Delegation Toolkit how-to guides" + } +} diff --git a/gator_versioned_docs/version-0.10.0/how-to/configure-delegator-accounts-signers.md b/gator_versioned_docs/version-0.10.0/how-to/configure-delegator-accounts-signers.md new file mode 100644 index 00000000000..c25833669be --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/how-to/configure-delegator-accounts-signers.md @@ -0,0 +1,303 @@ +--- +sidebar_label: Configure accounts and signers +description: Learn how to configure different types of delegator accounts and signers using Viem. +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Configure delegator accounts and signers + +The MetaMask Delegation Toolkit supports different [delegator account types](../concepts/delegator-accounts.md#delegator-account-types), +each with its own configuration and support for different signing mechanisms. +You can create flexible and secure delegator accounts tailored to your specific needs. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator account.](create-delegator-account.md) + +## Configure a Hybrid Delegator + +The [Hybrid Delegator](../concepts/delegator-accounts.md#hybrid-delegator) supports both an EOA "owner" and any number of P256 (passkey) signers. + +To configure a Hybrid Delegator, provide the following parameters: + +- `owner`: The owner's account address as a hex string. + The owner can be the zero address, indicating that there is no owner configured. +- `p256KeyIds`: An array of key identifiers for P256 signers as hex strings. +- `p256XValues`: An array of public key x-values for P256 signers as `bigint`s. +- `p256YValues`: An array of public key y-values for P256 signers as `bigint`s. +- `signatory`: A signer that will sign on behalf of the delegator account. + +:::note +You can set all `p256` parameters to empty arrays to configure no WebAuthn signer. +However, we recommend configuring at least one signer for account recoverability. +::: + +For a Hybrid Delegator, you can configure the following types of signatories: + +### Configure an account signatory + +This example creates a signatory from a private key using Viem's [`privateKeyToAccount`](https://viem.sh/docs/accounts/local/privateKeyToAccount) function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const signatory = { account }; +``` + + + + +### Configure a Wallet Client signatory + +This example creates a [Viem Wallet Client](https://viem.sh/docs/clients/wallet) as the signatory, +using Viem's `createWalletClient` function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient } from "viem"; + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +const walletClient = createWalletClient({ + account, + chain, + transport: http() +}) + +export const signatory = { walletClient }; +``` + + + + +### Configure a WebAuthn (passkey) signatory + +This example creates a [Viem WebAuthn Account](https://viem.sh/account-abstraction/accounts/webauthn) as the signatory, +using Viem's `toWebAuthnAccount` function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { createCredential, parsePublicKey } from "webauthn-p256"; +import { toWebAuthnAccount } from "viem/account-abstraction"; +import { toHex } from "viem"; + +const credential = await createCredential({ name: "Your Delegator Passkey" }); +const webAuthnAccount = toWebAuthnAccount({ credential }); +const keyId = toHex("my-key-id"); + +const signatory = { webAuthnAccount, keyId }; +``` + + + + + +## Configure a Multisig Delegator + +The [Multisig Delegator](../concepts/delegator-accounts.md#multisig-delegator) supports multiple EOA signers with a configurable threshold for execution. + +To configure a Multisig Delegator, provide the following parameters: + +- `signers`: An array of EOA signer addresses as hex strings. +- `threshold`: The number of signers required to execute a transaction, as a `bigint`. +- `signatory`: An array of signatories that will sign on behalf of the delegator account. + +### Configure signatories + +For a Multisig Delegator, you can use a combination of account signatories and Wallet Client signatories. +For example: + + + + +```typescript +import { publiClient } from "./client.ts"; +import { account, walletClient } from "./signers.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const signers = [ account.address, walletClient.address ]; +const signatory = { account, walletClient }; +const threshold = 2n + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.MultiSig, + deployParams: [signers, threshold], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient } from "viem"; + +// 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 walletClientPivatekey = generatePrivateKey(); +const walletClientAccount = privateKeyToAccount(walletClientPivatekey); + +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. +::: diff --git a/gator_versioned_docs/version-0.10.0/how-to/configure.md b/gator_versioned_docs/version-0.10.0/how-to/configure.md new file mode 100644 index 00000000000..e85115c1408 --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/how-to/configure.md @@ -0,0 +1,60 @@ +--- +description: Learn how to configure the MetaMask Delegation Toolkit using Viem. +sidebar_position: 1 +sidebar_label: Configure the toolkit +--- + +# Configure the Delegation Toolkit + +The MetaMask Delegation Toolkit enables you to easily integrate delegator accounts into your dapp, enabling a more flexible, secure, and frictionless experience for your users. + +The toolkit is highly configurable, allowing you to tailor it to your project's specific needs. It includes support for custom signers, multiple signatory schemes, custom paymasters and bundlers, and more. + +:::note +The MetaMask Delegation Toolkit provides custom middleware for [Pimlico's](https://docs.pimlico.io/) gas fee resolver, paymaster, and bundler. Additional options will be made available soon. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit](../get-started/install.md). +- Optionally, complete the [Delegation Toolkit quickstart](../get-started/quickstart.md) to + familiarize yourself with the toolkit's capabilities. + +## Viem's Account Abstraction API + +The toolkit uses Viem's Account Abstraction API. This provides a robust and flexible foundation for creating and managing smart contract accounts. +See Viem's [Smart Account documentation](https://viem.sh/account-abstraction/accounts/smart) for more information on the API's features, methods, and best practices. + + +## Configure Viem bundler and paymaster clients + +To use the bundler and paymaster clients with the toolkit, create instances of these clients and configure them as follows: + +```typescript +import { + createPaymasterClient, + createBundlerClient, +} from "viem/account-abstraction"; +import { http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +// 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, +}); +``` + +:::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 sufficient funds to pay for gas fees directly. +::: + diff --git a/gator_versioned_docs/version-0.10.0/how-to/create-delegation/create-custom-caveat-enforcer.md b/gator_versioned_docs/version-0.10.0/how-to/create-delegation/create-custom-caveat-enforcer.md new file mode 100644 index 00000000000..77416e901f5 --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/how-to/create-delegation/create-custom-caveat-enforcer.md @@ -0,0 +1,149 @@ +--- +description: Learn how to create, deploy, and apply a custom caveat enforcer +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a custom caveat enforcer + +When [restricting a delegation](restrict-delegation.md), the MetaMask Delegation Toolkit provides some [out-of-the-box caveat enforcers](restrict-delegation.md#caveat-types) +that cover common use cases. +For more granular or custom control, you can follow the instructions on this page to create custom caveat enforcers from scratch. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../get-started/install.md) +- [Configure the Delegation Toolkit.](../configure.md) + +## Steps + +### 1. Create the caveat enforcer + +Create a contract that extends the +[`ICaveatEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/interfaces/ICaveatEnforcer.sol) +interface. + +For example, the following is a simple caveat enforcer that only allows a delegation to be redeemed after a specific timestamp. + +```solidity title="AfterTimestampEnforcer.sol" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import { CaveatEnforcer } from "@delegator/src/enforcers/CaveatEnforcer.sol"; +import { ModeCode } from "../utils/Types.sol"; + +contract AfterTimestampEnforcer is CaveatEnforcer { + /** + * @notice The delegation may only be redeemed after the specified timestamp - validAfter in seconds. + * @param _terms The validAfter, timestamp in seconds after which the delegation may be redeemed. + * @param _delegationHash - The hash of the delegation being operated on. + */ + function beforeHook( + bytes calldata _terms, + bytes calldata, + ModeCode, + bytes calldata, + bytes32 _delegationHash, + address, + address _redeemer + ) + public + override + { + // Enforces the conditions that should hold before a transaction is performed. + // This function MUST revert if the conditions are not met. + // Get the current timestamp + uint256 timestamp = block.timestamp; + + uint256 validAfter = uint256(bytes32(_terms)); + + require(timestamp > validAfter, "AfterTimestampEnforcer:cannot-redeem-too-early"); + } +} +``` + +### 2. Deploy the caveat enforcer + +Deploy your custom caveat enforcer to obtain its contract address. +For example, you can [deploy your smart contract using Forge](https://book.getfoundry.sh/forge/deploying). + +### 3. Apply the caveat enforcer + +When creating a delegation, add the `Caveat` for the custom caveat to the `CaveatBuilder`. +Learn more about [applying caveats to a delegation](restrict-delegation.md). + +The following example uses the custom `AfterTimestampEnforcer.sol` caveat enforcer to create a delegation granting +an allowance of 1,000,000 wei that can only be spent after one hour from when the delegation is created. + +:::warning Important +Depending on the use case, it may be necessary to add additional caveats to restrict the delegation further. +::: + + + + +```typescript +import { + createCaveatBuilder, + createDelegation, +} from "@metamask/delegation-toolkit"; +import { toHex } from "viem"; +import { delegatorSmartAccount } from "./config.ts"; + +const environment = delegatorSmartAccount.enviroment; + +// Replace this with the address where the AfterTimestampEnforcer.sol contract is deployed. +const afterTimestampEnforcer = "0x22Ae4c4919C3aB4B5FC309713Bf707569B74876F"; + +const caveatBuilder = createCaveatBuilder(environment); + +const tenAM = 10 * 60 * 60; // 10:00 AM as seconds since midnight. + +const caveats = caveatBuilder + .addCaveat("nativeTokenTransferAmount", 1_000_000) + .addCaveat({ + enforcer: afterTimestampEnforcer, + terms: toHex(tenAm) + }); + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats +}); +``` + + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + diff --git a/gator_versioned_docs/version-0.10.0/how-to/create-delegation/index.md b/gator_versioned_docs/version-0.10.0/how-to/create-delegation/index.md new file mode 100644 index 00000000000..1f5094ee9af --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/how-to/create-delegation/index.md @@ -0,0 +1,349 @@ +--- +description: Learn how to create different types of delegations, and how to sign a delegation. +sidebar_position: 6 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a delegation + +The MetaMask Delegation Toolkit enables you to create [delegations](../../concepts/delegation.md) +from a delegator account to a delegate account. + +:::note +Delegations are compatible with [ERC-7710](https://eip.tools/eip/7710) and [ERC-7715](https://ethereum-magicians.org/t/erc-7715-grant-permissions-from-wallets/20100), to support a standardized minimal interface. +[Requesting ERC-7715 permissions](../../experimental/erc-7715-request-permissions.md) and [redeeming ERC-7710 delegations](../../experimental/erc-7710-redeem-delegations.md) +are experimental features. +::: + +:::warning +The examples on this page demonstrate delegations without any restrictions. +Unrestricted delegations grant complete control over the account to the delegate, which can pose significant security risks. +It is crucial to add caveats to limit the delegated authority. +Learn how to [restrict a delegation](./restrict-delegation.md) using caveat enforcers. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../get-started/install.md) +- [Configure the Delegation Toolkit.](../configure.md) +- [Create a delegator account.](../create-delegator-account.md) + +## Create a root delegation + +A *root delegation* is a delegation that doesn't derive its authority from another delegation. +It is when a delegator delegates its own authority away, as opposed to a [redelegation](#create-a-redelegation). +Create a root delegation as follows: + + + + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.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: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create an 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. +You must create open root delegations carefully, to ensure that they are not misused. +Create an open root delegation by setting the delegate property to the special address +`0x0000000000000000000000000000000000000a11` (available via the constant `ANY_BENEFICIARY`). + + + + +```typescript +import { createOpenDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +const openRootDelegation = createOpenDelegation({ + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create a redelegation + +A recipient of a delegation (the delegate), can *redelegate* that authority to a third party, potentially applying additional [restrictions](restrict-delegation.md). +Create a redelegation as follows: + + + + +```typescript +import { + createDelegation, + getDelegationHashOffchain + } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +// The address is used as the root delegator. While creating the leaf delegation, +// the root delegate address will be the delegator address. +const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +// The authority is the (typed data) hash of the delegation from which the authority is derived. +const parentDelegationHash = getDelegationHashOffchain(delegation); + +// The address is used as the delegate address while creating the redelegation. +const leafDelegate = "0xb4821Ab7d5942Bd2533387592068a12608B4a52C" + +const leafDelegation = createDelegation({ + to: leafDelegate, + from: delegate, + // You can also choose to pass the parent delegation object, and let function + // handle the rest. + parentDelegation: parentDelegationHash, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create an open redelegation + +An *open redelegation* is a [redelegation](#create-a-redelegation) that doesn't specify a delegate. +This means that any account can redeem the redelegation. +As with [open root delegations](#create-an-open-root-delegation), you must create open redelegations carefully, +to ensure that they are not misused. +Create an open redelegation as follows: + + + + +```typescript +import { + createDelegation, + createOpenDelegation, + getDelegationHashOffchain + } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +// The address is used as the root delegator. While creating the leaf delegation, +// the root delegate address will be the delegator address. +const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +// The authority is the (typed data) hash of the delegation from which the authority is derived. +const parentDelegationHash = getDelegationHashOffchain(delegation); + +const leafDelegation = createOpenDelegation({ + from: delegate, + // You can also choose to pass the parent delegation object, and let the function + // handle the rest. + parentDelegation: parentDelegationHash, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Sign a delegation + +A delegation must be signed by the delegator to be valid for redemption. The `MetaMaskSmartAccount` supports signing the delegation using [EIP-712 Typed Data](https://eips.ethereum.org/EIPS/eip-712) via the `signDelegation` method. +Sign a delegation as follows: + + + + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.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: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +const signature = await delegatorSmartAccount.signDelegation({ delegation }); + +const signedDelegation = { + ...delegation, + signature +}; +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); + +``` + + + \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.0/how-to/create-delegation/restrict-delegation.md b/gator_versioned_docs/version-0.10.0/how-to/create-delegation/restrict-delegation.md new file mode 100644 index 00000000000..83908aedd75 --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/how-to/create-delegation/restrict-delegation.md @@ -0,0 +1,807 @@ +--- +description: Learn how to restrict a delegation using caveat enforcers, and the available caveat types. +sidebar_position: 1 +toc_max_heading_level: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Restrict a delegation + +Use [caveat enforcers](../../concepts/caveat-enforcers.md) to apply specific rules and restrictions +to a delegation, ensuring that delegated executions are only performed under predefined circumstances. + +A delegation has a `caveats` property, which is an array of `Caveat` objects. +Each caveat is specified as follows: + +```typescript +export type Caveat = { + enforcer: Hex; // The address of the caveat enforcer contract. + terms: Hex; // Data passed to the caveat enforcer, describing how the redemption should be validated. + args: Hex; // Data that may be specified by the redeemer when redeeming the delegation (only used in limited cases). +}; +``` + +The MetaMask Delegation Toolkit provides a `CaveatBuilder` interface, which offers an intuitive way to define the `caveats` array. +Use the `CaveatBuilder` to easily ensure that your delegations grant only the necessary authority. + +## Create the caveat builder + +To create the caveat builder, call the `createCaveatBuilder()` function, passing an instance of `DeleGatorEnvironment`. +The environment can be accessed from the `MetaMaskSmartAccount`, as in this example: + +```typescript +const environment = delegatorSmartAccount.environment; + +const caveatBuilder = createCaveatBuilder(environment); +``` + +:::note +By default, the `CaveatBuilder` does not allow empty caveats. To allow the `CaveatBuilder` to build an empty caveats array, provide the following configuration: + +```typescript +const caveatBuilder = createCaveatBuilder(environment, { allowEmptyCaveats: true }); +``` +::: + +## Add caveats to the builder + +Add caveats to the builder using the `addCaveat` method, specifying the [caveat type](#caveat-types) and its parameters. You can chain multiple calls to `addCaveat` as in the following example: + +```typescript +const caveats = caveatBuilder + // allowedTargets accepts an array of addresses. + // This caveat restricts the caller to only use the delegation to interact with the specified address. + .addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92"]) + // allowedMethods accepts an array of methods. + // This caveat restricts the caller to only use the delegation to invoke the specified methods. + .addCaveat("allowedMethods", [ + "approve(address,uint256)", + "transfer(address,uint256)" + ]) + // limitedCalls accepts a number. + // This caveat restricts the caller to only use the delegation one time. + .addCaveat("limitedCalls", 1) + .build(); +``` + +
+Important considerations when using caveat enforcers +

+ +- Delegations without caveats are entirely permissive. + It is crucial to add appropriate caveats to restrict the delegated authority sufficiently. + Failing to do so could result in unintended access or actions. +- Caveat enforcers safeguard the execution process but do not guarantee a final state post-redemption. + Always combine caveat enforcers thoughtfully to create comprehensive protection. +- When using multiple caveat enforcers that modify external contract states, the order matters. + For example, if you include both [`NativeBalanceGteEnforcer`](#nativebalancegte) to ensure a balance has increased and + [`NativeTokenPaymentEnforcer`](#nativetokenpayment) to deduct from that balance, + executing `NativeTokenPaymentEnforcer` first might cause `NativeBalanceGteEnforcer` to fail validation. + Consider the sequence of enforcers carefully when creating delegations with interdependent caveats. + +

+
+ +For convenience, you can also pass the `CaveatBuilder` directly to the various helper methods for creating a delegation. For example: + +```typescript +const caveats = caveatBuilder + // allowedTargets accepts an array of addresses. + .addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92"]) + // allowedMethods accepts an array of methods. + .addCaveat("allowedMethods", [ + "approve(address,uint256)", + "transfer(address,uint256)" + ]) + // limitedCalls accepts a number. + .addCaveat("limitedCalls", 1); + +const delegation = createDelegation({ + to: delegate, + from: delegator, + caveats +}); +``` + +## Caveat types + +The `CaveatBuilder` supports various caveat types, each serving a specific purpose. +These caveat types correspond to the out-of-the-box caveat enforcers +that the MetaMask Delegation Toolkit provides. + +For more granular or custom control, you can also [create custom caveat enforcers](create-custom-caveat-enforcer.md) +and add them to the caveat builder. + +### `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 + +1. Index in the calldata byte array (including the 4-byte method selector) where the expected calldata starts +2. Expected calldata as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("allowedCalldata", + 4, + encodeAbiParameters([ + { type: "string" }, + { type: "uint256" } + ], [ + "Hello Gator", + 12345n + ]) +); +``` + +:::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 + +1. An array of methods as 4-byte hex strings, ABI function signatures, or `ABIFunction` objects + +#### Example + +```typescript +caveatBuilder.addCaveat("allowedMethods", [ + "0xa9059cbb", + "transfer(address,uint256)", + { + 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 + +1. An array of addresses as hex strings + +#### Example + +```typescript +caveatBuilder.addCaveat("allowedTargets", [ + "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 + +1. The expected `args` as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("argsEqualityCheck", + "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 + +1. After threshold block number as a `bigint` +2. Before threshold block number as a `bigint` + +You can specify `0n` to indicate that there is no limitation on a threshold. + +#### Example + +```typescript +caveatBuilder.addCaveat("blocknumber", + 19426587n, + 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 + +1. A contract address as a hex string +2. The salt to use with the contract, as a hex string +3. The bytecode of the contract as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("deployed", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x0e3e8e2381fde0e8515ed47ec9caec8ba2bc12603bc2b36133fa3e3fa4d88587", + "0x..." // The deploy bytecode for the contract at 0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92 +); +``` + +### `erc1155BalanceGte` + +Ensures the ERC-1155 balance of a specified address has increased by at least a specified amount after the execution has been performed, regardless of what the execution is. + +**Caveat enforcer contract:** [`ERC1155BalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC1155BalanceGteEnforcer.sol) + +#### Parameters + +1. An ERC-1155 contract address as a hex string +2. The recipient's address as a hex string +3. The ID of the ERC-1155 token as a bigint +4. The amount by which the balance must have increased as a bigint + +#### Example + +```typescript +caveatBuilder.addCaveat("erc1155BalanceGte", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1n, + 1_000_000n +); +``` + +### `erc20BalanceGte` + +Ensures the delegator's ERC-20 balance increases by at least the specified amount after execution, regardless of the execution. + +**Caveat enforcer contract:** [`ERC20BalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC20BalanceGteEnforcer.sol) + +#### Parameters + +1. An ERC-20 contract address as a hex string +2. Amount as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20BalanceGte", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n +); +``` + +### `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 + +1. The address of the ERC-20 token contract. +2. The maximum amount of tokens that can be transferred per period, in wei. +3. The duration of each period in seconds. +4. The timestamp when the first period begins. + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20PeriodTransfer", + "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", // Address of the ERC-20 token + 1000000000000000000n, // 1 ERC-20 token - 18 decimals, in wei + 86400, // 1 day in seconds + 1743763600, // April 4th, 2025, at 00:00:00 UTC +); +``` + +### `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 + +1. An ERC-20 contract address as a hex string +2. Initial amount available at start time as a `bigint` +3. Maximum total amount that can be unlocked as a `bigint` +4. Rate at which tokens accrue per second as a `bigint` +5. Start timestamp as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20Streaming", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n, + 10_000_000n, + 100n, + 1703980800 +); +``` + +### `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 + +1. An ERC-20 contract address as a hex string +2. Amount as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20TransferAmount", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n +); +``` + +### `erc721BalanceGte` + +Ensures the ERC-721 balance of the specified recipient address increases by at least the specified amount after execution, regardless of execution type. + +**Caveat enforcer contract:** [`ERC721BalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC721BalanceGteEnforcer.sol) + +#### Parameters + +1. An ERC-721 contract address as a hex string +2. The recipient's address as a hex string +3. The amount by which the balance must have increased as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc721BalanceGte", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n +); +``` + +### `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 + +1. The permitted ERC-721 contract address as a hex string +2. The permitted ID of the ERC-721 token as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc721Transfer", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 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 + +1. A hex value for calldata. + +#### Example + +```typescript +caveatBuilder.addCaveat("exactCalldata", + "0x1234567890abcdef" // Calldata to be matched +); +``` + +### `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 + +1. Array of expected `ExecutionStruct`, each containing target address, value, and calldata. + +#### Example + +```typescript +const executions = [ + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + callData: "0x", + }, + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 0n, + callData: "0x", + }, +]; + +caveatBuilder.addCaveat("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 + +1. `ExecutionStruct` to be expected. + +#### Example + +```typescript +caveatBuilder.addCaveat("exactExecution", { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + 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 + +1. Array of expected `ExecutionStruct`, each containing target address, value, and calldata. + +#### Example + +```typescript +const executions = [ + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + callData: "0x", + }, + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 0n, + callData: "0x", + }, +]; + +caveatBuilder.addCaveat("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 + +1. An ID as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("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 + +1. A count as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("limitedCalls", + 1 +); +``` + +### `nativeBalanceGte` + +Ensures that the recipient's native token balance has increased by at least the specified amount. + +**Caveat enforcer contract:** [`NativeBalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NativeBalanceGteEnforcer.sol) + +#### Parameters + +1. The recipient's address as a hex string +2. The amount by which the balance must have increased as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeBalanceGte", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n +); +``` + +### `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 + +1. The recipient's address as a hex string +2. The amount that must be paid as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenPayment", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n +); +``` + +### `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 + +1. The maximum amount of tokens that can be transferred per period, in wei. +2. The duration of each period in seconds. +3. The timestamp when the first period begins. + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenPeriodTransfer", + 1000000000000000000n, // 1 ETH in wei + 86400, // 1 day in seconds + 1743763600, // April 4th, 2025, at 00:00:00 UTC +) +``` + +### `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 + +1. Initial amount available at start time as a `bigint` +2. Maximum total amount that can be unlocked as a `bigint` +3. Rate at which tokens accrue per second as a `bigint` +4. Start timestamp as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenStreaming", + 1_000_000n, + 10_000_000n, + 100n, + 1703980800 +); +``` + +### `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 + +1. The allowance as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenTransferAmount", + 1_000_000n +); +``` + +### `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 + +1. A nonce as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("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 + +1. The target contract address as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("ownershipTransfer", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92" +); +``` + +### `redeemer` + +Limits the addresses that can redeem the delegation. +This caveat is designed for restricting 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 + +1. An array of addresses as hex strings + +#### Example + +```typescript +caveatBuilder.addCaveat("redeemer", + [ + "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 + +1. The address of the ERC-20 token contract. +2. The address that will receive the tokens. +3. The amount of tokens to transfer, in wei. +4. The target address for the first transaction. +5. The calldata for the first transaction. + +#### Example + +```typescript +caveatBuilder.addCaveat("specificActionERC20TransferBatch", + "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da" // Address of ERC-20 token contract + "0x027aeAFF3E5C33c4018FDD302c20a1B83aDCD96C", // Address that will receive the tokens + 1000000000000000000n, // 1 ERC-20 token - 18 decimals, in wei + "0xb49830091403f1Aa990859832767B39c25a8006B", // Target address for first transaction + "0x1234567890abcdef" // Calldata to be matched for first transaction +) +``` + +### `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 + +1. After threshold timestamp as a number +2. Before threshold timestamp as a number + +You can specify `0` to indicate that there is no limitation on a threshold. + +#### Example + +```typescript +caveatBuilder.addCaveat("timestamp", + 499165200, + 1445412480 +); +``` + +### `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 + +1. A value as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("valueLte", + 1_000_000_000_000_000_000n // 1 ETH in wei +); +``` diff --git a/gator_versioned_docs/version-0.10.0/how-to/create-delegator-account.md b/gator_versioned_docs/version-0.10.0/how-to/create-delegator-account.md new file mode 100644 index 00000000000..73e132ee5a1 --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/how-to/create-delegator-account.md @@ -0,0 +1,80 @@ +--- +description: Learn how to create a delegator account using Viem. +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a delegator account + +The MetaMask Delegation Toolkit is embedded, meaning that the end user can instantly interact with a dapp without wallet authorization, confirmations, or corporate logos. Enable users to create a [delegator account](../concepts/delegator-accounts.md) directly in your dapp. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) + +## Create a `MetaMaskSmartAccount` + +The following is an example of creating a delegator account using Viem Core SDK. +Viem Core SDK provides low-level interfaces to offer flexibility and control over the delegator +account creation lifecycle. + +In the example, the Viem [`privateKeyToAccount`](https://viem.sh/docs/accounts/privateKey.html) +function creates an externally owned account as the owner of the delegator account. + + + + +```typescript +import { publicClient, owner } from "./config.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const deploySalt = "0x"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner.address, [], [], []], + deploySalt, + signatory: { account: owner }, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); + +const privateKey = generatePrivateKey(); +export const owner = privateKeyToAccount(privateKey); +``` + + + + + +This example creates the `MetaMaskSmartAccount`, which can perform several functions: + +- In conjunction with [Viem Account Abstraction clients](configure.md), deploy the smart contract account, + and [send user operations](send-user-operation.md). +- [Sign delegations](create-delegation/index.md) that can be used to grant specific rights and permissions to other accounts. + +:::note +The example above uses the Hybrid Delegator smart contract account, which is configurable to have an EOA "owner" and any number of P256 (passkey) signers. +You can also [configure other delegator account types](configure-delegator-accounts-signers.md). +::: diff --git a/gator_versioned_docs/version-0.10.0/how-to/redeem-delegation.md b/gator_versioned_docs/version-0.10.0/how-to/redeem-delegation.md new file mode 100644 index 00000000000..23be468113e --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/how-to/redeem-delegation.md @@ -0,0 +1,347 @@ +--- +description: Learn how to redeem a delegation with a smart contract account (SCA) or an externally owned account (EOA). +sidebar_position: 7 +toc_max_heading_level: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Redeem a delegation + +A delegate can redeem a delegation by submitting either a user operation or a regular transaction, +depending on whether the delegate is a smart contract account (SCA) or externally owned account (EOA). + +The redeem transaction is sent to the `DelegationManager` contract, which validates the delegation and executes actions on the delegator's behalf. To prepare the call data for the redeem transaction, use the `redeemDelegation` utility function. The function supports batch redemption, allowing multiple delegations to be processed within a single transaction. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator account.](create-delegator-account.md) +- [Create a delegation.](create-delegation/index.md) + +## Redeem a delegation + +Redeem a delegation with a [smart contract account (SCA)](#redeem-with-an-sca) or an [externally owned account (EOA)](#redeem-with-an-eoa). + +### Redeem with an SCA + +The following example demonstrates how to submit a user operation to redeem a delegation. +It assumes you have a delegation signed by the delegator, and that the delegate is an SCA. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { bundlerClient, pimlicoClient } from "./client.ts"; +import { delegateSmartAccount } from "./account.ts"; + +const delegations: Delegation[] = [ signedDelegation ]; + +// SINGLE_DEFAULT_MODE is the default execution mode. +const mode: ExecutionMode = SINGLE_DEFAULT_MODE; + +// For SINGLE execution modes, the executions array must be length 1. +const executions: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const { fast: fee } = await pimlicoClient.getUserOperationGasPrice(); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: "", + data: redeemDelegationCalldata + } + ], + ...fee +}); +``` + + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +improt { publicClient } from "./client.ts" + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { createPimlicoClient } from "permissionless/clients/pimlico"; + +// You can get the API Key from the Pimlico dashboard. +const pimlicoUrl = "https://api.pimlico.io/v2/59141/rpc"; + +export const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http(pimlicoUrl), +}); + +export const pimlicoClient = createPimlicoClient({ + transport: http(pimlicoUrl), +}); +``` + + + + +### Redeem with an EOA + +The following example demonstrates how to submit a transaction to redeem a delegation. It assumes you have a delegation signed by the delegator, and that the delegate is an EOA. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { lineaSepolia as chain } from "viem/chains"; +import { delegateWalletClient } from "./account.ts"; + +const delegations: Delegation[] = [ signedDelegation ]; + +// SINGLE_DEFAULT_MODE is the default execution mode. +const mode: ExecutionMode = SINGLE_DEFAULT_MODE; + +// For SINGLE execution modes, the executions array must be length 1. +// Modify the executions to fit your use case. +const executions: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const transactionHash = await walletClient.sendTransaction({ + to: getDeleGatorEnvironment(chain.id).DelegationManager, + data: redeemDelegationCalldata, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { createWalletClient, http } from "viem"; + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateWalletClient = createWalletClient({ + account: delegateAccount, + chain, + transport: http(), +}) +``` + + + + +## Redeem multiple delegations + +You can redeem multiple delegations in a single user operation, each delegation independent of the others. +Each element in the `delegationsArray` must have a corresponding element in the `executionsArray` and `modes`. + +The following example assumes you already have multiple signed delegations and that the delegate is an SCA. +The preparation of the call data is the same when [using an EOA as the delegate](#redeem-with-an-eoa); +the primary difference is that an EOA submits a regular transaction instead of a user operation. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { bundlerClient, pimlicoClient } from "./client.ts"; +import { delegateSmartAccount } from "./account.ts"; + +const delegationsArray: Delegation[][] = [ + [ signedDelegation1 ] + [ signedDelegation2 ] + [ signedDelegation3 ] +]; + +const modes: ExecutionMode = [ + SINGLE_DEFAULT_MODE, + SINGLE_DEFAULT_MODE, + SINGLE_DEFAULT_MODE +]; + +const execution: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +// Modify the executions to fit your use case. For simplicity, we've +// included a basic example. The execution array can contain +// multiple different executions. +const executionsArray: ExecutionStruct:[][] = [ + execution, + execution, + execution +]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const { fast: fee } = await pimlicoClient.getUserOperationGasPrice(); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: "", + data: redeemDelegationCalldata + } + ], + ...fee +}); +``` + + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +import { publicClient } from "./client.ts"; + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { createPimlicoClient } from "permissionless/clients/pimlico"; + +// You can get the API Key from the Pimlico dashboard. +const pimlicoUrl = "https://api.pimlico.io/v2/59141/rpc"; + +export const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http(pimlicoUrl), +}); + +export const pimlicoClient = createPimlicoClient({ + transport: http(pimlicoUrl), +}); +``` + + + + +## Execution modes + +The Delegation Toolkit supports several execution modes based on [ERC-7579](https://erc7579.com/). +See the [ERC implementation](https://github.com/erc7579/erc7579-implementation/blob/main/src/lib/ModeLib.sol) for more details about the execution modes. + +The supported execution modes are `SINGLE_DEFAULT_MODE`, `SINGLE_TRY_MODE`, `BATCH_DEFAULT_MODE`, and `BATCH_TRY_MODE`. + +### `SINGLE` execution modes + +In `SINGLE` execution modes, only a single delegation chain and a single execution can be provided. This mode processes delegations sequentially: + +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. + +### `BATCH` execution modes + +In `BATCH` execution modes, multiple delegation chains and multiple executions can be provided. This mode executes delegations in an interleaved way: + +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. + +### `DEFAULT` modes + +In `DEFAULT` modes, if a revert occurs during redemption, the entire user operation reverts at that point. + +### `TRY` modes + +In `TRY` modes, if a revert occurs during redemption, execution of the user operation continues. diff --git a/gator_versioned_docs/version-0.10.0/how-to/send-user-operation.md b/gator_versioned_docs/version-0.10.0/how-to/send-user-operation.md new file mode 100644 index 00000000000..428d27bfe88 --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/how-to/send-user-operation.md @@ -0,0 +1,176 @@ +--- +description: Learn how to send an ERC-4337 user operation using Viem. +sidebar_position: 4 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# 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 Delegation Toolkit 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 smart contract account that has not been deployed, the toolkit configures the user operation to automatically deploy the account. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator account.](create-delegator-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 +import { bundlerClient, smartAccount } from "./config.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, + maxPriortyFeePerGas +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { createBundlerClient } from "viem/account-abstraction"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +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", + signatory: { account }, +}); + +export const bundlerClient = createBundlerClient({ + publicClient, + transport: http("https://public.pimlico.io/v2/1/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). + +```typescript title="example.ts" +// add-next-line ++ import { createPimlicoClient } from "permissionless/clients/pimlico"; +import { bundlerClient, smartAccount } from "./config.ts" // The config.ts is the same as in the previous example. + +// remove-start +- const maxFeePerGas = 1n; +- const maxPriorityFeePerGas = 1n; +// remove-end + +// add-start ++ const pimlicoClient = createPimlicoClient({ ++ transport: http("https://api.pimlico.io/v2/59141/rpc"), // 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, +- maxPriortyFeePerGas + // 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" +import { createPimlicoClient } from "permissionless/clients/pimlico"; +import { bundlerClient, smartAccount } from "./config.ts" // The config.ts is the same as in the previous example. + +const pimlicoClient = createPimlicoClient({ + transport: http("https://api.pimlico.io/v2/59141/rpc"), // 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 +``` diff --git a/gator_versioned_docs/version-0.10.0/index.md b/gator_versioned_docs/version-0.10.0/index.md new file mode 100644 index 00000000000..3cdab43dc69 --- /dev/null +++ b/gator_versioned_docs/version-0.10.0/index.md @@ -0,0 +1,63 @@ +--- +title: Introduction to the MetaMask Delegation Toolkit +sidebar_label: Introduction +description: High-level overview of the Delegation Toolkit, its benefits, and where to start in the documentation. +sidebar_position: 1 +--- + +import CardList from "@site/src/components/CardList" + +# MetaMask Delegation Toolkit documentation + +## Why use the toolkit? + +The MetaMask Delegation Toolkit enables developers to create frictionless new experiences based +on granular permission sharing and trust. +The toolkit offers a suite of contracts, libraries, and services designed for maximum composability, +allowing developers to build and extend their dapps with ease. +The toolkit enables: + +- **Instant user onboarding.** Provide frictionless onboarding with no browser extension, mobile + app, or seed phrase required. + +- **New web3 experiences.** Unlock new experiences such as peer-to-peer social + coordination using incentive trees, or recurring subscription payments that don't require users + to connect to the dapp. + +- **Uninterrupted user experiences.** Keep users immersed in the dapp by embedding the wallet + experience and reassigning gas costs to where they make sense. + +The toolkit includes the [Delegation Framework](concepts/delegation.md#delegation-framework) – a +pioneering set of open-source, customizable smart contracts, allowing dapps and protocols to +implement custom permission control. +Developers can use the Delegation Framework to prepare their dapps for +[delegations](concepts/delegation.md) created from +[delegator accounts](concepts/delegator-accounts.md). + +## Where do I start? + +Check out the following sections to get started with the MetaMask Delegation Toolkit: + + + +## Questions? + +If you have questions, email hellogators@consensys.net. diff --git a/gator_versioned_docs/version-0.10.1/changelog/0.10.0.md b/gator_versioned_docs/version-0.10.1/changelog/0.10.0.md new file mode 100644 index 00000000000..7493bd17dee --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/changelog/0.10.0.md @@ -0,0 +1,105 @@ +--- +sidebar_label: 0.10.0 +sidebar_position: 2 +description: MetaMask Delegation Toolkit v0.10.0 changelog +--- + +# What's new in v0.10.0? + +:::warning Breaking changes +The ⚠️ symbol denotes potentially breaking API changes. +As per the [semantic versioning specification](https://semver.org/#spec-item-4), from v1.0.0 onwards, +breaking changes will be released only in major version bumps. +::: + +## Enhancements + +### ⚠️ ERC-7715 permission request +- Removed `required` parameter. +- Added the `isAdjustmentAllowed` parameter, allowing user to modify the permission request as needed. + +```typescript +const permissions = { + chainId: '0x7a69', + address: bob.address, + expiry: 1234567890, + permission: { + type: 'native-token-stream', + data: { + amountPerSecond: '0x1', + maxAmount: '0x2', + initialAmount: undefined, + startTime: 2, + justification: 'Test justification', + }, + }, + // remove-next-line +- required: false + // add-next-line ++ isAdjustmentAllowed: true, + signer: { + type: 'account', + data: { + address: alice.address, + }, + }, +} +``` + +## Contract addresses + +The following are the contract addresses for the +[Delegation Framework version 1.3.0](https://github.com/MetaMask/delegation-framework/blob/v1.3.0/documents/Deployments.md), +as used by this version of the toolkit. + +### Delegation Framework + +| Contract | Address | +|----------|---------| +| EntryPoint | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | +| SimpleFactory | `0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c` | +| DelegationManager | `0x739309deED0Ae184E66a427ACa432aE1D91d022e` | +| MultiSigDeleGatorImpl | `0xB4ab520FF1761f7b6dc221fEaCaf79367629Ed12` | +| HybridDeleGatorImpl | `0xf4E57F579ad8169D0d4Da7AedF71AC3f83e8D2b4` | + +### Caveat enforcers + +| Enforcer | Address | +|----------|---------| +| AllowedCalldataEnforcer | `0x1021300501f6aDc446d4e506053F55a8a63cB1d7` | +| AllowedMethodsEnforcer | `0x371f95c92Be3A916B824A2aE086Ed6db7A6193Fb` | +| AllowedTargetsEnforcer | `0x91e043a13c61f9ddC02BDfe38dCA02A7F5b7Cc88` | +| ArgsEqualityCheckEnforcer | `0xACEC09a804020B307eFF00df9AAfb1Cf656DF9Cf` | +| BlockNumberEnforcer | `0x955C7732562c6Dc4760dF749440f3ab28F46F608` | +| DeployedEnforcer | `0xd2c8c04E2070c13CCB97FEAa25D1915676AAC191` | +| ERC20BalanceGteEnforcer | `0x262A37B51798c44F1BEAd1A076703E4488887b78` | +| ERC20TransferAmountEnforcer | `0x272E92835B12F014353E8754808C67682e9dddFA` | +| ERC721BalanceGteEnforcer | `0xd0960BC7324235DE4FAFe0f2eDCff64313220CC8` | +| ERC721TransferEnforcer | `0x82621E65240f67D8F60a920F709127743A8D20A9` | +| ERC1155BalanceGteEnforcer | `0x01a84C60B0B5c3EbB504fDa60a8236eB7e2D6655` | +| IdEnforcer | `0xd6403989C2cc145102c2AE76E70D1317947ef587` | +| LimitedCallsEnforcer | `0xA45dd3D90447640eB76085637132a74E18b310E3` | +| NonceEnforcer | `0x1ba53a54eDa7021E08065C1C1943bCE91e0FceA3` | +| TimestampEnforcer | `0xABc2591a40db08eD7045D91A29B3DBC33082DB54` | +| ValueLteEnforcer | `0x9C458b17Cd6570e322Ee9a4180b309dAFD08e24C` | +| NativeTokenTransferAmountEnforcer | `0xcfD1BD7922D123Caa194597BF7A0073899a284Df` | +| NativeBalanceGteEnforcer | `0xDb5BAF405159f47Ab70DD424021ef114A450E101` | +| NativeTokenPaymentEnforcer | `0x6e21bABB3779bc694DC3A4DCeB35C1ecC1d9087b` | +| OwnershipTransferEnforcer | `0x5f263261676d24Dd146545F22E485708900B2B83` | +| RedeemerEnforcer | `0x596CC67C2Da64ED51E27B6d61f46e3F687E9182d` | + +### Supported mainnet networks +- Ethereum +- Polygon +- Binance Smart Chain +- Optimism +- Arbitrum +- Linea +- Base +- Gnosis Chain + +### Supported testnet networks +- Ethereum Sepolia +- Linea Sepolia +- Base Sepolia +- MegaEth \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.1/changelog/0.10.1.md b/gator_versioned_docs/version-0.10.1/changelog/0.10.1.md new file mode 100644 index 00000000000..86170524e68 --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/changelog/0.10.1.md @@ -0,0 +1,69 @@ +--- +sidebar_label: 0.10.1 +sidebar_position: 1 +description: MetaMask Delegation Toolkit v0.10.1 changelog +--- + +# What's new in v0.10.1? + +## Bug fixes + +- Fixes an issue where the ERC-7715 `PermissionRequest` object could not be serialized when `maxAmount` or `initialAmount` was set to `null`. + +## Contract addresses + +The following are the contract addresses for the +[Delegation Framework version 1.3.0](https://github.com/MetaMask/delegation-framework/blob/v1.3.0/documents/Deployments.md), +as used by this version of the toolkit. + +### Delegation Framework + +| Contract | Address | +|----------|---------| +| EntryPoint | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | +| SimpleFactory | `0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c` | +| DelegationManager | `0x739309deED0Ae184E66a427ACa432aE1D91d022e` | +| MultiSigDeleGatorImpl | `0xB4ab520FF1761f7b6dc221fEaCaf79367629Ed12` | +| HybridDeleGatorImpl | `0xf4E57F579ad8169D0d4Da7AedF71AC3f83e8D2b4` | + +### Caveat enforcers + +| Enforcer | Address | +|----------|---------| +| AllowedCalldataEnforcer | `0x1021300501f6aDc446d4e506053F55a8a63cB1d7` | +| AllowedMethodsEnforcer | `0x371f95c92Be3A916B824A2aE086Ed6db7A6193Fb` | +| AllowedTargetsEnforcer | `0x91e043a13c61f9ddC02BDfe38dCA02A7F5b7Cc88` | +| ArgsEqualityCheckEnforcer | `0xACEC09a804020B307eFF00df9AAfb1Cf656DF9Cf` | +| BlockNumberEnforcer | `0x955C7732562c6Dc4760dF749440f3ab28F46F608` | +| DeployedEnforcer | `0xd2c8c04E2070c13CCB97FEAa25D1915676AAC191` | +| ERC20BalanceGteEnforcer | `0x262A37B51798c44F1BEAd1A076703E4488887b78` | +| ERC20TransferAmountEnforcer | `0x272E92835B12F014353E8754808C67682e9dddFA` | +| ERC721BalanceGteEnforcer | `0xd0960BC7324235DE4FAFe0f2eDCff64313220CC8` | +| ERC721TransferEnforcer | `0x82621E65240f67D8F60a920F709127743A8D20A9` | +| ERC1155BalanceGteEnforcer | `0x01a84C60B0B5c3EbB504fDa60a8236eB7e2D6655` | +| IdEnforcer | `0xd6403989C2cc145102c2AE76E70D1317947ef587` | +| LimitedCallsEnforcer | `0xA45dd3D90447640eB76085637132a74E18b310E3` | +| NonceEnforcer | `0x1ba53a54eDa7021E08065C1C1943bCE91e0FceA3` | +| TimestampEnforcer | `0xABc2591a40db08eD7045D91A29B3DBC33082DB54` | +| ValueLteEnforcer | `0x9C458b17Cd6570e322Ee9a4180b309dAFD08e24C` | +| NativeTokenTransferAmountEnforcer | `0xcfD1BD7922D123Caa194597BF7A0073899a284Df` | +| NativeBalanceGteEnforcer | `0xDb5BAF405159f47Ab70DD424021ef114A450E101` | +| NativeTokenPaymentEnforcer | `0x6e21bABB3779bc694DC3A4DCeB35C1ecC1d9087b` | +| OwnershipTransferEnforcer | `0x5f263261676d24Dd146545F22E485708900B2B83` | +| RedeemerEnforcer | `0x596CC67C2Da64ED51E27B6d61f46e3F687E9182d` | + +### Supported mainnet networks +- Ethereum +- Polygon +- Binance Smart Chain +- Optimism +- Arbitrum +- Linea +- Base +- Gnosis Chain + +### Supported testnet networks +- Ethereum Sepolia +- Linea Sepolia +- Base Sepolia +- MegaEth \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.1/changelog/0.9.0.md b/gator_versioned_docs/version-0.10.1/changelog/0.9.0.md new file mode 100644 index 00000000000..aa401a0512e --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/changelog/0.9.0.md @@ -0,0 +1,108 @@ +--- +sidebar_label: 0.9.0 +sidebar_position: 3 +description: MetaMask Delegation Toolkit v0.9.0 changelog +--- + +# What's new in v0.9.0? + +:::warning Breaking changes +The ⚠️ symbol denotes potentially breaking API changes. +As per the [semantic versioning specification](https://semver.org/#spec-item-4), from v1.0.0 onwards, +breaking changes will be released only in major version bumps. +::: + +## Breaking Changes + +### ⚠️ Package references + +- Moves the package from `@metamask-private` to `@metamask` organization. +- Renames the `delegator-core-viem` entrypoint to `delegation-toolkit`. + +```typescript +// remove-next-line +- import { toMetaMaskSmartAccount } from "@metamask-private/delegator-core-viem"; +// add-next-line ++ import { toMetaMaskSmartAccount } from "@metamask/delegation-toolkit"; +``` + +### ⚠️ SimpleFactory address + +Updates the `SimpleFactory` contract address. Please note, this will result in a change to the smart account address for `MetaMaskSmartAccount`. + +```typescript +// Delegation enviroment for 1.3.0 +export const deployment_1_3_0 = { +//... +// remove-next-line +- SimpleFactory: '0x6ff518884f21168c30c58CB21184D6AdBC18Ad90', +// add-next-line ++ SimpleFactory: '0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c', +//... +} + +``` + +## Enhancements + +### ⚠️ Package installation + +Package installation no longer requires an authentication token, as it has transitioned out of private alpha. 🎉 + +## Contract addresses + +The following are the contract addresses for the +[Delegation Framework version 1.3.0](https://github.com/MetaMask/delegation-framework/blob/v1.3.0/documents/Deployments.md), +as used by this version of the toolkit. + +### Delegation Framework + +| Contract | Address | +|----------|---------| +| EntryPoint | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | +| SimpleFactory | `0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c` | +| DelegationManager | `0x739309deED0Ae184E66a427ACa432aE1D91d022e` | +| MultiSigDeleGatorImpl | `0xB4ab520FF1761f7b6dc221fEaCaf79367629Ed12` | +| HybridDeleGatorImpl | `0xf4E57F579ad8169D0d4Da7AedF71AC3f83e8D2b4` | + +### Caveat enforcers + +| Enforcer | Address | +|----------|---------| +| AllowedCalldataEnforcer | `0x1021300501f6aDc446d4e506053F55a8a63cB1d7` | +| AllowedMethodsEnforcer | `0x371f95c92Be3A916B824A2aE086Ed6db7A6193Fb` | +| AllowedTargetsEnforcer | `0x91e043a13c61f9ddC02BDfe38dCA02A7F5b7Cc88` | +| ArgsEqualityCheckEnforcer | `0xACEC09a804020B307eFF00df9AAfb1Cf656DF9Cf` | +| BlockNumberEnforcer | `0x955C7732562c6Dc4760dF749440f3ab28F46F608` | +| DeployedEnforcer | `0xd2c8c04E2070c13CCB97FEAa25D1915676AAC191` | +| ERC20BalanceGteEnforcer | `0x262A37B51798c44F1BEAd1A076703E4488887b78` | +| ERC20TransferAmountEnforcer | `0x272E92835B12F014353E8754808C67682e9dddFA` | +| ERC721BalanceGteEnforcer | `0xd0960BC7324235DE4FAFe0f2eDCff64313220CC8` | +| ERC721TransferEnforcer | `0x82621E65240f67D8F60a920F709127743A8D20A9` | +| ERC1155BalanceGteEnforcer | `0x01a84C60B0B5c3EbB504fDa60a8236eB7e2D6655` | +| IdEnforcer | `0xd6403989C2cc145102c2AE76E70D1317947ef587` | +| LimitedCallsEnforcer | `0xA45dd3D90447640eB76085637132a74E18b310E3` | +| NonceEnforcer | `0x1ba53a54eDa7021E08065C1C1943bCE91e0FceA3` | +| TimestampEnforcer | `0xABc2591a40db08eD7045D91A29B3DBC33082DB54` | +| ValueLteEnforcer | `0x9C458b17Cd6570e322Ee9a4180b309dAFD08e24C` | +| NativeTokenTransferAmountEnforcer | `0xcfD1BD7922D123Caa194597BF7A0073899a284Df` | +| NativeBalanceGteEnforcer | `0xDb5BAF405159f47Ab70DD424021ef114A450E101` | +| NativeTokenPaymentEnforcer | `0x6e21bABB3779bc694DC3A4DCeB35C1ecC1d9087b` | +| OwnershipTransferEnforcer | `0x5f263261676d24Dd146545F22E485708900B2B83` | +| RedeemerEnforcer | `0x596CC67C2Da64ED51E27B6d61f46e3F687E9182d` | + +### Supported mainnet networks +- Ethereum +- Polygon +- Binance Smart Chain +- Optimism +- Arbitrum +- Linea +- Base +- Gnosis Chain + +### Supported testnet networks +- Ethereum Sepolia +- Linea Sepolia +- Base Sepolia +- MegaEth \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.1/changelog/_category_.json b/gator_versioned_docs/version-0.10.1/changelog/_category_.json new file mode 100644 index 00000000000..f33608bfdf1 --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/changelog/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Changelog" +} diff --git a/gator_versioned_docs/version-0.10.1/concepts/_category_.json b/gator_versioned_docs/version-0.10.1/concepts/_category_.json new file mode 100644 index 00000000000..122abce7702 --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/concepts/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Concepts", + "position": 3, + "link": { + "type": "generated-index", + "slug": "/concepts", + "title": "Delegation Toolkit concepts" + } +} diff --git a/gator_versioned_docs/version-0.10.1/concepts/caveat-enforcers.md b/gator_versioned_docs/version-0.10.1/concepts/caveat-enforcers.md new file mode 100644 index 00000000000..b394cb4870d --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/concepts/caveat-enforcers.md @@ -0,0 +1,185 @@ +--- +description: Learn about caveat enforcers and how they restrict delegations. +sidebar_position: 4 +--- + +# Caveat enforcers + +The MetaMask Delegation Toolkit 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](delegation.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; + +import { ModeCode } from "../utils/Types.sol"; + +/** + * 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`](../how-to/create-delegation/restrict-delegation.md) interface in the MetaMask Delegation Toolkit. + +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](../how-to/create-delegation/restrict-delegation.md#caveat-types) 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`](../how-to/create-delegation/restrict-delegation.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`](../how-to/create-delegation/restrict-delegation.md#nativetokenpayment) before + [`NativeBalanceGteEnforcer`](../how-to/create-delegation/restrict-delegation.md#nativebalancegte) 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 Delegation Toolkit provides [many out-of-the-box caveat enforcers](../how-to/create-delegation/restrict-delegation.md#caveat-types) +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 more complex scenarios, you can also [create custom caveat enforcers](../how-to/create-delegation/create-custom-caveat-enforcer.md) by implementing the `ICaveatEnforcer` interface. + +## Attenuating authority with redelegations + +When [creating chains of delegations](../how-to/create-delegation/index.md#create-a-redelegation), 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. diff --git a/gator_versioned_docs/version-0.10.1/concepts/delegation.md b/gator_versioned_docs/version-0.10.1/concepts/delegation.md new file mode 100644 index 00000000000..6c07b28bb2d --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/concepts/delegation.md @@ -0,0 +1,90 @@ +--- +description: Learn about delegation, the delegation lifecycle, and the Delegation Framework. +sidebar_position: 2 +--- + +# Delegation + +*Delegation* is the ability for a [delegator account](delegator-accounts.md) to grant permission to another smart contract account (SCA) +or externally owned account (EOA) to perform specific executions on the delegator's behalf, under defined rules and restrictions. + +The MetaMask Delegation Toolkit includes the following delegation features: + +- **Caveats** - 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. + +- **Chain of delegations** - Users can redelegate permissions that have been delegated to them, creating a chain of delegations across trusted parties. + +Delegations are created using the `Delegation` type, which is specified as follows: + +```typescript +export type Delegation = { + delegate: Hex; // The address to which the delegation is being granted. + delegator: Hex; // The address that is granting the delegation. + authority: Hex; // Hash of the parent delegation, or the constant ROOT_AUTHORITY. + caveats: Caveat[]; // Caveats that restrict the authority being granted. + salt: Hex; // Used to avoid hash collisions between identical delegations. + signature: Hex; // Signature from the delegator account. +}; +``` + +## Delegation lifecycle + +The delegation lifecycle is as follows: + +1. **Delegation creation** - A delegation is initialized, and the delegator account signs it. + +2. **Caveat enforcement** - The caveats applied to the delegation specify conditions under which + the delegation can be redeemed. + +3. **Delegation storage** - The delegation can be stored, enabling retrieval for future redemption. + + :::note + [Storing and retrieving delegations](../experimental/store-retrieve-delegations.md) using the toolkit's + `DelegationStorageClient` is an experimental feature. + ::: + +4. **Delegation redemption** - The delegate (the account being granted the permission) redeems the + delegation through an [ERC-4337 user operation](delegator-accounts.md#account-abstraction-erc-4337), + which verifies that the delegated authority is valid in order to perform the execution. + +See [how to create a delegation](../how-to/create-delegation/index.md) to get started with the +delegation lifecycle. + +## Delegation Framework + +The MetaMask Delegation Toolkit 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** - There are [multiple delegator account implementations](delegator-accounts.md#delegator-account-types), + with the main difference being the signature scheme 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 a delegation is redeemed, 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 `permissionContexts`, `modes`, and + `executionCallDatas` match, or throws `BatchDataLengthMismatch`. + 2. Decodes and validates the delegation, checking that the caller (`msg.sender`) is the delegate + and that there are no empty signatures, or throws `InvalidDelegate`. + 3. Verifies delegation signatures, ensuring validity using `ECDSA` (for EOAs) or + `isValidSignature` (for contracts), or throws `InvalidSignature`. + 4. Validates the delegation chain's authority and ensures delegations are not disabled. + 5. Executes the `beforeHook` for each `caveat` 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. diff --git a/gator_versioned_docs/version-0.10.1/concepts/delegator-accounts.md b/gator_versioned_docs/version-0.10.1/concepts/delegator-accounts.md new file mode 100644 index 00000000000..60448c2b7d6 --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/concepts/delegator-accounts.md @@ -0,0 +1,79 @@ +--- +description: Learn about account abstraction, the delegator account flow, and account types. +sidebar_position: 1 +--- + +# Delegator accounts + +The MetaMask Delegation Toolkit enables you to create and manage *delegator accounts*. +Delegator accounts are [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) smart contract accounts (SCAs) +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 delegator +accounts use smart contracts to govern account logic. + +## 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 SCAs containing arbitrary verification logic. +ERC-4337 enables SCAs 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 SCA 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 SCAs to facilitate gas abstraction. + +## Delegator account flow + +The MetaMask delegator account flow is as follows: + +1. **Account setup** - A user creates an SCA by deploying a smart contract, and initializing it with + ownership and security settings. + The user can customize the SCA 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 signatory. + +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 account types + +The MetaMask Delegation Toolkit supports two types of delegator accounts, each offering unique features and use cases. +See [Configure accounts and signers](../how-to/configure-delegator-accounts-signers.md) to learn how to use these different account types. + +### Hybrid Delegator + +The Hybrid Delegator is a flexible implementation that supports both an externally owned account (EOA) "owner" and any number of P256 (passkey) signers. +You can configure any of these signers as the signatory, and use them to sign on behalf of the delegator. + +This type is referenced in the toolkit as `Implementation.Hybrid`. + +### Multisig Delegator + +The Multisig Delegator is an implementation that supports multiple signers with a configurable threshold for valid signatures, allowing for enhanced security and flexibility in account management. The signatory must have at least as many signers include as the threshold is configured for the account. + +This type is referenced in the Toolkit as `Implementation.Multisig`. diff --git a/gator_versioned_docs/version-0.10.1/concepts/environment.md b/gator_versioned_docs/version-0.10.1/concepts/environment.md new file mode 100644 index 00000000000..c169c2ee302 --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/concepts/environment.md @@ -0,0 +1,228 @@ +--- +description: Learn about the delegator environment object `DeleGatorEnvironment` and how to use it. +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Delegator environment + +The `DeleGatorEnvironment` object is a component of the MetaMask Delegation Toolkit that defines the contract addresses necessary for interacting with the [Delegation Framework](delegation.md#delegation-framework) on a specific network. + +The delegator environment 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 delegator environment + +When you create a [`MetaMaskSmartAccount`](../how-to/create-delegator-account.md) instance, the Delegation 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 +import { DeleGatorEnvironment } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +const environment: DeleGatorEnvironment = delegatorSmartAccount.environment; +``` + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +const delegatorAccount = privateKeyToAccount("0x..."); + +const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegatorAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegatorAccount }, +}); + +export delegatorSmartAccount; +``` + + + + +:::note +See the [changelog](../changelog/0.10.1.md) of the toolkit version you are using for supported chains. +::: + +Alternatively, you can use the `getDelegatorEnvironment` function to resolve the environment. +This function is especially useful if your delegator is not a smart contract account when +[creating a redelegation](../how-to/create-delegation/index.md#create-a-redelegation). + +```typescript +import { + getDeleGatorEnvironment, + DeleGatorEnvironment, +} from "@metamask/delegation-toolkit"; + +// Resolves the DeleGatorEnvironment for Linea Sepolia +const environment: DeleGatorEnvironment = getDelegatorEnvironment(59141); +``` + +## Deploy custom delegator environment + +You can deploy the contracts using any method, but the toolkit provides a convenient `deployDelegatorEnvironment` 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.html), [Wallet Client](https://viem.sh/docs/clients/wallet.html), and [Chain](https://viem.sh/docs/glossary/types#chain) +to deploy the contracts and resolve the `DeleGatorEnvironment`. + +Your wallet must have sufficient native token balance to deploy the contracts. + + + + +```typescript +import { walletClient, publicClient } from "./config.ts"; +import { lineaSepolia as chain } from "viem/chains"; + +const environment = await deployDeleGatorEnvironment( + walletClient, + publicClient, + chain +); +``` + + + + +```typescript +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient, createPublicClient } from "viem"; + +// 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 `deployDelegatorEnvironment`. +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. +import { walletClient, publicClient } from "./config.ts"; +import { lineaSepolia as chain } from "viem/chains"; + +const environment = await deployDeleGatorEnvironment( + walletClient, + publicClient, + chain, + // add-start ++ { ++ EntryPoint: "0x0000000071727De22E5E9d8BAf0edAc6f37da032" ++ } + // add-end +); +``` + +Once the contracts are deployed, you can use them to override the delegator environment. + +## Override delegator environment + +To override the delegator environment, the toolkit provides a `overrideDeployedEnvironment` function to resolve +`DeleGatorEnvironment` with specified contracts for the given chain and contract version. + +```typescript +// The config.ts is the same as in the previous example. +import { walletClient, publicClient } from "./config.ts"; +import { lineaSepolia as chain } from "viem/chains"; +import { + DeleGatorEnvironment, + overrideDeployedEnvironment, + deployDeleGatorEnvironment +} from "@metamask/delegation-toolkit"; + +const environment: DeleGatorEnvironment = await deployDeleGatorEnvironment( + walletClient, + publicClient, + chain +); + +const delegatorEnvironment: DeleGatorEnvironment = overrideDeployedEnvironment( + chainId, + "1.3.0", + environment, +); +``` + +If you've already deployed the contracts using a different method, you can create a `DelegatorEnvironment` instance with the required contract addresses, and pass it to the function. + +```typescript +// remove-start +- import { walletClient, publicClient } from "./config.ts"; +- import { lineaSepolia as chain } from "viem/chains"; +// remove-end +import { + DeleGatorEnvironment, + overrideDeployedEnvironment, + // remove-next-line +- deployDeleGatorEnvironment +} from "@metamask/delegation-toolkit"; + +// remove-start +- const environment: DeleGatorEnvironment = await deployDeleGatorEnvironment( +- walletClient, +- publicClient, +- chain +- ); +// remove-end + +// add-start ++ const evniroment: DeleGatorEnvironment = { ++ SimpleFactory: "0x124..", ++ // ... ++ implementations: { ++ // ... ++ }, ++ }; +// add-end + +const delegatorEnvironment: DeleGatorEnvironment = overrideDeployedEnvironment( + chainId, + "1.3.0", + environment +); +``` + +:::note +Make sure to specify the Delegation Framework version required by the toolkit. +See the [changelog](../changelog/0.10.1.md) of the toolkit version you are using for its required Framework version. +::: diff --git a/gator_versioned_docs/version-0.10.1/experimental/_category_.json b/gator_versioned_docs/version-0.10.1/experimental/_category_.json new file mode 100644 index 00000000000..5661f467b80 --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/experimental/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Experimental", + "position": 5, + "link": { + "type": "generated-index", + "slug": "/experimental", + "title": "Delegation Toolkit experimental features" + } +} diff --git a/gator_versioned_docs/version-0.10.1/experimental/erc-7710-redeem-delegations.md b/gator_versioned_docs/version-0.10.1/experimental/erc-7710-redeem-delegations.md new file mode 100644 index 00000000000..85325565000 --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/experimental/erc-7710-redeem-delegations.md @@ -0,0 +1,237 @@ +--- +description: Learn how to redeem ERC-7710 delegations with a smart contract account or an externally owned account (EOA). +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# ERC-7710: Redeem delegations + +:::caution Experimental +This is an experimental feature and may change in future releases. +::: + +[ERC-7710](https://eip.tools/eip/7710) introduces a standard way for smart contract accounts (SCAs) to delegate capabilities to other +SCAs or externally owned accounts (EOAs). + +The MetaMask Delegation Toolkit provides two experimental functions, `erc7710BundlerActions()` and `erc7710WalletActions()`, that let +a caller redeem delegations granted by MetaMask's permissions system. + +## Extract relevant data + +Refer to [ERC-7715: Request permissions](erc-7715-request-permissions.md) for information on how to request user permissions. +Once the permission has been granted, extract the relevant data from the response. +For example: + +```typescript +// Response received from the ERC-7715 wallet_grantPermissions request. +const permissionsResponse = [{ + chainId: "0xe715", + account: "0xD6f56C2B10b1e02D841E4a97c60Afe914E884DBd", + expiry: 1234567890, + permission: { + type: "native-token-stream", + data: { + amountPerSecond: "0x1", + maxAmount: "0x2", + initialAmount: undefined, + startTime: 2, + }, + }, + context: "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d" + signer: { + type: "account", + data: { + account: "0x07bfc7230D5BD2544059816D88A895BB000Abe00" + } + }, + signerMeta: { + delegationManager: "0xDC7e12b41E5e61BfCc7F56AAFB7B93288F61e841" + }, + accountMetadata: [{ + factory: "0x65E726b404149fE37F4b291c81Dc6eddd44763A7", + factoryData: "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b" + }] +}]; + +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +// accountMeta is only present when the smart contract account is not deployed. +const accountMetadata = permissionsResponse[0].accountMeta; +``` + +This data encodes the authority that lets the delegate redeem the permission. + +### Security considerations for `accountMeta` + +When a user grants a permission, they can provide `accountMeta` which is an array of `factory` and `factoryData` values. +These calls must be executed before redeeming the permission (this is handled for you in `sendUserOperationWithDelegation`). + +Because each `accountMeta` is an arbitrary call specified by the granter, it is important that these are executed carefully. +We recommend taking the following precautions: + +- **Only grant permissions to session accounts** - When requesting permissions, use an account that is only used for that single purpose, and does not contain tokens. +This way, any `accountMeta` executed can't perform any damaging actions. + +- **Only execute `accountMeta` against trusted factory addresses** - Ensure that only `accountMeta` targeting a known factory address is executed. +The bundler action `sendUserOperationWithDelegation` only executes `accountMeta` that targets the `SimpleFactory` address for the current Delegation Framework. +If you redeem delegations in any other way, it is your responsibility to validate trusted factory addresses. + +## Redeem the permission + +Redeem a delegation using one of two methods. Choose the method based on your account type: + +- If redeeming with an SCA, call `sendUserOperationWithDelegation`. +- If redeeming with an EOA, call `sendTransactionWithDelegation`. + +### Redeem with an SCA + +To redeem a delegation with a smart contract account, create a [`MetaMaskSmartAccount`](../how-to/create-delegator-account.md#create-a-metamasksmartaccount) +and a [Viem Bundler Client](https://viem.sh/account-abstraction/clients/bundler). + +After setting up your Bundler Client, you can extend its functionality with `erc7710BundlerActions` actions to support ERC-7710. Once extended, use `sendUserOperationWithDelegation` to redeem the permission. + + + + +```typescript +import { sessionAccount, bundlerClient, publicClient } from "./config.ts"; + +// These properties must be extracted from the permission response. +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +const accountMetadata = permissionsResponse[0].accountMeta; + +// 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 + accountMetadata, +}); +``` + + + + +```typescript +import { createPublicClient, http, createBundlerClient } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { erc7710BundlerActions } from "@metamask/delegation-toolkit/experimental"; +import { toMetaMaskSmartAccount, Implementation } from "@metamask/delegation-toolkit"; + +export const publicClient = createPublicClient({ + chain: chain, + transport: http(), +}); + +// Your session account for requesting and redeeming should be same. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + + +export const sessionAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { 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()); +``` + + + +:::note +`sendUserOperationWithDelegation` is similar to the `sendUserOperation` function, but does not accept `callData` directly. +::: + +### Redeem with an EOA + +To redeem a delegation with an EOA, create a [Viem Wallet Client](https://viem.sh/docs/clients/wallet). + +After creating your Wallet Client, you can extend its functionality with `erc7710WalletActions` actions to support ERC-7710. Once extended, use `sendTransactionWithDelegation` to redeem the permission. + + + + +```typescript +import { walletClient, publicClient } from "./config.ts"; + +// These properties must be extracted from the permission response. +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +const accountMetadata = permissionsResponse[0].accountMeta; + +if (accountMetadata?.length !== 0) { + // If the granted permission contains accountMetadata, this must be executed before attempting to + // redeem the delegation. + + // This transaction will deploy the delegator account. + const hash = walletClient.sendTransaction({ + to: accountMetadata.factory, + data: accountMetadata.factoryData, + }); + + // You should wait for transaction to be successfully executed. + // You can use the TransactionReceipt.status to verify the state. + await publicClient.waitForTransactionReceipt( { hash }); +} + +const hash = walletClient.sendTransactionWithDelegation({ + chain, + to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + value: 1n, + permissionsContext, + delegationManager +}); +``` + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { erc7710WalletActions } from "@metamask/delegation-toolkit/experimental"; + +export const publicClient = createPublicClient({ + chain, + transport: http() +}); + +// Your session account for requesting and redeeming should be same. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + +const walletClient = createWalletClient({ + account, + transport: http(), + chain, +}).extend(erc7710WalletActions()); +``` + + diff --git a/gator_versioned_docs/version-0.10.1/experimental/erc-7715-request-permissions.md b/gator_versioned_docs/version-0.10.1/experimental/erc-7715-request-permissions.md new file mode 100644 index 00000000000..8d605b4a76a --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/experimental/erc-7715-request-permissions.md @@ -0,0 +1,182 @@ +--- +description: Learn how to request ERC-7715 permissions. +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# ERC-7715: Request permissions + +:::caution Experimental +This is an experimental feature. +It requires MetaMask Flask 12.14.2 or later, and may change in future releases. +::: + +[ERC-7715](https://eip.tools/eip/7715) introduces a standard way for dapps to request permissions from a wallet to execute +transactions on a user's behalf. + +The MetaMask Delegation Toolkit provides the experimental actions for ERC-7715 that lets a caller request permissions from MetaMask's permissions system. + +## Request permissions + +To request permissions, extend your [Viem Wallet Client](https://viem.sh/docs/clients/wallet) with `erc7715ProviderActions` actions. +You'll need a session account to request the permission, which can be either an externally owned account (EOA) or a smart contract account (SCA). +This example uses an SCA: + + + + +```typescript +import { sepolia as chain } from "viem/chains"; +import { sessionAccount, walletClient } from "./config.ts"; + +const expiry = Math.floor(Date.now() / 1000 + 604_800); // 1 week from now. +const currentTime = Math.floor(Date.now() / 1000); // now + +const grantedPermissions = await walletClient.grantPermissions([{ + chainId: chain.id, + expiry, + signer: { + type: "account", + data: { + address: sessionAccount.address, + }, + }, + permission: { + type: "native-token-stream", + data: { + initialAmount: 1n, // 1 wei + amountPerSecond: 1n, // 1 wei per second + maxAmount: 10n, // 10 wei + startTime: currentTime, + justification: "Payment for a week long subscription", + }, + }, +}]); +``` + + + + + +```typescript +import { createWalletClient, custom, createPublicClient, http } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { erc7715ProviderActions } from "@metamask/delegation-toolkit/experimental"; +import { toMetaMaskSmartAccount, Implementation } from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain: chain, + transport: http(), +}); + +// The private key of the session owner. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + +export const sessionAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); + +export const walletClient = createWalletClient({ + transport: custom(window.ethereum), +}).extend(erc7715ProviderActions()); +``` + + + +
+ ERC-7715 request permission sample + + Here's what your ERC-7715 native token streaming allowance request looks like: + + ```ts + [{ + chainId: "0xaa36a7", + expiry: 1745041429, + permission: { + type: "native-token-stream", + data: { + amountPerSecond: "0x1", + maxAmount: "0x1", + initialAmount: "0xa", + startTime: 1744955029, + justification: "Payment for a week long subscription", + }, + }, + signer: { + type: "account", + data: { + address: "0x1234...", + }, + }, + }] + ``` + + Learn more about the [ERC-7715 permission schema](https://eip.tools/eip/7715). +
+ + +Additionally, you can allow users to modify the requested permission by setting the `isAdjustmentAllowed` parameter to `true` in the request. + +```typescript +import { sepolia as chain } from "viem/chains"; +// The config.ts is the same as in the previous example. +import { sessionAccount, walletClient } from "./config.ts"; + +const expiry = Math.floor(Date.now() / 1000 + 604_800); // 1 week from now. +const currentTime = Math.floor(Date.now() / 1000); // now + +const grantedPermissions = await walletClient.grantPermissions([{ + chainId: chain.id, + expiry, + signer: { + type: "account", + data: { + address: sessionAccount.address, + }, + }, + permission: { + type: "native-token-stream", + // add-next-line ++ isAdjustmentAllowed: true, + data: { + initialAmount: 1n, // 1 wei + amountPerSecond: 1n, // 1 wei per second + maxAmount: 10n, // 10 wei + startTime: currentTime, + justification: "Payment for a week long subscription", + }, + }, +}]); +``` + +:::note +Users have full control over the permissions they grant—depending on the permission you request, they may choose to grant more limited permissions than requested. +You should always verify the granted permissions and adjust your dapp's behavior accordingly. +::: + +## Security considerations for `accountMeta` + +When a user grants a permission, they can provide [`accountMeta`](erc-7710-redeem-delegations.md#extract-relevant-data) which is an array of `factory` and `factoryData` values. +These calls must be executed before redeeming the permission (this is handled for you in `sendUserOperationWithDelegation`). + +Because each `accountMeta` is an arbitrary call specified by the granter, it is important that these are executed carefully. +We recommend taking the following precautions: + +- **Only grant permissions to session accounts** - When requesting permissions, use an account that is only used for that single purpose, and does not contain tokens. + This way, any `accountMeta` executed can't perform any damaging actions. + +- **Only execute `accountMeta` against trusted factory addresses** - Ensure that only `accountMeta` targeting a known factory address is executed. + The bundler action `sendUserOperationWithDelegation` only executes `accountMeta` that targets the `SimpleFactory` address for the current Delegation Framework. + If you redeem delegations in any other way, it is your responsibility to validate trusted factory addresses. + +## Next steps + +You can redeem the granted permission using the experimental [ERC-7710 `erc7710WalletActions()`](erc-7710-redeem-delegations.md). diff --git a/gator_versioned_docs/version-0.10.1/experimental/store-retrieve-delegations.md b/gator_versioned_docs/version-0.10.1/experimental/store-retrieve-delegations.md new file mode 100644 index 00000000000..3b6cde29fd6 --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/experimental/store-retrieve-delegations.md @@ -0,0 +1,172 @@ +--- +description: Store and retrieve delegations using the `DelegationStorageClient`. +sidebar_position: 1 +toc_max_heading_level: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Store and retrieve delegations + +:::caution Experimental +This is an experimental feature and may change in future releases. +::: + +You can use methods provided by the `DelegationStorageClient` of the MetaMask Delegation Toolkit to store and retrieve +[delegations](../concepts/delegation.md). + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](../how-to/configure.md) +- Ensure you have an API key and API key ID to interact with the `DelegationStorageClient`. + If you need to gain access, email hellogators@consensys.net. + +## Configure the storage client + +Create the `DelegationStorageClient` instance, and configure it using your API key and API key ID. + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + +## Store a delegation + +To store a delegation, use the `storeDelegation` method of the `DelegationStorageClient`. This method takes one parameter: + +1. `delegation` - A `Delegation` object representing the delegation to be stored. + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; + +const delegationHash = await delegationStorageClient.storeDelegation(delegation); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + + +## Retrieve a delegation chain + +To retrieve a delegation chain, use the `getDelegationChain` method of the `DelegationStorageClient`. This method takes one parameter: + +1. `leafDelegationOrDelegationHash` - Either a `Delegation` object or the delegation hash as a hex string. + +:::note +A delegation can be a root delegation, where its `authority` is `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`. It can also be a child of another delegation, where its `authority` is the hash of its parent delegation. This method returns the delegation referenced by `leafDelegationOrDelegationHash` and any ancestors. +::: + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; +import { getDelegationHashOffchain } from "@metamask/delegation-toolkit"; + +// Assuming you have the leaf delegation +const delegationHash = getDelegationHashOffchain(leafDelegation); + +const delegationChain: Delegation[] = await delegationStorageClient.getDelegationChain( + delegationHash +); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + + +## Retrieve delegations for a specific account + +To retrieve delegations stored for a specific account, use the `fetchDelegations` method of the `DelegationStorageClient`. This method allows you to fetch delegations where the specified account is either the delegator or the delegate. +It takes two parameters: + +1. `account` - The address of the account for which you want to retrieve delegations. +2. `filter` - The nature of the delegations. Possible values are: + - `DelegationStoreFilter.Given` - For delegations where the specified `account` is the `delegator`. + - `DelegationStoreFilter.Received` - For delegations where the specified `account` is the `delegate`. + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; + +const address = "0x027aeAFF3E5C33c4018FDD302c20a1B83aDCD96C" + +// Fetch the delegations given by address. +const grantedDelegations = await delegationStorageClient.fetchDelegations( + address, + DelegationStoreFilter.Given, +); + +// Fetch the delegations received by the address. +const receivedDelegations = await delegationStore.fetchDelegations( + address, + DelegationStoreFilter.Received, +); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + diff --git a/gator_versioned_docs/version-0.10.1/get-started/_category_.json b/gator_versioned_docs/version-0.10.1/get-started/_category_.json new file mode 100644 index 00000000000..bc9fe8e48f6 --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/get-started/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Get started", + "position": 2, + "link": { + "type": "generated-index", + "slug": "/get-started", + "title": "Get started with the Delegation Toolkit" + } +} \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.1/get-started/install.md b/gator_versioned_docs/version-0.10.1/get-started/install.md new file mode 100644 index 00000000000..060d67f0ecd --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/get-started/install.md @@ -0,0 +1,48 @@ +--- +sidebar_label: Install and set up +description: Learn how to install and set up the MetaMask Delegation Toolkit. +sidebar_position: 1 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Install and set up the Delegation Toolkit + +This page provides instructions to install and set up the MetaMask Delegation Toolkit. + +## 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](../how-to/create-delegation/create-custom-caveat-enforcer.md)), install + [Foundry](https://book.getfoundry.sh/getting-started/installation). + +## Steps + +### 1. Install the toolkit + +Install the MetaMask Delegation Toolkit dependencies: + +```bash npm2yarn +npm install @metamask/delegation-toolkit +``` + +### 2. (Optional) Install the contracts + +If you plan to extend the Delegation Framework smart contracts (for example, to +[create a custom caveat enforcer](../how-to/create-delegation/create-custom-caveat-enforcer.md)), 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 MetaMask Delegation Toolkit. +Check out the [Delegation Toolkit quickstart](quickstart.md) to walk through a simple example. \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.1/get-started/quickstart.md b/gator_versioned_docs/version-0.10.1/get-started/quickstart.md new file mode 100644 index 00000000000..41b7d5462cc --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/get-started/quickstart.md @@ -0,0 +1,173 @@ +--- +description: Get started quickly with the MetaMask Delegation Toolkit. +sidebar_position: 2 +sidebar_label: Quickstart +--- + +# Delegation Toolkit quickstart + +This page demonstrates how to get started quickly with the MetaMask Delegation Toolkit, +by creating a delegator account and completing the delegation lifecycle (creating, signing, and redeeming a delegation). + +## Prerequisites + +[Install and set up the Delegation Toolkit.](install.md) + +## Steps + +### 1. 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 delegator account query the signer's account state and interact with smart contracts. + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const publicClient = createPublicClient({ + chain, + transport: http(), +}); +``` + +### 2. 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 +import { createBundlerClient } from "viem/account-abstraction"; + +const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http("https://your-bundler-rpc.com"), +}); +``` + +### 3. Create a delegator account + +[Create a delegator account](../how-to/create-delegator-account.md) to set up a delegation. +The delegator must be a smart account. + +This example configures a [Hybrid Delegator](../how-to/configure-delegator-accounts-signers.md#configure-a-hybrid-delegator): + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; + +const delegatorAccount = privateKeyToAccount("0x..."); + +const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegatorAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegatorAccount }, +}); +``` + +### 4. Create a delegate account + +Create a delegate account to receive the delegation. +The delegate can be either a smart contract account (SCA) or an externally owned account (EOA). + +This example uses an SCA: + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; + +const delegateAccount = privateKeyToAccount("0x..."); + +const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + +### 5. Create a delegation + +[Create a root delegation](../how-to/create-delegation/index.md#create-a-root-delegation) from the +delegator account to the delegate account. + +This example passes an empty `caveats` array, which means the delegate can perform any action on the delegator's behalf. +We recommend [restricting the delegation](../how-to/create-delegation/restrict-delegation.md) by adding caveat enforcers. + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +const delegation = createDelegation({ + to: delegateSmartAccount.address, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + +### 6. Sign the delegation + +[Sign the delegation](../how-to/create-delegation/index.md#sign-a-delegation) using the `signDelegation` method from `MetaMaskSmartAccount`. +Alternatively, you can use the Delegation Toolkit's `signDelegation` utility. +The signed delegation will be used later to perform actions on behalf of the delegator. + +```typescript +const signature = await delegatorSmartAccount.signDelegation({ + delegation +}); + +const signedDelegation = { + ...delegation, + signature, +}; +``` + +### 7. Redeem the delegation + +The delegate account can now [redeem the delegation](../how-to/redeem-delegation.md). +The redeem transaction is sent to the `DelegationManager` contract, which validates the delegation and +executes actions on the delegator's behalf. + +To prepare the call data for the redeem transaction, use the `redeemDelegation` utility function from the Delegation Toolkit. + +```typescript +import { + createExecution, + DelegationFramework, + SINGLE_DEFAULT_MODE, +} from "@metamask/delegation-toolkit"; +import { zeroAddress } from "viem"; + +const delegations = [ signedDelegation ]; + +const executions = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ SINGLE_DEFAULT_MODE ], + executions: [ executions ] +}); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: delegateSmartAccount.address, + data: redeemDelegationCalldata + } + ], + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, +}); +``` diff --git a/gator_versioned_docs/version-0.10.1/how-to/_category_.json b/gator_versioned_docs/version-0.10.1/how-to/_category_.json new file mode 100644 index 00000000000..2b6df81f83d --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/how-to/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "How to", + "position": 4, + "link": { + "type": "generated-index", + "slug": "/how-to", + "title": "Delegation Toolkit how-to guides" + } +} diff --git a/gator_versioned_docs/version-0.10.1/how-to/configure-delegator-accounts-signers.md b/gator_versioned_docs/version-0.10.1/how-to/configure-delegator-accounts-signers.md new file mode 100644 index 00000000000..c25833669be --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/how-to/configure-delegator-accounts-signers.md @@ -0,0 +1,303 @@ +--- +sidebar_label: Configure accounts and signers +description: Learn how to configure different types of delegator accounts and signers using Viem. +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Configure delegator accounts and signers + +The MetaMask Delegation Toolkit supports different [delegator account types](../concepts/delegator-accounts.md#delegator-account-types), +each with its own configuration and support for different signing mechanisms. +You can create flexible and secure delegator accounts tailored to your specific needs. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator account.](create-delegator-account.md) + +## Configure a Hybrid Delegator + +The [Hybrid Delegator](../concepts/delegator-accounts.md#hybrid-delegator) supports both an EOA "owner" and any number of P256 (passkey) signers. + +To configure a Hybrid Delegator, provide the following parameters: + +- `owner`: The owner's account address as a hex string. + The owner can be the zero address, indicating that there is no owner configured. +- `p256KeyIds`: An array of key identifiers for P256 signers as hex strings. +- `p256XValues`: An array of public key x-values for P256 signers as `bigint`s. +- `p256YValues`: An array of public key y-values for P256 signers as `bigint`s. +- `signatory`: A signer that will sign on behalf of the delegator account. + +:::note +You can set all `p256` parameters to empty arrays to configure no WebAuthn signer. +However, we recommend configuring at least one signer for account recoverability. +::: + +For a Hybrid Delegator, you can configure the following types of signatories: + +### Configure an account signatory + +This example creates a signatory from a private key using Viem's [`privateKeyToAccount`](https://viem.sh/docs/accounts/local/privateKeyToAccount) function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const signatory = { account }; +``` + + + + +### Configure a Wallet Client signatory + +This example creates a [Viem Wallet Client](https://viem.sh/docs/clients/wallet) as the signatory, +using Viem's `createWalletClient` function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient } from "viem"; + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +const walletClient = createWalletClient({ + account, + chain, + transport: http() +}) + +export const signatory = { walletClient }; +``` + + + + +### Configure a WebAuthn (passkey) signatory + +This example creates a [Viem WebAuthn Account](https://viem.sh/account-abstraction/accounts/webauthn) as the signatory, +using Viem's `toWebAuthnAccount` function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { createCredential, parsePublicKey } from "webauthn-p256"; +import { toWebAuthnAccount } from "viem/account-abstraction"; +import { toHex } from "viem"; + +const credential = await createCredential({ name: "Your Delegator Passkey" }); +const webAuthnAccount = toWebAuthnAccount({ credential }); +const keyId = toHex("my-key-id"); + +const signatory = { webAuthnAccount, keyId }; +``` + + + + + +## Configure a Multisig Delegator + +The [Multisig Delegator](../concepts/delegator-accounts.md#multisig-delegator) supports multiple EOA signers with a configurable threshold for execution. + +To configure a Multisig Delegator, provide the following parameters: + +- `signers`: An array of EOA signer addresses as hex strings. +- `threshold`: The number of signers required to execute a transaction, as a `bigint`. +- `signatory`: An array of signatories that will sign on behalf of the delegator account. + +### Configure signatories + +For a Multisig Delegator, you can use a combination of account signatories and Wallet Client signatories. +For example: + + + + +```typescript +import { publiClient } from "./client.ts"; +import { account, walletClient } from "./signers.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const signers = [ account.address, walletClient.address ]; +const signatory = { account, walletClient }; +const threshold = 2n + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.MultiSig, + deployParams: [signers, threshold], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient } from "viem"; + +// 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 walletClientPivatekey = generatePrivateKey(); +const walletClientAccount = privateKeyToAccount(walletClientPivatekey); + +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. +::: diff --git a/gator_versioned_docs/version-0.10.1/how-to/configure.md b/gator_versioned_docs/version-0.10.1/how-to/configure.md new file mode 100644 index 00000000000..e85115c1408 --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/how-to/configure.md @@ -0,0 +1,60 @@ +--- +description: Learn how to configure the MetaMask Delegation Toolkit using Viem. +sidebar_position: 1 +sidebar_label: Configure the toolkit +--- + +# Configure the Delegation Toolkit + +The MetaMask Delegation Toolkit enables you to easily integrate delegator accounts into your dapp, enabling a more flexible, secure, and frictionless experience for your users. + +The toolkit is highly configurable, allowing you to tailor it to your project's specific needs. It includes support for custom signers, multiple signatory schemes, custom paymasters and bundlers, and more. + +:::note +The MetaMask Delegation Toolkit provides custom middleware for [Pimlico's](https://docs.pimlico.io/) gas fee resolver, paymaster, and bundler. Additional options will be made available soon. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit](../get-started/install.md). +- Optionally, complete the [Delegation Toolkit quickstart](../get-started/quickstart.md) to + familiarize yourself with the toolkit's capabilities. + +## Viem's Account Abstraction API + +The toolkit uses Viem's Account Abstraction API. This provides a robust and flexible foundation for creating and managing smart contract accounts. +See Viem's [Smart Account documentation](https://viem.sh/account-abstraction/accounts/smart) for more information on the API's features, methods, and best practices. + + +## Configure Viem bundler and paymaster clients + +To use the bundler and paymaster clients with the toolkit, create instances of these clients and configure them as follows: + +```typescript +import { + createPaymasterClient, + createBundlerClient, +} from "viem/account-abstraction"; +import { http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +// 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, +}); +``` + +:::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 sufficient funds to pay for gas fees directly. +::: + diff --git a/gator_versioned_docs/version-0.10.1/how-to/create-delegation/create-custom-caveat-enforcer.md b/gator_versioned_docs/version-0.10.1/how-to/create-delegation/create-custom-caveat-enforcer.md new file mode 100644 index 00000000000..77416e901f5 --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/how-to/create-delegation/create-custom-caveat-enforcer.md @@ -0,0 +1,149 @@ +--- +description: Learn how to create, deploy, and apply a custom caveat enforcer +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a custom caveat enforcer + +When [restricting a delegation](restrict-delegation.md), the MetaMask Delegation Toolkit provides some [out-of-the-box caveat enforcers](restrict-delegation.md#caveat-types) +that cover common use cases. +For more granular or custom control, you can follow the instructions on this page to create custom caveat enforcers from scratch. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../get-started/install.md) +- [Configure the Delegation Toolkit.](../configure.md) + +## Steps + +### 1. Create the caveat enforcer + +Create a contract that extends the +[`ICaveatEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/interfaces/ICaveatEnforcer.sol) +interface. + +For example, the following is a simple caveat enforcer that only allows a delegation to be redeemed after a specific timestamp. + +```solidity title="AfterTimestampEnforcer.sol" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import { CaveatEnforcer } from "@delegator/src/enforcers/CaveatEnforcer.sol"; +import { ModeCode } from "../utils/Types.sol"; + +contract AfterTimestampEnforcer is CaveatEnforcer { + /** + * @notice The delegation may only be redeemed after the specified timestamp - validAfter in seconds. + * @param _terms The validAfter, timestamp in seconds after which the delegation may be redeemed. + * @param _delegationHash - The hash of the delegation being operated on. + */ + function beforeHook( + bytes calldata _terms, + bytes calldata, + ModeCode, + bytes calldata, + bytes32 _delegationHash, + address, + address _redeemer + ) + public + override + { + // Enforces the conditions that should hold before a transaction is performed. + // This function MUST revert if the conditions are not met. + // Get the current timestamp + uint256 timestamp = block.timestamp; + + uint256 validAfter = uint256(bytes32(_terms)); + + require(timestamp > validAfter, "AfterTimestampEnforcer:cannot-redeem-too-early"); + } +} +``` + +### 2. Deploy the caveat enforcer + +Deploy your custom caveat enforcer to obtain its contract address. +For example, you can [deploy your smart contract using Forge](https://book.getfoundry.sh/forge/deploying). + +### 3. Apply the caveat enforcer + +When creating a delegation, add the `Caveat` for the custom caveat to the `CaveatBuilder`. +Learn more about [applying caveats to a delegation](restrict-delegation.md). + +The following example uses the custom `AfterTimestampEnforcer.sol` caveat enforcer to create a delegation granting +an allowance of 1,000,000 wei that can only be spent after one hour from when the delegation is created. + +:::warning Important +Depending on the use case, it may be necessary to add additional caveats to restrict the delegation further. +::: + + + + +```typescript +import { + createCaveatBuilder, + createDelegation, +} from "@metamask/delegation-toolkit"; +import { toHex } from "viem"; +import { delegatorSmartAccount } from "./config.ts"; + +const environment = delegatorSmartAccount.enviroment; + +// Replace this with the address where the AfterTimestampEnforcer.sol contract is deployed. +const afterTimestampEnforcer = "0x22Ae4c4919C3aB4B5FC309713Bf707569B74876F"; + +const caveatBuilder = createCaveatBuilder(environment); + +const tenAM = 10 * 60 * 60; // 10:00 AM as seconds since midnight. + +const caveats = caveatBuilder + .addCaveat("nativeTokenTransferAmount", 1_000_000) + .addCaveat({ + enforcer: afterTimestampEnforcer, + terms: toHex(tenAm) + }); + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats +}); +``` + + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + diff --git a/gator_versioned_docs/version-0.10.1/how-to/create-delegation/index.md b/gator_versioned_docs/version-0.10.1/how-to/create-delegation/index.md new file mode 100644 index 00000000000..8cd0f457f6f --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/how-to/create-delegation/index.md @@ -0,0 +1,349 @@ +--- +description: Learn how to create different types of delegations, and how to sign a delegation. +sidebar_position: 6 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a delegation + +The MetaMask Delegation Toolkit enables you to create [delegations](../../concepts/delegation.md) +from a delegator account to a delegate account. + +:::note +Delegations are compatible with [ERC-7710](https://eip.tools/eip/7710) and [ERC-7715](https://ethereum-magicians.org/t/erc-7715-grant-permissions-from-wallets/20100), to support a standardized minimal interface. +[Requesting ERC-7715 permissions](../../experimental/erc-7715-request-permissions.md) and [redeeming ERC-7710 delegations](../../experimental/erc-7710-redeem-delegations.md) +are experimental features. +::: + +:::warning +The examples on this page demonstrate delegations without any restrictions. +Unrestricted delegations grant complete control over the account to the delegate, which can pose significant security risks. +It is crucial to add caveats to limit the delegated authority. +Learn how to [restrict a delegation](./restrict-delegation.md) using caveat enforcers. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../get-started/install.md) +- [Configure the Delegation Toolkit.](../configure.md) +- [Create a delegator account.](../create-delegator-account.md) + +## Create a root delegation + +A *root delegation* is a delegation that doesn't derive its authority from another delegation. +It is when a delegator delegates its own authority away, as opposed to a [redelegation](#create-a-redelegation). +Create a root delegation as follows: + + + + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.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: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create an 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. +You must create open root delegations carefully, to ensure that they are not misused. +Create an open root delegation by setting the delegate property to the special address +`0x0000000000000000000000000000000000000a11` (available via the constant `ANY_BENEFICIARY`). + + + + +```typescript +import { createOpenDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +const openRootDelegation = createOpenDelegation({ + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create a redelegation + +A recipient of a delegation (the delegate), can *redelegate* that authority to a third party, potentially applying additional [restrictions](restrict-delegation.md). +Create a redelegation as follows: + + + + +```typescript +import { + createDelegation, + getDelegationHashOffchain + } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +// The address is used as the root delegator. While creating the redelegation, +// the root delegate address will be the delegator address. +const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +// The authority is the (typed data) hash of the delegation from which the authority is derived. +const parentDelegationHash = getDelegationHashOffchain(delegation); + +// The address is used as the delegate address while creating the redelegation. +const leafDelegate = "0xb4821Ab7d5942Bd2533387592068a12608B4a52C" + +const leafDelegation = createDelegation({ + to: leafDelegate, + from: delegate, + // You can also choose to pass the parent delegation object, and let function + // handle the rest. + parentDelegation: parentDelegationHash, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create an open redelegation + +An *open redelegation* is a [redelegation](#create-a-redelegation) that doesn't specify a delegate. +This means that any account can redeem the redelegation. +As with [open root delegations](#create-an-open-root-delegation), you must create open redelegations carefully, +to ensure that they are not misused. +Create an open redelegation as follows: + + + + +```typescript +import { + createDelegation, + createOpenDelegation, + getDelegationHashOffchain + } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +// The address is used as the root delegator. While creating the redelegation, +// the root delegate address will be the delegator address. +const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +// The authority is the (typed data) hash of the delegation from which the authority is derived. +const parentDelegationHash = getDelegationHashOffchain(delegation); + +const leafDelegation = createOpenDelegation({ + from: delegate, + // You can also choose to pass the parent delegation object, and let the function + // handle the rest. + parentDelegation: parentDelegationHash, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Sign a delegation + +A delegation must be signed by the delegator to be valid for redemption. The `MetaMaskSmartAccount` supports signing the delegation using [EIP-712 Typed Data](https://eips.ethereum.org/EIPS/eip-712) via the `signDelegation` method. +Sign a delegation as follows: + + + + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.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: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +const signature = await delegatorSmartAccount.signDelegation({ delegation }); + +const signedDelegation = { + ...delegation, + signature +}; +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); + +``` + + + \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.1/how-to/create-delegation/restrict-delegation.md b/gator_versioned_docs/version-0.10.1/how-to/create-delegation/restrict-delegation.md new file mode 100644 index 00000000000..83908aedd75 --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/how-to/create-delegation/restrict-delegation.md @@ -0,0 +1,807 @@ +--- +description: Learn how to restrict a delegation using caveat enforcers, and the available caveat types. +sidebar_position: 1 +toc_max_heading_level: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Restrict a delegation + +Use [caveat enforcers](../../concepts/caveat-enforcers.md) to apply specific rules and restrictions +to a delegation, ensuring that delegated executions are only performed under predefined circumstances. + +A delegation has a `caveats` property, which is an array of `Caveat` objects. +Each caveat is specified as follows: + +```typescript +export type Caveat = { + enforcer: Hex; // The address of the caveat enforcer contract. + terms: Hex; // Data passed to the caveat enforcer, describing how the redemption should be validated. + args: Hex; // Data that may be specified by the redeemer when redeeming the delegation (only used in limited cases). +}; +``` + +The MetaMask Delegation Toolkit provides a `CaveatBuilder` interface, which offers an intuitive way to define the `caveats` array. +Use the `CaveatBuilder` to easily ensure that your delegations grant only the necessary authority. + +## Create the caveat builder + +To create the caveat builder, call the `createCaveatBuilder()` function, passing an instance of `DeleGatorEnvironment`. +The environment can be accessed from the `MetaMaskSmartAccount`, as in this example: + +```typescript +const environment = delegatorSmartAccount.environment; + +const caveatBuilder = createCaveatBuilder(environment); +``` + +:::note +By default, the `CaveatBuilder` does not allow empty caveats. To allow the `CaveatBuilder` to build an empty caveats array, provide the following configuration: + +```typescript +const caveatBuilder = createCaveatBuilder(environment, { allowEmptyCaveats: true }); +``` +::: + +## Add caveats to the builder + +Add caveats to the builder using the `addCaveat` method, specifying the [caveat type](#caveat-types) and its parameters. You can chain multiple calls to `addCaveat` as in the following example: + +```typescript +const caveats = caveatBuilder + // allowedTargets accepts an array of addresses. + // This caveat restricts the caller to only use the delegation to interact with the specified address. + .addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92"]) + // allowedMethods accepts an array of methods. + // This caveat restricts the caller to only use the delegation to invoke the specified methods. + .addCaveat("allowedMethods", [ + "approve(address,uint256)", + "transfer(address,uint256)" + ]) + // limitedCalls accepts a number. + // This caveat restricts the caller to only use the delegation one time. + .addCaveat("limitedCalls", 1) + .build(); +``` + +
+Important considerations when using caveat enforcers +

+ +- Delegations without caveats are entirely permissive. + It is crucial to add appropriate caveats to restrict the delegated authority sufficiently. + Failing to do so could result in unintended access or actions. +- Caveat enforcers safeguard the execution process but do not guarantee a final state post-redemption. + Always combine caveat enforcers thoughtfully to create comprehensive protection. +- When using multiple caveat enforcers that modify external contract states, the order matters. + For example, if you include both [`NativeBalanceGteEnforcer`](#nativebalancegte) to ensure a balance has increased and + [`NativeTokenPaymentEnforcer`](#nativetokenpayment) to deduct from that balance, + executing `NativeTokenPaymentEnforcer` first might cause `NativeBalanceGteEnforcer` to fail validation. + Consider the sequence of enforcers carefully when creating delegations with interdependent caveats. + +

+
+ +For convenience, you can also pass the `CaveatBuilder` directly to the various helper methods for creating a delegation. For example: + +```typescript +const caveats = caveatBuilder + // allowedTargets accepts an array of addresses. + .addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92"]) + // allowedMethods accepts an array of methods. + .addCaveat("allowedMethods", [ + "approve(address,uint256)", + "transfer(address,uint256)" + ]) + // limitedCalls accepts a number. + .addCaveat("limitedCalls", 1); + +const delegation = createDelegation({ + to: delegate, + from: delegator, + caveats +}); +``` + +## Caveat types + +The `CaveatBuilder` supports various caveat types, each serving a specific purpose. +These caveat types correspond to the out-of-the-box caveat enforcers +that the MetaMask Delegation Toolkit provides. + +For more granular or custom control, you can also [create custom caveat enforcers](create-custom-caveat-enforcer.md) +and add them to the caveat builder. + +### `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 + +1. Index in the calldata byte array (including the 4-byte method selector) where the expected calldata starts +2. Expected calldata as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("allowedCalldata", + 4, + encodeAbiParameters([ + { type: "string" }, + { type: "uint256" } + ], [ + "Hello Gator", + 12345n + ]) +); +``` + +:::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 + +1. An array of methods as 4-byte hex strings, ABI function signatures, or `ABIFunction` objects + +#### Example + +```typescript +caveatBuilder.addCaveat("allowedMethods", [ + "0xa9059cbb", + "transfer(address,uint256)", + { + 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 + +1. An array of addresses as hex strings + +#### Example + +```typescript +caveatBuilder.addCaveat("allowedTargets", [ + "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 + +1. The expected `args` as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("argsEqualityCheck", + "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 + +1. After threshold block number as a `bigint` +2. Before threshold block number as a `bigint` + +You can specify `0n` to indicate that there is no limitation on a threshold. + +#### Example + +```typescript +caveatBuilder.addCaveat("blocknumber", + 19426587n, + 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 + +1. A contract address as a hex string +2. The salt to use with the contract, as a hex string +3. The bytecode of the contract as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("deployed", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x0e3e8e2381fde0e8515ed47ec9caec8ba2bc12603bc2b36133fa3e3fa4d88587", + "0x..." // The deploy bytecode for the contract at 0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92 +); +``` + +### `erc1155BalanceGte` + +Ensures the ERC-1155 balance of a specified address has increased by at least a specified amount after the execution has been performed, regardless of what the execution is. + +**Caveat enforcer contract:** [`ERC1155BalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC1155BalanceGteEnforcer.sol) + +#### Parameters + +1. An ERC-1155 contract address as a hex string +2. The recipient's address as a hex string +3. The ID of the ERC-1155 token as a bigint +4. The amount by which the balance must have increased as a bigint + +#### Example + +```typescript +caveatBuilder.addCaveat("erc1155BalanceGte", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1n, + 1_000_000n +); +``` + +### `erc20BalanceGte` + +Ensures the delegator's ERC-20 balance increases by at least the specified amount after execution, regardless of the execution. + +**Caveat enforcer contract:** [`ERC20BalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC20BalanceGteEnforcer.sol) + +#### Parameters + +1. An ERC-20 contract address as a hex string +2. Amount as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20BalanceGte", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n +); +``` + +### `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 + +1. The address of the ERC-20 token contract. +2. The maximum amount of tokens that can be transferred per period, in wei. +3. The duration of each period in seconds. +4. The timestamp when the first period begins. + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20PeriodTransfer", + "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", // Address of the ERC-20 token + 1000000000000000000n, // 1 ERC-20 token - 18 decimals, in wei + 86400, // 1 day in seconds + 1743763600, // April 4th, 2025, at 00:00:00 UTC +); +``` + +### `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 + +1. An ERC-20 contract address as a hex string +2. Initial amount available at start time as a `bigint` +3. Maximum total amount that can be unlocked as a `bigint` +4. Rate at which tokens accrue per second as a `bigint` +5. Start timestamp as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20Streaming", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n, + 10_000_000n, + 100n, + 1703980800 +); +``` + +### `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 + +1. An ERC-20 contract address as a hex string +2. Amount as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20TransferAmount", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n +); +``` + +### `erc721BalanceGte` + +Ensures the ERC-721 balance of the specified recipient address increases by at least the specified amount after execution, regardless of execution type. + +**Caveat enforcer contract:** [`ERC721BalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC721BalanceGteEnforcer.sol) + +#### Parameters + +1. An ERC-721 contract address as a hex string +2. The recipient's address as a hex string +3. The amount by which the balance must have increased as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc721BalanceGte", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n +); +``` + +### `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 + +1. The permitted ERC-721 contract address as a hex string +2. The permitted ID of the ERC-721 token as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc721Transfer", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 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 + +1. A hex value for calldata. + +#### Example + +```typescript +caveatBuilder.addCaveat("exactCalldata", + "0x1234567890abcdef" // Calldata to be matched +); +``` + +### `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 + +1. Array of expected `ExecutionStruct`, each containing target address, value, and calldata. + +#### Example + +```typescript +const executions = [ + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + callData: "0x", + }, + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 0n, + callData: "0x", + }, +]; + +caveatBuilder.addCaveat("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 + +1. `ExecutionStruct` to be expected. + +#### Example + +```typescript +caveatBuilder.addCaveat("exactExecution", { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + 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 + +1. Array of expected `ExecutionStruct`, each containing target address, value, and calldata. + +#### Example + +```typescript +const executions = [ + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + callData: "0x", + }, + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 0n, + callData: "0x", + }, +]; + +caveatBuilder.addCaveat("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 + +1. An ID as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("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 + +1. A count as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("limitedCalls", + 1 +); +``` + +### `nativeBalanceGte` + +Ensures that the recipient's native token balance has increased by at least the specified amount. + +**Caveat enforcer contract:** [`NativeBalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NativeBalanceGteEnforcer.sol) + +#### Parameters + +1. The recipient's address as a hex string +2. The amount by which the balance must have increased as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeBalanceGte", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n +); +``` + +### `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 + +1. The recipient's address as a hex string +2. The amount that must be paid as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenPayment", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n +); +``` + +### `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 + +1. The maximum amount of tokens that can be transferred per period, in wei. +2. The duration of each period in seconds. +3. The timestamp when the first period begins. + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenPeriodTransfer", + 1000000000000000000n, // 1 ETH in wei + 86400, // 1 day in seconds + 1743763600, // April 4th, 2025, at 00:00:00 UTC +) +``` + +### `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 + +1. Initial amount available at start time as a `bigint` +2. Maximum total amount that can be unlocked as a `bigint` +3. Rate at which tokens accrue per second as a `bigint` +4. Start timestamp as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenStreaming", + 1_000_000n, + 10_000_000n, + 100n, + 1703980800 +); +``` + +### `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 + +1. The allowance as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenTransferAmount", + 1_000_000n +); +``` + +### `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 + +1. A nonce as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("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 + +1. The target contract address as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("ownershipTransfer", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92" +); +``` + +### `redeemer` + +Limits the addresses that can redeem the delegation. +This caveat is designed for restricting 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 + +1. An array of addresses as hex strings + +#### Example + +```typescript +caveatBuilder.addCaveat("redeemer", + [ + "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 + +1. The address of the ERC-20 token contract. +2. The address that will receive the tokens. +3. The amount of tokens to transfer, in wei. +4. The target address for the first transaction. +5. The calldata for the first transaction. + +#### Example + +```typescript +caveatBuilder.addCaveat("specificActionERC20TransferBatch", + "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da" // Address of ERC-20 token contract + "0x027aeAFF3E5C33c4018FDD302c20a1B83aDCD96C", // Address that will receive the tokens + 1000000000000000000n, // 1 ERC-20 token - 18 decimals, in wei + "0xb49830091403f1Aa990859832767B39c25a8006B", // Target address for first transaction + "0x1234567890abcdef" // Calldata to be matched for first transaction +) +``` + +### `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 + +1. After threshold timestamp as a number +2. Before threshold timestamp as a number + +You can specify `0` to indicate that there is no limitation on a threshold. + +#### Example + +```typescript +caveatBuilder.addCaveat("timestamp", + 499165200, + 1445412480 +); +``` + +### `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 + +1. A value as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("valueLte", + 1_000_000_000_000_000_000n // 1 ETH in wei +); +``` diff --git a/gator_versioned_docs/version-0.10.1/how-to/create-delegator-account.md b/gator_versioned_docs/version-0.10.1/how-to/create-delegator-account.md new file mode 100644 index 00000000000..73e132ee5a1 --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/how-to/create-delegator-account.md @@ -0,0 +1,80 @@ +--- +description: Learn how to create a delegator account using Viem. +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a delegator account + +The MetaMask Delegation Toolkit is embedded, meaning that the end user can instantly interact with a dapp without wallet authorization, confirmations, or corporate logos. Enable users to create a [delegator account](../concepts/delegator-accounts.md) directly in your dapp. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) + +## Create a `MetaMaskSmartAccount` + +The following is an example of creating a delegator account using Viem Core SDK. +Viem Core SDK provides low-level interfaces to offer flexibility and control over the delegator +account creation lifecycle. + +In the example, the Viem [`privateKeyToAccount`](https://viem.sh/docs/accounts/privateKey.html) +function creates an externally owned account as the owner of the delegator account. + + + + +```typescript +import { publicClient, owner } from "./config.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const deploySalt = "0x"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner.address, [], [], []], + deploySalt, + signatory: { account: owner }, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); + +const privateKey = generatePrivateKey(); +export const owner = privateKeyToAccount(privateKey); +``` + + + + + +This example creates the `MetaMaskSmartAccount`, which can perform several functions: + +- In conjunction with [Viem Account Abstraction clients](configure.md), deploy the smart contract account, + and [send user operations](send-user-operation.md). +- [Sign delegations](create-delegation/index.md) that can be used to grant specific rights and permissions to other accounts. + +:::note +The example above uses the Hybrid Delegator smart contract account, which is configurable to have an EOA "owner" and any number of P256 (passkey) signers. +You can also [configure other delegator account types](configure-delegator-accounts-signers.md). +::: diff --git a/gator_versioned_docs/version-0.10.1/how-to/redeem-delegation.md b/gator_versioned_docs/version-0.10.1/how-to/redeem-delegation.md new file mode 100644 index 00000000000..23be468113e --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/how-to/redeem-delegation.md @@ -0,0 +1,347 @@ +--- +description: Learn how to redeem a delegation with a smart contract account (SCA) or an externally owned account (EOA). +sidebar_position: 7 +toc_max_heading_level: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Redeem a delegation + +A delegate can redeem a delegation by submitting either a user operation or a regular transaction, +depending on whether the delegate is a smart contract account (SCA) or externally owned account (EOA). + +The redeem transaction is sent to the `DelegationManager` contract, which validates the delegation and executes actions on the delegator's behalf. To prepare the call data for the redeem transaction, use the `redeemDelegation` utility function. The function supports batch redemption, allowing multiple delegations to be processed within a single transaction. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator account.](create-delegator-account.md) +- [Create a delegation.](create-delegation/index.md) + +## Redeem a delegation + +Redeem a delegation with a [smart contract account (SCA)](#redeem-with-an-sca) or an [externally owned account (EOA)](#redeem-with-an-eoa). + +### Redeem with an SCA + +The following example demonstrates how to submit a user operation to redeem a delegation. +It assumes you have a delegation signed by the delegator, and that the delegate is an SCA. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { bundlerClient, pimlicoClient } from "./client.ts"; +import { delegateSmartAccount } from "./account.ts"; + +const delegations: Delegation[] = [ signedDelegation ]; + +// SINGLE_DEFAULT_MODE is the default execution mode. +const mode: ExecutionMode = SINGLE_DEFAULT_MODE; + +// For SINGLE execution modes, the executions array must be length 1. +const executions: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const { fast: fee } = await pimlicoClient.getUserOperationGasPrice(); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: "", + data: redeemDelegationCalldata + } + ], + ...fee +}); +``` + + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +improt { publicClient } from "./client.ts" + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { createPimlicoClient } from "permissionless/clients/pimlico"; + +// You can get the API Key from the Pimlico dashboard. +const pimlicoUrl = "https://api.pimlico.io/v2/59141/rpc"; + +export const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http(pimlicoUrl), +}); + +export const pimlicoClient = createPimlicoClient({ + transport: http(pimlicoUrl), +}); +``` + + + + +### Redeem with an EOA + +The following example demonstrates how to submit a transaction to redeem a delegation. It assumes you have a delegation signed by the delegator, and that the delegate is an EOA. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { lineaSepolia as chain } from "viem/chains"; +import { delegateWalletClient } from "./account.ts"; + +const delegations: Delegation[] = [ signedDelegation ]; + +// SINGLE_DEFAULT_MODE is the default execution mode. +const mode: ExecutionMode = SINGLE_DEFAULT_MODE; + +// For SINGLE execution modes, the executions array must be length 1. +// Modify the executions to fit your use case. +const executions: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const transactionHash = await walletClient.sendTransaction({ + to: getDeleGatorEnvironment(chain.id).DelegationManager, + data: redeemDelegationCalldata, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { createWalletClient, http } from "viem"; + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateWalletClient = createWalletClient({ + account: delegateAccount, + chain, + transport: http(), +}) +``` + + + + +## Redeem multiple delegations + +You can redeem multiple delegations in a single user operation, each delegation independent of the others. +Each element in the `delegationsArray` must have a corresponding element in the `executionsArray` and `modes`. + +The following example assumes you already have multiple signed delegations and that the delegate is an SCA. +The preparation of the call data is the same when [using an EOA as the delegate](#redeem-with-an-eoa); +the primary difference is that an EOA submits a regular transaction instead of a user operation. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { bundlerClient, pimlicoClient } from "./client.ts"; +import { delegateSmartAccount } from "./account.ts"; + +const delegationsArray: Delegation[][] = [ + [ signedDelegation1 ] + [ signedDelegation2 ] + [ signedDelegation3 ] +]; + +const modes: ExecutionMode = [ + SINGLE_DEFAULT_MODE, + SINGLE_DEFAULT_MODE, + SINGLE_DEFAULT_MODE +]; + +const execution: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +// Modify the executions to fit your use case. For simplicity, we've +// included a basic example. The execution array can contain +// multiple different executions. +const executionsArray: ExecutionStruct:[][] = [ + execution, + execution, + execution +]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const { fast: fee } = await pimlicoClient.getUserOperationGasPrice(); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: "", + data: redeemDelegationCalldata + } + ], + ...fee +}); +``` + + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +import { publicClient } from "./client.ts"; + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { createPimlicoClient } from "permissionless/clients/pimlico"; + +// You can get the API Key from the Pimlico dashboard. +const pimlicoUrl = "https://api.pimlico.io/v2/59141/rpc"; + +export const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http(pimlicoUrl), +}); + +export const pimlicoClient = createPimlicoClient({ + transport: http(pimlicoUrl), +}); +``` + + + + +## Execution modes + +The Delegation Toolkit supports several execution modes based on [ERC-7579](https://erc7579.com/). +See the [ERC implementation](https://github.com/erc7579/erc7579-implementation/blob/main/src/lib/ModeLib.sol) for more details about the execution modes. + +The supported execution modes are `SINGLE_DEFAULT_MODE`, `SINGLE_TRY_MODE`, `BATCH_DEFAULT_MODE`, and `BATCH_TRY_MODE`. + +### `SINGLE` execution modes + +In `SINGLE` execution modes, only a single delegation chain and a single execution can be provided. This mode processes delegations sequentially: + +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. + +### `BATCH` execution modes + +In `BATCH` execution modes, multiple delegation chains and multiple executions can be provided. This mode executes delegations in an interleaved way: + +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. + +### `DEFAULT` modes + +In `DEFAULT` modes, if a revert occurs during redemption, the entire user operation reverts at that point. + +### `TRY` modes + +In `TRY` modes, if a revert occurs during redemption, execution of the user operation continues. diff --git a/gator_versioned_docs/version-0.10.1/how-to/send-user-operation.md b/gator_versioned_docs/version-0.10.1/how-to/send-user-operation.md new file mode 100644 index 00000000000..428d27bfe88 --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/how-to/send-user-operation.md @@ -0,0 +1,176 @@ +--- +description: Learn how to send an ERC-4337 user operation using Viem. +sidebar_position: 4 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# 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 Delegation Toolkit 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 smart contract account that has not been deployed, the toolkit configures the user operation to automatically deploy the account. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator account.](create-delegator-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 +import { bundlerClient, smartAccount } from "./config.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, + maxPriortyFeePerGas +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { createBundlerClient } from "viem/account-abstraction"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +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", + signatory: { account }, +}); + +export const bundlerClient = createBundlerClient({ + publicClient, + transport: http("https://public.pimlico.io/v2/1/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). + +```typescript title="example.ts" +// add-next-line ++ import { createPimlicoClient } from "permissionless/clients/pimlico"; +import { bundlerClient, smartAccount } from "./config.ts" // The config.ts is the same as in the previous example. + +// remove-start +- const maxFeePerGas = 1n; +- const maxPriorityFeePerGas = 1n; +// remove-end + +// add-start ++ const pimlicoClient = createPimlicoClient({ ++ transport: http("https://api.pimlico.io/v2/59141/rpc"), // 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, +- maxPriortyFeePerGas + // 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" +import { createPimlicoClient } from "permissionless/clients/pimlico"; +import { bundlerClient, smartAccount } from "./config.ts" // The config.ts is the same as in the previous example. + +const pimlicoClient = createPimlicoClient({ + transport: http("https://api.pimlico.io/v2/59141/rpc"), // 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 +``` diff --git a/gator_versioned_docs/version-0.10.1/index.md b/gator_versioned_docs/version-0.10.1/index.md new file mode 100644 index 00000000000..3cdab43dc69 --- /dev/null +++ b/gator_versioned_docs/version-0.10.1/index.md @@ -0,0 +1,63 @@ +--- +title: Introduction to the MetaMask Delegation Toolkit +sidebar_label: Introduction +description: High-level overview of the Delegation Toolkit, its benefits, and where to start in the documentation. +sidebar_position: 1 +--- + +import CardList from "@site/src/components/CardList" + +# MetaMask Delegation Toolkit documentation + +## Why use the toolkit? + +The MetaMask Delegation Toolkit enables developers to create frictionless new experiences based +on granular permission sharing and trust. +The toolkit offers a suite of contracts, libraries, and services designed for maximum composability, +allowing developers to build and extend their dapps with ease. +The toolkit enables: + +- **Instant user onboarding.** Provide frictionless onboarding with no browser extension, mobile + app, or seed phrase required. + +- **New web3 experiences.** Unlock new experiences such as peer-to-peer social + coordination using incentive trees, or recurring subscription payments that don't require users + to connect to the dapp. + +- **Uninterrupted user experiences.** Keep users immersed in the dapp by embedding the wallet + experience and reassigning gas costs to where they make sense. + +The toolkit includes the [Delegation Framework](concepts/delegation.md#delegation-framework) – a +pioneering set of open-source, customizable smart contracts, allowing dapps and protocols to +implement custom permission control. +Developers can use the Delegation Framework to prepare their dapps for +[delegations](concepts/delegation.md) created from +[delegator accounts](concepts/delegator-accounts.md). + +## Where do I start? + +Check out the following sections to get started with the MetaMask Delegation Toolkit: + + + +## Questions? + +If you have questions, email hellogators@consensys.net. diff --git a/gator_versioned_docs/version-0.10.2/changelog/0.10.0.md b/gator_versioned_docs/version-0.10.2/changelog/0.10.0.md new file mode 100644 index 00000000000..7e77bf15ff4 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/changelog/0.10.0.md @@ -0,0 +1,114 @@ +--- +sidebar_label: 0.10.0 +sidebar_position: 3 +description: MetaMask Delegation Toolkit v0.10.0 changelog +--- + +# What's new in v0.10.0? + +:::warning Breaking changes +The ⚠️ symbol denotes potentially breaking API changes. +As per the [semantic versioning specification](https://semver.org/#spec-item-4), from v1.0.0 onwards, +breaking changes will be released only in major version bumps. +::: + +## Enhancements + +### ⚠️ ERC-7715 permission request +- Removed `required` parameter. +- Added the `isAdjustmentAllowed` parameter, allowing user to modify the permission request as needed. + +```typescript +const permissions = { + chainId: '0x7a69', + address: bob.address, + expiry: 1234567890, + permission: { + type: 'native-token-stream', + data: { + amountPerSecond: '0x1', + maxAmount: '0x2', + initialAmount: undefined, + startTime: 2, + justification: 'Test justification', + }, + }, + // remove-next-line +- required: false + // add-next-line ++ isAdjustmentAllowed: true, + signer: { + type: 'account', + data: { + address: alice.address, + }, + }, +} +``` + +## Contract addresses + +The following are the contract addresses for the +[Delegation Framework version 1.3.0](https://github.com/MetaMask/delegation-framework/blob/v1.3.0/documents/Deployments.md), +as used by this version of the toolkit. + +### Delegation Framework + +| Contract | Address | +|----------|---------| +| EntryPoint | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | +| SimpleFactory | `0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c` | +| DelegationManager | `0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3` | +| MultiSigDeleGatorImpl | `0x56a9EdB16a0105eb5a4C54f4C062e2868844f3A7` | +| HybridDeleGatorImpl | `0x48dBe696A4D990079e039489bA2053B36E8FFEC4` | + +### Caveat enforcers + +| Enforcer | Address | +|----------|---------| +| AllowedCalldataEnforcer | `0xc2b0d624c1c4319760C96503BA27C347F3260f55` | +| AllowedMethodsEnforcer | `0x2c21fD0Cb9DC8445CB3fb0DC5E7Bb0Aca01842B5` | +| AllowedTargetsEnforcer | `0x7F20f61b1f09b08D970938F6fa563634d65c4EeB` | +| ArgsEqualityCheckEnforcer | `0x44B8C6ae3C304213c3e298495e12497Ed3E56E41` | +| BlockNumberEnforcer | `0x5d9818dF0AE3f66e9c3D0c5029DAF99d1823ca6c` | +| DeployedEnforcer | `0x24ff2AA430D53a8CD6788018E902E098083dcCd2` | +| ERC20BalanceGteEnforcer | `0x433A6A4d9875D87510584fd6cc586eB1c5F8A1d2` | +| ERC20TransferAmountEnforcer | `0xf100b0819427117EcF76Ed94B358B1A5b5C6D2Fc` | +| ERC20PeriodTransferEnforcer| `0x474e3Ae7E169e940607cC624Da8A15Eb120139aB` | +| ERC20StreamingEnforcer | `0x56c97aE02f233B29fa03502Ecc0457266d9be00e` | +| ERC721BalanceGteEnforcer | `0xA5d03eb350FA89f854685f6313CeCA27A4212542` | +| ERC721TransferEnforcer | `0x3790e6B7233f779b09DA74C72b6e94813925b9aF` | +| ERC1155BalanceGteEnforcer | `0x831b76f53601f38BfaCa2e6b442D6A5408Ae375c` | +| ExactCalldataBatchEnforcer | `0x982FD5C86BBF425d7d1451f974192d4525113DfD` | +| ExactCalldataEnforcer | `0x99F2e9bF15ce5eC84685604836F71aB835DBBdED` | +| ExactExecutionBatchEnforcer | `0x1e141e455d08721Dd5BCDA1BaA6Ea5633Afd5017` | +| ExactExecutionEnforcer | `0x146713078D39eCC1F5338309c28405ccf85Abfbb` | +| IdEnforcer | `0xC8B5D93463c893401094cc70e66A206fb5987997` | +| LimitedCallsEnforcer | `0x04658B29F6b82ed55274221a06Fc97D318E25416` | +| NonceEnforcer | `0xDE4f2FAC4B3D87A1d9953Ca5FC09FCa7F366254f` | +| TimestampEnforcer | `0x1046bb45C8d673d4ea75321280DB34899413c069` | +| ValueLteEnforcer | `0x92Bf12322527cAA612fd31a0e810472BBB106A8F` | +| NativeBalanceGteEnforcer | `0x54e17146b9CCE2642881E0879e06e9D63F7d7606` | +| NativeTokenPaymentEnforcer | `0x4803a326ddED6dDBc60e659e5ed12d85c7582811` | +| NativeTokenTransferAmountEnforcer | `0xF71af580b9c3078fbc2BBF16FbB8EEd82b330320` | +| NativeTokenStreamingEnforcer | `0xD10b97905a320b13a0608f7E9cC506b56747df19` | +| NativeTokenPeriodTransferEnforcer | `0x9BC0FAf4Aca5AE429F4c06aEEaC517520CB16BD9` | +| OwnershipTransferEnforcer | `0x7EEf9734E7092032B5C56310Eb9BbD1f4A524681` | +| RedeemerEnforcer | `0xE144b0b2618071B4E56f746313528a669c7E65c5` | +| SpecificActionERC20TransferBatchEnforcer | `0x00e0251aaA263dfE3B3541B758A82D1CBA1c3B6D` | + +### Supported mainnet networks +- Ethereum +- Polygon +- Binance Smart Chain +- Optimism +- Arbitrum +- Linea +- Base +- Gnosis Chain + +### Supported testnet networks +- Ethereum Sepolia +- Linea Sepolia +- Base Sepolia +- MegaEth \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.2/changelog/0.10.1.md b/gator_versioned_docs/version-0.10.2/changelog/0.10.1.md new file mode 100644 index 00000000000..cee71d13b53 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/changelog/0.10.1.md @@ -0,0 +1,78 @@ +--- +sidebar_label: 0.10.1 +sidebar_position: 2 +description: MetaMask Delegation Toolkit v0.10.1 changelog +--- + +# What's new in v0.10.1? + +## Bug fixes + +- Fixed an issue where the ERC-7715 `PermissionRequest` object could not be serialized when `maxAmount` or `initialAmount` was set to `null`. + +## Contract addresses + +The following are the contract addresses for the +[Delegation Framework version 1.3.0](https://github.com/MetaMask/delegation-framework/blob/v1.3.0/documents/Deployments.md), +as used by this version of the toolkit. + +### Delegation Framework + +| Contract | Address | +|----------|---------| +| EntryPoint | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | +| SimpleFactory | `0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c` | +| DelegationManager | `0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3` | +| MultiSigDeleGatorImpl | `0x56a9EdB16a0105eb5a4C54f4C062e2868844f3A7` | +| HybridDeleGatorImpl | `0x48dBe696A4D990079e039489bA2053B36E8FFEC4` | + +### Caveat enforcers + +| Enforcer | Address | +|----------|---------| +| AllowedCalldataEnforcer | `0xc2b0d624c1c4319760C96503BA27C347F3260f55` | +| AllowedMethodsEnforcer | `0x2c21fD0Cb9DC8445CB3fb0DC5E7Bb0Aca01842B5` | +| AllowedTargetsEnforcer | `0x7F20f61b1f09b08D970938F6fa563634d65c4EeB` | +| ArgsEqualityCheckEnforcer | `0x44B8C6ae3C304213c3e298495e12497Ed3E56E41` | +| BlockNumberEnforcer | `0x5d9818dF0AE3f66e9c3D0c5029DAF99d1823ca6c` | +| DeployedEnforcer | `0x24ff2AA430D53a8CD6788018E902E098083dcCd2` | +| ERC20BalanceGteEnforcer | `0x433A6A4d9875D87510584fd6cc586eB1c5F8A1d2` | +| ERC20TransferAmountEnforcer | `0xf100b0819427117EcF76Ed94B358B1A5b5C6D2Fc` | +| ERC20PeriodTransferEnforcer| `0x474e3Ae7E169e940607cC624Da8A15Eb120139aB` | +| ERC20StreamingEnforcer | `0x56c97aE02f233B29fa03502Ecc0457266d9be00e` | +| ERC721BalanceGteEnforcer | `0xA5d03eb350FA89f854685f6313CeCA27A4212542` | +| ERC721TransferEnforcer | `0x3790e6B7233f779b09DA74C72b6e94813925b9aF` | +| ERC1155BalanceGteEnforcer | `0x831b76f53601f38BfaCa2e6b442D6A5408Ae375c` | +| ExactCalldataBatchEnforcer | `0x982FD5C86BBF425d7d1451f974192d4525113DfD` | +| ExactCalldataEnforcer | `0x99F2e9bF15ce5eC84685604836F71aB835DBBdED` | +| ExactExecutionBatchEnforcer | `0x1e141e455d08721Dd5BCDA1BaA6Ea5633Afd5017` | +| ExactExecutionEnforcer | `0x146713078D39eCC1F5338309c28405ccf85Abfbb` | +| IdEnforcer | `0xC8B5D93463c893401094cc70e66A206fb5987997` | +| LimitedCallsEnforcer | `0x04658B29F6b82ed55274221a06Fc97D318E25416` | +| NonceEnforcer | `0xDE4f2FAC4B3D87A1d9953Ca5FC09FCa7F366254f` | +| TimestampEnforcer | `0x1046bb45C8d673d4ea75321280DB34899413c069` | +| ValueLteEnforcer | `0x92Bf12322527cAA612fd31a0e810472BBB106A8F` | +| NativeBalanceGteEnforcer | `0x54e17146b9CCE2642881E0879e06e9D63F7d7606` | +| NativeTokenPaymentEnforcer | `0x4803a326ddED6dDBc60e659e5ed12d85c7582811` | +| NativeTokenTransferAmountEnforcer | `0xF71af580b9c3078fbc2BBF16FbB8EEd82b330320` | +| NativeTokenStreamingEnforcer | `0xD10b97905a320b13a0608f7E9cC506b56747df19` | +| NativeTokenPeriodTransferEnforcer | `0x9BC0FAf4Aca5AE429F4c06aEEaC517520CB16BD9` | +| OwnershipTransferEnforcer | `0x7EEf9734E7092032B5C56310Eb9BbD1f4A524681` | +| RedeemerEnforcer | `0xE144b0b2618071B4E56f746313528a669c7E65c5` | +| SpecificActionERC20TransferBatchEnforcer | `0x00e0251aaA263dfE3B3541B758A82D1CBA1c3B6D` | + +### Supported mainnet networks +- Ethereum +- Polygon +- Binance Smart Chain +- Optimism +- Arbitrum +- Linea +- Base +- Gnosis Chain + +### Supported testnet networks +- Ethereum Sepolia +- Linea Sepolia +- Base Sepolia +- MegaEth \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.2/changelog/0.10.2.md b/gator_versioned_docs/version-0.10.2/changelog/0.10.2.md new file mode 100644 index 00000000000..08300d26d9f --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/changelog/0.10.2.md @@ -0,0 +1,79 @@ +--- +sidebar_label: 0.10.2 +sidebar_position: 1 +description: MetaMask Delegation Toolkit v0.10.2 changelog +--- + +# What's new in v0.10.2? + +## Bug fixes + +- Fixed the ERC-7715 request permission issue on Firefox. +- Dropped ESM exports to enforce CommonJS module resolution. + +## Contract addresses + +The following are the contract addresses for the +[Delegation Framework version 1.3.0](https://github.com/MetaMask/delegation-framework/blob/v1.3.0/documents/Deployments.md), +as used by this version of the toolkit. + +### Delegation Framework + +| Contract | Address | +|----------|---------| +| EntryPoint | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | +| SimpleFactory | `0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c` | +| DelegationManager | `0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3` | +| MultiSigDeleGatorImpl | `0x56a9EdB16a0105eb5a4C54f4C062e2868844f3A7` | +| HybridDeleGatorImpl | `0x48dBe696A4D990079e039489bA2053B36E8FFEC4` | + +### Caveat enforcers + +| Enforcer | Address | +|----------|---------| +| AllowedCalldataEnforcer | `0xc2b0d624c1c4319760C96503BA27C347F3260f55` | +| AllowedMethodsEnforcer | `0x2c21fD0Cb9DC8445CB3fb0DC5E7Bb0Aca01842B5` | +| AllowedTargetsEnforcer | `0x7F20f61b1f09b08D970938F6fa563634d65c4EeB` | +| ArgsEqualityCheckEnforcer | `0x44B8C6ae3C304213c3e298495e12497Ed3E56E41` | +| BlockNumberEnforcer | `0x5d9818dF0AE3f66e9c3D0c5029DAF99d1823ca6c` | +| DeployedEnforcer | `0x24ff2AA430D53a8CD6788018E902E098083dcCd2` | +| ERC20BalanceGteEnforcer | `0x433A6A4d9875D87510584fd6cc586eB1c5F8A1d2` | +| ERC20TransferAmountEnforcer | `0xf100b0819427117EcF76Ed94B358B1A5b5C6D2Fc` | +| ERC20PeriodTransferEnforcer| `0x474e3Ae7E169e940607cC624Da8A15Eb120139aB` | +| ERC20StreamingEnforcer | `0x56c97aE02f233B29fa03502Ecc0457266d9be00e` | +| ERC721BalanceGteEnforcer | `0xA5d03eb350FA89f854685f6313CeCA27A4212542` | +| ERC721TransferEnforcer | `0x3790e6B7233f779b09DA74C72b6e94813925b9aF` | +| ERC1155BalanceGteEnforcer | `0x831b76f53601f38BfaCa2e6b442D6A5408Ae375c` | +| ExactCalldataBatchEnforcer | `0x982FD5C86BBF425d7d1451f974192d4525113DfD` | +| ExactCalldataEnforcer | `0x99F2e9bF15ce5eC84685604836F71aB835DBBdED` | +| ExactExecutionBatchEnforcer | `0x1e141e455d08721Dd5BCDA1BaA6Ea5633Afd5017` | +| ExactExecutionEnforcer | `0x146713078D39eCC1F5338309c28405ccf85Abfbb` | +| IdEnforcer | `0xC8B5D93463c893401094cc70e66A206fb5987997` | +| LimitedCallsEnforcer | `0x04658B29F6b82ed55274221a06Fc97D318E25416` | +| NonceEnforcer | `0xDE4f2FAC4B3D87A1d9953Ca5FC09FCa7F366254f` | +| TimestampEnforcer | `0x1046bb45C8d673d4ea75321280DB34899413c069` | +| ValueLteEnforcer | `0x92Bf12322527cAA612fd31a0e810472BBB106A8F` | +| NativeBalanceGteEnforcer | `0x54e17146b9CCE2642881E0879e06e9D63F7d7606` | +| NativeTokenPaymentEnforcer | `0x4803a326ddED6dDBc60e659e5ed12d85c7582811` | +| NativeTokenTransferAmountEnforcer | `0xF71af580b9c3078fbc2BBF16FbB8EEd82b330320` | +| NativeTokenStreamingEnforcer | `0xD10b97905a320b13a0608f7E9cC506b56747df19` | +| NativeTokenPeriodTransferEnforcer | `0x9BC0FAf4Aca5AE429F4c06aEEaC517520CB16BD9` | +| OwnershipTransferEnforcer | `0x7EEf9734E7092032B5C56310Eb9BbD1f4A524681` | +| RedeemerEnforcer | `0xE144b0b2618071B4E56f746313528a669c7E65c5` | +| SpecificActionERC20TransferBatchEnforcer | `0x00e0251aaA263dfE3B3541B758A82D1CBA1c3B6D` | + +### Supported mainnet networks +- Ethereum +- Polygon +- Binance Smart Chain +- Optimism +- Arbitrum +- Linea +- Base +- Gnosis Chain + +### Supported testnet networks +- Ethereum Sepolia +- Linea Sepolia +- Base Sepolia +- MegaEth \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.2/changelog/0.9.0.md b/gator_versioned_docs/version-0.10.2/changelog/0.9.0.md new file mode 100644 index 00000000000..f031e2ddaf8 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/changelog/0.9.0.md @@ -0,0 +1,117 @@ +--- +sidebar_label: 0.9.0 +sidebar_position: 4 +description: MetaMask Delegation Toolkit v0.9.0 changelog +--- + +# What's new in v0.9.0? + +:::warning Breaking changes +The ⚠️ symbol denotes potentially breaking API changes. +As per the [semantic versioning specification](https://semver.org/#spec-item-4), from v1.0.0 onwards, +breaking changes will be released only in major version bumps. +::: + +## Breaking Changes + +### ⚠️ Package references + +- Moved the package from `@metamask-private` to `@metamask` organization. +- Renames the `delegator-core-viem` entrypoint to `delegation-toolkit`. + +```typescript +// remove-next-line +- import { toMetaMaskSmartAccount } from "@metamask-private/delegator-core-viem"; +// add-next-line ++ import { toMetaMaskSmartAccount } from "@metamask/delegation-toolkit"; +``` + +### ⚠️ SimpleFactory address + +Updates the `SimpleFactory` contract address. Please note, this will result in a change to the smart account address for `MetaMaskSmartAccount`. + +```typescript +// Delegation enviroment for 1.3.0 +export const deployment_1_3_0 = { +//... +// remove-next-line +- SimpleFactory: '0x6ff518884f21168c30c58CB21184D6AdBC18Ad90', +// add-next-line ++ SimpleFactory: '0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c', +//... +} + +``` + +## Enhancements + +### ⚠️ Package installation + +Package installation no longer requires an authentication token, as it has transitioned out of private alpha. 🎉 + +## Contract addresses + +The following are the contract addresses for the +[Delegation Framework version 1.3.0](https://github.com/MetaMask/delegation-framework/blob/v1.3.0/documents/Deployments.md), +as used by this version of the toolkit. + +### Delegation Framework + +| Contract | Address | +|----------|---------| +| EntryPoint | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | +| SimpleFactory | `0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c` | +| DelegationManager | `0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3` | +| MultiSigDeleGatorImpl | `0x56a9EdB16a0105eb5a4C54f4C062e2868844f3A7` | +| HybridDeleGatorImpl | `0x48dBe696A4D990079e039489bA2053B36E8FFEC4` | + +### Caveat enforcers + +| Enforcer | Address | +|----------|---------| +| AllowedCalldataEnforcer | `0xc2b0d624c1c4319760C96503BA27C347F3260f55` | +| AllowedMethodsEnforcer | `0x2c21fD0Cb9DC8445CB3fb0DC5E7Bb0Aca01842B5` | +| AllowedTargetsEnforcer | `0x7F20f61b1f09b08D970938F6fa563634d65c4EeB` | +| ArgsEqualityCheckEnforcer | `0x44B8C6ae3C304213c3e298495e12497Ed3E56E41` | +| BlockNumberEnforcer | `0x5d9818dF0AE3f66e9c3D0c5029DAF99d1823ca6c` | +| DeployedEnforcer | `0x24ff2AA430D53a8CD6788018E902E098083dcCd2` | +| ERC20BalanceGteEnforcer | `0x433A6A4d9875D87510584fd6cc586eB1c5F8A1d2` | +| ERC20TransferAmountEnforcer | `0xf100b0819427117EcF76Ed94B358B1A5b5C6D2Fc` | +| ERC20PeriodTransferEnforcer| `0x474e3Ae7E169e940607cC624Da8A15Eb120139aB` | +| ERC20StreamingEnforcer | `0x56c97aE02f233B29fa03502Ecc0457266d9be00e` | +| ERC721BalanceGteEnforcer | `0xA5d03eb350FA89f854685f6313CeCA27A4212542` | +| ERC721TransferEnforcer | `0x3790e6B7233f779b09DA74C72b6e94813925b9aF` | +| ERC1155BalanceGteEnforcer | `0x831b76f53601f38BfaCa2e6b442D6A5408Ae375c` | +| ExactCalldataBatchEnforcer | `0x982FD5C86BBF425d7d1451f974192d4525113DfD` | +| ExactCalldataEnforcer | `0x99F2e9bF15ce5eC84685604836F71aB835DBBdED` | +| ExactExecutionBatchEnforcer | `0x1e141e455d08721Dd5BCDA1BaA6Ea5633Afd5017` | +| ExactExecutionEnforcer | `0x146713078D39eCC1F5338309c28405ccf85Abfbb` | +| IdEnforcer | `0xC8B5D93463c893401094cc70e66A206fb5987997` | +| LimitedCallsEnforcer | `0x04658B29F6b82ed55274221a06Fc97D318E25416` | +| NonceEnforcer | `0xDE4f2FAC4B3D87A1d9953Ca5FC09FCa7F366254f` | +| TimestampEnforcer | `0x1046bb45C8d673d4ea75321280DB34899413c069` | +| ValueLteEnforcer | `0x92Bf12322527cAA612fd31a0e810472BBB106A8F` | +| NativeBalanceGteEnforcer | `0x54e17146b9CCE2642881E0879e06e9D63F7d7606` | +| NativeTokenPaymentEnforcer | `0x4803a326ddED6dDBc60e659e5ed12d85c7582811` | +| NativeTokenTransferAmountEnforcer | `0xF71af580b9c3078fbc2BBF16FbB8EEd82b330320` | +| NativeTokenStreamingEnforcer | `0xD10b97905a320b13a0608f7E9cC506b56747df19` | +| NativeTokenPeriodTransferEnforcer | `0x9BC0FAf4Aca5AE429F4c06aEEaC517520CB16BD9` | +| OwnershipTransferEnforcer | `0x7EEf9734E7092032B5C56310Eb9BbD1f4A524681` | +| RedeemerEnforcer | `0xE144b0b2618071B4E56f746313528a669c7E65c5` | +| SpecificActionERC20TransferBatchEnforcer | `0x00e0251aaA263dfE3B3541B758A82D1CBA1c3B6D` | + +### Supported mainnet networks +- Ethereum +- Polygon +- Binance Smart Chain +- Optimism +- Arbitrum +- Linea +- Base +- Gnosis Chain + +### Supported testnet networks +- Ethereum Sepolia +- Linea Sepolia +- Base Sepolia +- MegaEth \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.2/changelog/_category_.json b/gator_versioned_docs/version-0.10.2/changelog/_category_.json new file mode 100644 index 00000000000..f33608bfdf1 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/changelog/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Changelog" +} diff --git a/gator_versioned_docs/version-0.10.2/concepts/_category_.json b/gator_versioned_docs/version-0.10.2/concepts/_category_.json new file mode 100644 index 00000000000..122abce7702 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/concepts/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Concepts", + "position": 3, + "link": { + "type": "generated-index", + "slug": "/concepts", + "title": "Delegation Toolkit concepts" + } +} diff --git a/gator_versioned_docs/version-0.10.2/concepts/caveat-enforcers.md b/gator_versioned_docs/version-0.10.2/concepts/caveat-enforcers.md new file mode 100644 index 00000000000..b394cb4870d --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/concepts/caveat-enforcers.md @@ -0,0 +1,185 @@ +--- +description: Learn about caveat enforcers and how they restrict delegations. +sidebar_position: 4 +--- + +# Caveat enforcers + +The MetaMask Delegation Toolkit 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](delegation.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; + +import { ModeCode } from "../utils/Types.sol"; + +/** + * 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`](../how-to/create-delegation/restrict-delegation.md) interface in the MetaMask Delegation Toolkit. + +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](../how-to/create-delegation/restrict-delegation.md#caveat-types) 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`](../how-to/create-delegation/restrict-delegation.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`](../how-to/create-delegation/restrict-delegation.md#nativetokenpayment) before + [`NativeBalanceGteEnforcer`](../how-to/create-delegation/restrict-delegation.md#nativebalancegte) 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 Delegation Toolkit provides [many out-of-the-box caveat enforcers](../how-to/create-delegation/restrict-delegation.md#caveat-types) +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 more complex scenarios, you can also [create custom caveat enforcers](../how-to/create-delegation/create-custom-caveat-enforcer.md) by implementing the `ICaveatEnforcer` interface. + +## Attenuating authority with redelegations + +When [creating chains of delegations](../how-to/create-delegation/index.md#create-a-redelegation), 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. diff --git a/gator_versioned_docs/version-0.10.2/concepts/delegation.md b/gator_versioned_docs/version-0.10.2/concepts/delegation.md new file mode 100644 index 00000000000..6c07b28bb2d --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/concepts/delegation.md @@ -0,0 +1,90 @@ +--- +description: Learn about delegation, the delegation lifecycle, and the Delegation Framework. +sidebar_position: 2 +--- + +# Delegation + +*Delegation* is the ability for a [delegator account](delegator-accounts.md) to grant permission to another smart contract account (SCA) +or externally owned account (EOA) to perform specific executions on the delegator's behalf, under defined rules and restrictions. + +The MetaMask Delegation Toolkit includes the following delegation features: + +- **Caveats** - 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. + +- **Chain of delegations** - Users can redelegate permissions that have been delegated to them, creating a chain of delegations across trusted parties. + +Delegations are created using the `Delegation` type, which is specified as follows: + +```typescript +export type Delegation = { + delegate: Hex; // The address to which the delegation is being granted. + delegator: Hex; // The address that is granting the delegation. + authority: Hex; // Hash of the parent delegation, or the constant ROOT_AUTHORITY. + caveats: Caveat[]; // Caveats that restrict the authority being granted. + salt: Hex; // Used to avoid hash collisions between identical delegations. + signature: Hex; // Signature from the delegator account. +}; +``` + +## Delegation lifecycle + +The delegation lifecycle is as follows: + +1. **Delegation creation** - A delegation is initialized, and the delegator account signs it. + +2. **Caveat enforcement** - The caveats applied to the delegation specify conditions under which + the delegation can be redeemed. + +3. **Delegation storage** - The delegation can be stored, enabling retrieval for future redemption. + + :::note + [Storing and retrieving delegations](../experimental/store-retrieve-delegations.md) using the toolkit's + `DelegationStorageClient` is an experimental feature. + ::: + +4. **Delegation redemption** - The delegate (the account being granted the permission) redeems the + delegation through an [ERC-4337 user operation](delegator-accounts.md#account-abstraction-erc-4337), + which verifies that the delegated authority is valid in order to perform the execution. + +See [how to create a delegation](../how-to/create-delegation/index.md) to get started with the +delegation lifecycle. + +## Delegation Framework + +The MetaMask Delegation Toolkit 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** - There are [multiple delegator account implementations](delegator-accounts.md#delegator-account-types), + with the main difference being the signature scheme 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 a delegation is redeemed, 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 `permissionContexts`, `modes`, and + `executionCallDatas` match, or throws `BatchDataLengthMismatch`. + 2. Decodes and validates the delegation, checking that the caller (`msg.sender`) is the delegate + and that there are no empty signatures, or throws `InvalidDelegate`. + 3. Verifies delegation signatures, ensuring validity using `ECDSA` (for EOAs) or + `isValidSignature` (for contracts), or throws `InvalidSignature`. + 4. Validates the delegation chain's authority and ensures delegations are not disabled. + 5. Executes the `beforeHook` for each `caveat` 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. diff --git a/gator_versioned_docs/version-0.10.2/concepts/delegator-accounts.md b/gator_versioned_docs/version-0.10.2/concepts/delegator-accounts.md new file mode 100644 index 00000000000..60448c2b7d6 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/concepts/delegator-accounts.md @@ -0,0 +1,79 @@ +--- +description: Learn about account abstraction, the delegator account flow, and account types. +sidebar_position: 1 +--- + +# Delegator accounts + +The MetaMask Delegation Toolkit enables you to create and manage *delegator accounts*. +Delegator accounts are [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) smart contract accounts (SCAs) +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 delegator +accounts use smart contracts to govern account logic. + +## 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 SCAs containing arbitrary verification logic. +ERC-4337 enables SCAs 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 SCA 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 SCAs to facilitate gas abstraction. + +## Delegator account flow + +The MetaMask delegator account flow is as follows: + +1. **Account setup** - A user creates an SCA by deploying a smart contract, and initializing it with + ownership and security settings. + The user can customize the SCA 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 signatory. + +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 account types + +The MetaMask Delegation Toolkit supports two types of delegator accounts, each offering unique features and use cases. +See [Configure accounts and signers](../how-to/configure-delegator-accounts-signers.md) to learn how to use these different account types. + +### Hybrid Delegator + +The Hybrid Delegator is a flexible implementation that supports both an externally owned account (EOA) "owner" and any number of P256 (passkey) signers. +You can configure any of these signers as the signatory, and use them to sign on behalf of the delegator. + +This type is referenced in the toolkit as `Implementation.Hybrid`. + +### Multisig Delegator + +The Multisig Delegator is an implementation that supports multiple signers with a configurable threshold for valid signatures, allowing for enhanced security and flexibility in account management. The signatory must have at least as many signers include as the threshold is configured for the account. + +This type is referenced in the Toolkit as `Implementation.Multisig`. diff --git a/gator_versioned_docs/version-0.10.2/concepts/environment.md b/gator_versioned_docs/version-0.10.2/concepts/environment.md new file mode 100644 index 00000000000..d45a015e98b --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/concepts/environment.md @@ -0,0 +1,228 @@ +--- +description: Learn about the delegator environment object `DeleGatorEnvironment` and how to use it. +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Delegator environment + +The `DeleGatorEnvironment` object is a component of the MetaMask Delegation Toolkit that defines the contract addresses necessary for interacting with the [Delegation Framework](delegation.md#delegation-framework) on a specific network. + +The delegator environment 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 delegator environment + +When you create a [`MetaMaskSmartAccount`](../how-to/create-delegator-account.md) instance, the Delegation 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 +import { DeleGatorEnvironment } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +const environment: DeleGatorEnvironment = delegatorSmartAccount.environment; +``` + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +const delegatorAccount = privateKeyToAccount("0x..."); + +const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegatorAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegatorAccount }, +}); + +export delegatorSmartAccount; +``` + + + + +:::note +See the [changelog](../changelog/0.10.2.md) of the toolkit version you are using for supported chains. +::: + +Alternatively, you can use the `getDelegatorEnvironment` function to resolve the environment. +This function is especially useful if your delegator is not a smart contract account when +[creating a redelegation](../how-to/create-delegation/index.md#create-a-redelegation). + +```typescript +import { + getDeleGatorEnvironment, + DeleGatorEnvironment, +} from "@metamask/delegation-toolkit"; + +// Resolves the DeleGatorEnvironment for Linea Sepolia +const environment: DeleGatorEnvironment = getDelegatorEnvironment(59141); +``` + +## Deploy custom delegator environment + +You can deploy the contracts using any method, but the toolkit provides a convenient `deployDelegatorEnvironment` 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.html), [Wallet Client](https://viem.sh/docs/clients/wallet.html), and [Chain](https://viem.sh/docs/glossary/types#chain) +to deploy the contracts and resolve the `DeleGatorEnvironment`. + +Your wallet must have sufficient native token balance to deploy the contracts. + + + + +```typescript +import { walletClient, publicClient } from "./config.ts"; +import { lineaSepolia as chain } from "viem/chains"; + +const environment = await deployDeleGatorEnvironment( + walletClient, + publicClient, + chain +); +``` + + + + +```typescript +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient, createPublicClient } from "viem"; + +// 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 `deployDelegatorEnvironment`. +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. +import { walletClient, publicClient } from "./config.ts"; +import { lineaSepolia as chain } from "viem/chains"; + +const environment = await deployDeleGatorEnvironment( + walletClient, + publicClient, + chain, + // add-start ++ { ++ EntryPoint: "0x0000000071727De22E5E9d8BAf0edAc6f37da032" ++ } + // add-end +); +``` + +Once the contracts are deployed, you can use them to override the delegator environment. + +## Override delegator environment + +To override the delegator environment, the toolkit provides a `overrideDeployedEnvironment` function to resolve +`DeleGatorEnvironment` with specified contracts for the given chain and contract version. + +```typescript +// The config.ts is the same as in the previous example. +import { walletClient, publicClient } from "./config.ts"; +import { lineaSepolia as chain } from "viem/chains"; +import { + DeleGatorEnvironment, + overrideDeployedEnvironment, + deployDeleGatorEnvironment +} from "@metamask/delegation-toolkit"; + +const environment: DeleGatorEnvironment = await deployDeleGatorEnvironment( + walletClient, + publicClient, + chain +); + +const delegatorEnvironment: DeleGatorEnvironment = overrideDeployedEnvironment( + chainId, + "1.3.0", + environment, +); +``` + +If you've already deployed the contracts using a different method, you can create a `DelegatorEnvironment` instance with the required contract addresses, and pass it to the function. + +```typescript +// remove-start +- import { walletClient, publicClient } from "./config.ts"; +- import { lineaSepolia as chain } from "viem/chains"; +// remove-end +import { + DeleGatorEnvironment, + overrideDeployedEnvironment, + // remove-next-line +- deployDeleGatorEnvironment +} from "@metamask/delegation-toolkit"; + +// remove-start +- const environment: DeleGatorEnvironment = await deployDeleGatorEnvironment( +- walletClient, +- publicClient, +- chain +- ); +// remove-end + +// add-start ++ const evniroment: DeleGatorEnvironment = { ++ SimpleFactory: "0x124..", ++ // ... ++ implementations: { ++ // ... ++ }, ++ }; +// add-end + +const delegatorEnvironment: DeleGatorEnvironment = overrideDeployedEnvironment( + chainId, + "1.3.0", + environment +); +``` + +:::note +Make sure to specify the Delegation Framework version required by the toolkit. +See the [changelog](../changelog/0.10.2.md) of the toolkit version you are using for its required Framework version. +::: diff --git a/gator_versioned_docs/version-0.10.2/experimental/_category_.json b/gator_versioned_docs/version-0.10.2/experimental/_category_.json new file mode 100644 index 00000000000..2592b227a06 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/experimental/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Experimental", + "position": 5, + "link": { + "type": "generated-index", + "slug": "/experimental", + "title": "Delegation Toolkit experimental features" + } +} \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.2/experimental/erc-7710-redeem-delegations.md b/gator_versioned_docs/version-0.10.2/experimental/erc-7710-redeem-delegations.md new file mode 100644 index 00000000000..85325565000 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/experimental/erc-7710-redeem-delegations.md @@ -0,0 +1,237 @@ +--- +description: Learn how to redeem ERC-7710 delegations with a smart contract account or an externally owned account (EOA). +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# ERC-7710: Redeem delegations + +:::caution Experimental +This is an experimental feature and may change in future releases. +::: + +[ERC-7710](https://eip.tools/eip/7710) introduces a standard way for smart contract accounts (SCAs) to delegate capabilities to other +SCAs or externally owned accounts (EOAs). + +The MetaMask Delegation Toolkit provides two experimental functions, `erc7710BundlerActions()` and `erc7710WalletActions()`, that let +a caller redeem delegations granted by MetaMask's permissions system. + +## Extract relevant data + +Refer to [ERC-7715: Request permissions](erc-7715-request-permissions.md) for information on how to request user permissions. +Once the permission has been granted, extract the relevant data from the response. +For example: + +```typescript +// Response received from the ERC-7715 wallet_grantPermissions request. +const permissionsResponse = [{ + chainId: "0xe715", + account: "0xD6f56C2B10b1e02D841E4a97c60Afe914E884DBd", + expiry: 1234567890, + permission: { + type: "native-token-stream", + data: { + amountPerSecond: "0x1", + maxAmount: "0x2", + initialAmount: undefined, + startTime: 2, + }, + }, + context: "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d" + signer: { + type: "account", + data: { + account: "0x07bfc7230D5BD2544059816D88A895BB000Abe00" + } + }, + signerMeta: { + delegationManager: "0xDC7e12b41E5e61BfCc7F56AAFB7B93288F61e841" + }, + accountMetadata: [{ + factory: "0x65E726b404149fE37F4b291c81Dc6eddd44763A7", + factoryData: "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b" + }] +}]; + +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +// accountMeta is only present when the smart contract account is not deployed. +const accountMetadata = permissionsResponse[0].accountMeta; +``` + +This data encodes the authority that lets the delegate redeem the permission. + +### Security considerations for `accountMeta` + +When a user grants a permission, they can provide `accountMeta` which is an array of `factory` and `factoryData` values. +These calls must be executed before redeeming the permission (this is handled for you in `sendUserOperationWithDelegation`). + +Because each `accountMeta` is an arbitrary call specified by the granter, it is important that these are executed carefully. +We recommend taking the following precautions: + +- **Only grant permissions to session accounts** - When requesting permissions, use an account that is only used for that single purpose, and does not contain tokens. +This way, any `accountMeta` executed can't perform any damaging actions. + +- **Only execute `accountMeta` against trusted factory addresses** - Ensure that only `accountMeta` targeting a known factory address is executed. +The bundler action `sendUserOperationWithDelegation` only executes `accountMeta` that targets the `SimpleFactory` address for the current Delegation Framework. +If you redeem delegations in any other way, it is your responsibility to validate trusted factory addresses. + +## Redeem the permission + +Redeem a delegation using one of two methods. Choose the method based on your account type: + +- If redeeming with an SCA, call `sendUserOperationWithDelegation`. +- If redeeming with an EOA, call `sendTransactionWithDelegation`. + +### Redeem with an SCA + +To redeem a delegation with a smart contract account, create a [`MetaMaskSmartAccount`](../how-to/create-delegator-account.md#create-a-metamasksmartaccount) +and a [Viem Bundler Client](https://viem.sh/account-abstraction/clients/bundler). + +After setting up your Bundler Client, you can extend its functionality with `erc7710BundlerActions` actions to support ERC-7710. Once extended, use `sendUserOperationWithDelegation` to redeem the permission. + + + + +```typescript +import { sessionAccount, bundlerClient, publicClient } from "./config.ts"; + +// These properties must be extracted from the permission response. +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +const accountMetadata = permissionsResponse[0].accountMeta; + +// 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 + accountMetadata, +}); +``` + + + + +```typescript +import { createPublicClient, http, createBundlerClient } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { erc7710BundlerActions } from "@metamask/delegation-toolkit/experimental"; +import { toMetaMaskSmartAccount, Implementation } from "@metamask/delegation-toolkit"; + +export const publicClient = createPublicClient({ + chain: chain, + transport: http(), +}); + +// Your session account for requesting and redeeming should be same. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + + +export const sessionAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { 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()); +``` + + + +:::note +`sendUserOperationWithDelegation` is similar to the `sendUserOperation` function, but does not accept `callData` directly. +::: + +### Redeem with an EOA + +To redeem a delegation with an EOA, create a [Viem Wallet Client](https://viem.sh/docs/clients/wallet). + +After creating your Wallet Client, you can extend its functionality with `erc7710WalletActions` actions to support ERC-7710. Once extended, use `sendTransactionWithDelegation` to redeem the permission. + + + + +```typescript +import { walletClient, publicClient } from "./config.ts"; + +// These properties must be extracted from the permission response. +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +const accountMetadata = permissionsResponse[0].accountMeta; + +if (accountMetadata?.length !== 0) { + // If the granted permission contains accountMetadata, this must be executed before attempting to + // redeem the delegation. + + // This transaction will deploy the delegator account. + const hash = walletClient.sendTransaction({ + to: accountMetadata.factory, + data: accountMetadata.factoryData, + }); + + // You should wait for transaction to be successfully executed. + // You can use the TransactionReceipt.status to verify the state. + await publicClient.waitForTransactionReceipt( { hash }); +} + +const hash = walletClient.sendTransactionWithDelegation({ + chain, + to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + value: 1n, + permissionsContext, + delegationManager +}); +``` + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { erc7710WalletActions } from "@metamask/delegation-toolkit/experimental"; + +export const publicClient = createPublicClient({ + chain, + transport: http() +}); + +// Your session account for requesting and redeeming should be same. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + +const walletClient = createWalletClient({ + account, + transport: http(), + chain, +}).extend(erc7710WalletActions()); +``` + + diff --git a/gator_versioned_docs/version-0.10.2/experimental/erc-7715-request-permissions.md b/gator_versioned_docs/version-0.10.2/experimental/erc-7715-request-permissions.md new file mode 100644 index 00000000000..8d605b4a76a --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/experimental/erc-7715-request-permissions.md @@ -0,0 +1,182 @@ +--- +description: Learn how to request ERC-7715 permissions. +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# ERC-7715: Request permissions + +:::caution Experimental +This is an experimental feature. +It requires MetaMask Flask 12.14.2 or later, and may change in future releases. +::: + +[ERC-7715](https://eip.tools/eip/7715) introduces a standard way for dapps to request permissions from a wallet to execute +transactions on a user's behalf. + +The MetaMask Delegation Toolkit provides the experimental actions for ERC-7715 that lets a caller request permissions from MetaMask's permissions system. + +## Request permissions + +To request permissions, extend your [Viem Wallet Client](https://viem.sh/docs/clients/wallet) with `erc7715ProviderActions` actions. +You'll need a session account to request the permission, which can be either an externally owned account (EOA) or a smart contract account (SCA). +This example uses an SCA: + + + + +```typescript +import { sepolia as chain } from "viem/chains"; +import { sessionAccount, walletClient } from "./config.ts"; + +const expiry = Math.floor(Date.now() / 1000 + 604_800); // 1 week from now. +const currentTime = Math.floor(Date.now() / 1000); // now + +const grantedPermissions = await walletClient.grantPermissions([{ + chainId: chain.id, + expiry, + signer: { + type: "account", + data: { + address: sessionAccount.address, + }, + }, + permission: { + type: "native-token-stream", + data: { + initialAmount: 1n, // 1 wei + amountPerSecond: 1n, // 1 wei per second + maxAmount: 10n, // 10 wei + startTime: currentTime, + justification: "Payment for a week long subscription", + }, + }, +}]); +``` + + + + + +```typescript +import { createWalletClient, custom, createPublicClient, http } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { erc7715ProviderActions } from "@metamask/delegation-toolkit/experimental"; +import { toMetaMaskSmartAccount, Implementation } from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain: chain, + transport: http(), +}); + +// The private key of the session owner. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + +export const sessionAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); + +export const walletClient = createWalletClient({ + transport: custom(window.ethereum), +}).extend(erc7715ProviderActions()); +``` + + + +
+ ERC-7715 request permission sample + + Here's what your ERC-7715 native token streaming allowance request looks like: + + ```ts + [{ + chainId: "0xaa36a7", + expiry: 1745041429, + permission: { + type: "native-token-stream", + data: { + amountPerSecond: "0x1", + maxAmount: "0x1", + initialAmount: "0xa", + startTime: 1744955029, + justification: "Payment for a week long subscription", + }, + }, + signer: { + type: "account", + data: { + address: "0x1234...", + }, + }, + }] + ``` + + Learn more about the [ERC-7715 permission schema](https://eip.tools/eip/7715). +
+ + +Additionally, you can allow users to modify the requested permission by setting the `isAdjustmentAllowed` parameter to `true` in the request. + +```typescript +import { sepolia as chain } from "viem/chains"; +// The config.ts is the same as in the previous example. +import { sessionAccount, walletClient } from "./config.ts"; + +const expiry = Math.floor(Date.now() / 1000 + 604_800); // 1 week from now. +const currentTime = Math.floor(Date.now() / 1000); // now + +const grantedPermissions = await walletClient.grantPermissions([{ + chainId: chain.id, + expiry, + signer: { + type: "account", + data: { + address: sessionAccount.address, + }, + }, + permission: { + type: "native-token-stream", + // add-next-line ++ isAdjustmentAllowed: true, + data: { + initialAmount: 1n, // 1 wei + amountPerSecond: 1n, // 1 wei per second + maxAmount: 10n, // 10 wei + startTime: currentTime, + justification: "Payment for a week long subscription", + }, + }, +}]); +``` + +:::note +Users have full control over the permissions they grant—depending on the permission you request, they may choose to grant more limited permissions than requested. +You should always verify the granted permissions and adjust your dapp's behavior accordingly. +::: + +## Security considerations for `accountMeta` + +When a user grants a permission, they can provide [`accountMeta`](erc-7710-redeem-delegations.md#extract-relevant-data) which is an array of `factory` and `factoryData` values. +These calls must be executed before redeeming the permission (this is handled for you in `sendUserOperationWithDelegation`). + +Because each `accountMeta` is an arbitrary call specified by the granter, it is important that these are executed carefully. +We recommend taking the following precautions: + +- **Only grant permissions to session accounts** - When requesting permissions, use an account that is only used for that single purpose, and does not contain tokens. + This way, any `accountMeta` executed can't perform any damaging actions. + +- **Only execute `accountMeta` against trusted factory addresses** - Ensure that only `accountMeta` targeting a known factory address is executed. + The bundler action `sendUserOperationWithDelegation` only executes `accountMeta` that targets the `SimpleFactory` address for the current Delegation Framework. + If you redeem delegations in any other way, it is your responsibility to validate trusted factory addresses. + +## Next steps + +You can redeem the granted permission using the experimental [ERC-7710 `erc7710WalletActions()`](erc-7710-redeem-delegations.md). diff --git a/gator_versioned_docs/version-0.10.2/experimental/store-retrieve-delegations.md b/gator_versioned_docs/version-0.10.2/experimental/store-retrieve-delegations.md new file mode 100644 index 00000000000..3b6cde29fd6 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/experimental/store-retrieve-delegations.md @@ -0,0 +1,172 @@ +--- +description: Store and retrieve delegations using the `DelegationStorageClient`. +sidebar_position: 1 +toc_max_heading_level: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Store and retrieve delegations + +:::caution Experimental +This is an experimental feature and may change in future releases. +::: + +You can use methods provided by the `DelegationStorageClient` of the MetaMask Delegation Toolkit to store and retrieve +[delegations](../concepts/delegation.md). + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](../how-to/configure.md) +- Ensure you have an API key and API key ID to interact with the `DelegationStorageClient`. + If you need to gain access, email hellogators@consensys.net. + +## Configure the storage client + +Create the `DelegationStorageClient` instance, and configure it using your API key and API key ID. + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + +## Store a delegation + +To store a delegation, use the `storeDelegation` method of the `DelegationStorageClient`. This method takes one parameter: + +1. `delegation` - A `Delegation` object representing the delegation to be stored. + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; + +const delegationHash = await delegationStorageClient.storeDelegation(delegation); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + + +## Retrieve a delegation chain + +To retrieve a delegation chain, use the `getDelegationChain` method of the `DelegationStorageClient`. This method takes one parameter: + +1. `leafDelegationOrDelegationHash` - Either a `Delegation` object or the delegation hash as a hex string. + +:::note +A delegation can be a root delegation, where its `authority` is `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`. It can also be a child of another delegation, where its `authority` is the hash of its parent delegation. This method returns the delegation referenced by `leafDelegationOrDelegationHash` and any ancestors. +::: + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; +import { getDelegationHashOffchain } from "@metamask/delegation-toolkit"; + +// Assuming you have the leaf delegation +const delegationHash = getDelegationHashOffchain(leafDelegation); + +const delegationChain: Delegation[] = await delegationStorageClient.getDelegationChain( + delegationHash +); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + + +## Retrieve delegations for a specific account + +To retrieve delegations stored for a specific account, use the `fetchDelegations` method of the `DelegationStorageClient`. This method allows you to fetch delegations where the specified account is either the delegator or the delegate. +It takes two parameters: + +1. `account` - The address of the account for which you want to retrieve delegations. +2. `filter` - The nature of the delegations. Possible values are: + - `DelegationStoreFilter.Given` - For delegations where the specified `account` is the `delegator`. + - `DelegationStoreFilter.Received` - For delegations where the specified `account` is the `delegate`. + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; + +const address = "0x027aeAFF3E5C33c4018FDD302c20a1B83aDCD96C" + +// Fetch the delegations given by address. +const grantedDelegations = await delegationStorageClient.fetchDelegations( + address, + DelegationStoreFilter.Given, +); + +// Fetch the delegations received by the address. +const receivedDelegations = await delegationStore.fetchDelegations( + address, + DelegationStoreFilter.Received, +); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + diff --git a/gator_versioned_docs/version-0.10.2/get-started/_category_.json b/gator_versioned_docs/version-0.10.2/get-started/_category_.json new file mode 100644 index 00000000000..bc9fe8e48f6 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/get-started/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Get started", + "position": 2, + "link": { + "type": "generated-index", + "slug": "/get-started", + "title": "Get started with the Delegation Toolkit" + } +} \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.2/get-started/install.md b/gator_versioned_docs/version-0.10.2/get-started/install.md new file mode 100644 index 00000000000..060d67f0ecd --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/get-started/install.md @@ -0,0 +1,48 @@ +--- +sidebar_label: Install and set up +description: Learn how to install and set up the MetaMask Delegation Toolkit. +sidebar_position: 1 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Install and set up the Delegation Toolkit + +This page provides instructions to install and set up the MetaMask Delegation Toolkit. + +## 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](../how-to/create-delegation/create-custom-caveat-enforcer.md)), install + [Foundry](https://book.getfoundry.sh/getting-started/installation). + +## Steps + +### 1. Install the toolkit + +Install the MetaMask Delegation Toolkit dependencies: + +```bash npm2yarn +npm install @metamask/delegation-toolkit +``` + +### 2. (Optional) Install the contracts + +If you plan to extend the Delegation Framework smart contracts (for example, to +[create a custom caveat enforcer](../how-to/create-delegation/create-custom-caveat-enforcer.md)), 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 MetaMask Delegation Toolkit. +Check out the [Delegation Toolkit quickstart](quickstart.md) to walk through a simple example. \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.2/get-started/quickstart.md b/gator_versioned_docs/version-0.10.2/get-started/quickstart.md new file mode 100644 index 00000000000..41b7d5462cc --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/get-started/quickstart.md @@ -0,0 +1,173 @@ +--- +description: Get started quickly with the MetaMask Delegation Toolkit. +sidebar_position: 2 +sidebar_label: Quickstart +--- + +# Delegation Toolkit quickstart + +This page demonstrates how to get started quickly with the MetaMask Delegation Toolkit, +by creating a delegator account and completing the delegation lifecycle (creating, signing, and redeeming a delegation). + +## Prerequisites + +[Install and set up the Delegation Toolkit.](install.md) + +## Steps + +### 1. 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 delegator account query the signer's account state and interact with smart contracts. + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const publicClient = createPublicClient({ + chain, + transport: http(), +}); +``` + +### 2. 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 +import { createBundlerClient } from "viem/account-abstraction"; + +const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http("https://your-bundler-rpc.com"), +}); +``` + +### 3. Create a delegator account + +[Create a delegator account](../how-to/create-delegator-account.md) to set up a delegation. +The delegator must be a smart account. + +This example configures a [Hybrid Delegator](../how-to/configure-delegator-accounts-signers.md#configure-a-hybrid-delegator): + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; + +const delegatorAccount = privateKeyToAccount("0x..."); + +const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegatorAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegatorAccount }, +}); +``` + +### 4. Create a delegate account + +Create a delegate account to receive the delegation. +The delegate can be either a smart contract account (SCA) or an externally owned account (EOA). + +This example uses an SCA: + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; + +const delegateAccount = privateKeyToAccount("0x..."); + +const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + +### 5. Create a delegation + +[Create a root delegation](../how-to/create-delegation/index.md#create-a-root-delegation) from the +delegator account to the delegate account. + +This example passes an empty `caveats` array, which means the delegate can perform any action on the delegator's behalf. +We recommend [restricting the delegation](../how-to/create-delegation/restrict-delegation.md) by adding caveat enforcers. + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +const delegation = createDelegation({ + to: delegateSmartAccount.address, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + +### 6. Sign the delegation + +[Sign the delegation](../how-to/create-delegation/index.md#sign-a-delegation) using the `signDelegation` method from `MetaMaskSmartAccount`. +Alternatively, you can use the Delegation Toolkit's `signDelegation` utility. +The signed delegation will be used later to perform actions on behalf of the delegator. + +```typescript +const signature = await delegatorSmartAccount.signDelegation({ + delegation +}); + +const signedDelegation = { + ...delegation, + signature, +}; +``` + +### 7. Redeem the delegation + +The delegate account can now [redeem the delegation](../how-to/redeem-delegation.md). +The redeem transaction is sent to the `DelegationManager` contract, which validates the delegation and +executes actions on the delegator's behalf. + +To prepare the call data for the redeem transaction, use the `redeemDelegation` utility function from the Delegation Toolkit. + +```typescript +import { + createExecution, + DelegationFramework, + SINGLE_DEFAULT_MODE, +} from "@metamask/delegation-toolkit"; +import { zeroAddress } from "viem"; + +const delegations = [ signedDelegation ]; + +const executions = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ SINGLE_DEFAULT_MODE ], + executions: [ executions ] +}); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: delegateSmartAccount.address, + data: redeemDelegationCalldata + } + ], + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, +}); +``` diff --git a/gator_versioned_docs/version-0.10.2/how-to/_category_.json b/gator_versioned_docs/version-0.10.2/how-to/_category_.json new file mode 100644 index 00000000000..2b6df81f83d --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/how-to/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "How to", + "position": 4, + "link": { + "type": "generated-index", + "slug": "/how-to", + "title": "Delegation Toolkit how-to guides" + } +} diff --git a/gator_versioned_docs/version-0.10.2/how-to/configure-delegator-accounts-signers.md b/gator_versioned_docs/version-0.10.2/how-to/configure-delegator-accounts-signers.md new file mode 100644 index 00000000000..c25833669be --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/how-to/configure-delegator-accounts-signers.md @@ -0,0 +1,303 @@ +--- +sidebar_label: Configure accounts and signers +description: Learn how to configure different types of delegator accounts and signers using Viem. +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Configure delegator accounts and signers + +The MetaMask Delegation Toolkit supports different [delegator account types](../concepts/delegator-accounts.md#delegator-account-types), +each with its own configuration and support for different signing mechanisms. +You can create flexible and secure delegator accounts tailored to your specific needs. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator account.](create-delegator-account.md) + +## Configure a Hybrid Delegator + +The [Hybrid Delegator](../concepts/delegator-accounts.md#hybrid-delegator) supports both an EOA "owner" and any number of P256 (passkey) signers. + +To configure a Hybrid Delegator, provide the following parameters: + +- `owner`: The owner's account address as a hex string. + The owner can be the zero address, indicating that there is no owner configured. +- `p256KeyIds`: An array of key identifiers for P256 signers as hex strings. +- `p256XValues`: An array of public key x-values for P256 signers as `bigint`s. +- `p256YValues`: An array of public key y-values for P256 signers as `bigint`s. +- `signatory`: A signer that will sign on behalf of the delegator account. + +:::note +You can set all `p256` parameters to empty arrays to configure no WebAuthn signer. +However, we recommend configuring at least one signer for account recoverability. +::: + +For a Hybrid Delegator, you can configure the following types of signatories: + +### Configure an account signatory + +This example creates a signatory from a private key using Viem's [`privateKeyToAccount`](https://viem.sh/docs/accounts/local/privateKeyToAccount) function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const signatory = { account }; +``` + + + + +### Configure a Wallet Client signatory + +This example creates a [Viem Wallet Client](https://viem.sh/docs/clients/wallet) as the signatory, +using Viem's `createWalletClient` function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient } from "viem"; + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +const walletClient = createWalletClient({ + account, + chain, + transport: http() +}) + +export const signatory = { walletClient }; +``` + + + + +### Configure a WebAuthn (passkey) signatory + +This example creates a [Viem WebAuthn Account](https://viem.sh/account-abstraction/accounts/webauthn) as the signatory, +using Viem's `toWebAuthnAccount` function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { createCredential, parsePublicKey } from "webauthn-p256"; +import { toWebAuthnAccount } from "viem/account-abstraction"; +import { toHex } from "viem"; + +const credential = await createCredential({ name: "Your Delegator Passkey" }); +const webAuthnAccount = toWebAuthnAccount({ credential }); +const keyId = toHex("my-key-id"); + +const signatory = { webAuthnAccount, keyId }; +``` + + + + + +## Configure a Multisig Delegator + +The [Multisig Delegator](../concepts/delegator-accounts.md#multisig-delegator) supports multiple EOA signers with a configurable threshold for execution. + +To configure a Multisig Delegator, provide the following parameters: + +- `signers`: An array of EOA signer addresses as hex strings. +- `threshold`: The number of signers required to execute a transaction, as a `bigint`. +- `signatory`: An array of signatories that will sign on behalf of the delegator account. + +### Configure signatories + +For a Multisig Delegator, you can use a combination of account signatories and Wallet Client signatories. +For example: + + + + +```typescript +import { publiClient } from "./client.ts"; +import { account, walletClient } from "./signers.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const signers = [ account.address, walletClient.address ]; +const signatory = { account, walletClient }; +const threshold = 2n + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.MultiSig, + deployParams: [signers, threshold], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient } from "viem"; + +// 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 walletClientPivatekey = generatePrivateKey(); +const walletClientAccount = privateKeyToAccount(walletClientPivatekey); + +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. +::: diff --git a/gator_versioned_docs/version-0.10.2/how-to/configure.md b/gator_versioned_docs/version-0.10.2/how-to/configure.md new file mode 100644 index 00000000000..e85115c1408 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/how-to/configure.md @@ -0,0 +1,60 @@ +--- +description: Learn how to configure the MetaMask Delegation Toolkit using Viem. +sidebar_position: 1 +sidebar_label: Configure the toolkit +--- + +# Configure the Delegation Toolkit + +The MetaMask Delegation Toolkit enables you to easily integrate delegator accounts into your dapp, enabling a more flexible, secure, and frictionless experience for your users. + +The toolkit is highly configurable, allowing you to tailor it to your project's specific needs. It includes support for custom signers, multiple signatory schemes, custom paymasters and bundlers, and more. + +:::note +The MetaMask Delegation Toolkit provides custom middleware for [Pimlico's](https://docs.pimlico.io/) gas fee resolver, paymaster, and bundler. Additional options will be made available soon. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit](../get-started/install.md). +- Optionally, complete the [Delegation Toolkit quickstart](../get-started/quickstart.md) to + familiarize yourself with the toolkit's capabilities. + +## Viem's Account Abstraction API + +The toolkit uses Viem's Account Abstraction API. This provides a robust and flexible foundation for creating and managing smart contract accounts. +See Viem's [Smart Account documentation](https://viem.sh/account-abstraction/accounts/smart) for more information on the API's features, methods, and best practices. + + +## Configure Viem bundler and paymaster clients + +To use the bundler and paymaster clients with the toolkit, create instances of these clients and configure them as follows: + +```typescript +import { + createPaymasterClient, + createBundlerClient, +} from "viem/account-abstraction"; +import { http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +// 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, +}); +``` + +:::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 sufficient funds to pay for gas fees directly. +::: + diff --git a/gator_versioned_docs/version-0.10.2/how-to/create-delegation/create-custom-caveat-enforcer.md b/gator_versioned_docs/version-0.10.2/how-to/create-delegation/create-custom-caveat-enforcer.md new file mode 100644 index 00000000000..77416e901f5 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/how-to/create-delegation/create-custom-caveat-enforcer.md @@ -0,0 +1,149 @@ +--- +description: Learn how to create, deploy, and apply a custom caveat enforcer +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a custom caveat enforcer + +When [restricting a delegation](restrict-delegation.md), the MetaMask Delegation Toolkit provides some [out-of-the-box caveat enforcers](restrict-delegation.md#caveat-types) +that cover common use cases. +For more granular or custom control, you can follow the instructions on this page to create custom caveat enforcers from scratch. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../get-started/install.md) +- [Configure the Delegation Toolkit.](../configure.md) + +## Steps + +### 1. Create the caveat enforcer + +Create a contract that extends the +[`ICaveatEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/interfaces/ICaveatEnforcer.sol) +interface. + +For example, the following is a simple caveat enforcer that only allows a delegation to be redeemed after a specific timestamp. + +```solidity title="AfterTimestampEnforcer.sol" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import { CaveatEnforcer } from "@delegator/src/enforcers/CaveatEnforcer.sol"; +import { ModeCode } from "../utils/Types.sol"; + +contract AfterTimestampEnforcer is CaveatEnforcer { + /** + * @notice The delegation may only be redeemed after the specified timestamp - validAfter in seconds. + * @param _terms The validAfter, timestamp in seconds after which the delegation may be redeemed. + * @param _delegationHash - The hash of the delegation being operated on. + */ + function beforeHook( + bytes calldata _terms, + bytes calldata, + ModeCode, + bytes calldata, + bytes32 _delegationHash, + address, + address _redeemer + ) + public + override + { + // Enforces the conditions that should hold before a transaction is performed. + // This function MUST revert if the conditions are not met. + // Get the current timestamp + uint256 timestamp = block.timestamp; + + uint256 validAfter = uint256(bytes32(_terms)); + + require(timestamp > validAfter, "AfterTimestampEnforcer:cannot-redeem-too-early"); + } +} +``` + +### 2. Deploy the caveat enforcer + +Deploy your custom caveat enforcer to obtain its contract address. +For example, you can [deploy your smart contract using Forge](https://book.getfoundry.sh/forge/deploying). + +### 3. Apply the caveat enforcer + +When creating a delegation, add the `Caveat` for the custom caveat to the `CaveatBuilder`. +Learn more about [applying caveats to a delegation](restrict-delegation.md). + +The following example uses the custom `AfterTimestampEnforcer.sol` caveat enforcer to create a delegation granting +an allowance of 1,000,000 wei that can only be spent after one hour from when the delegation is created. + +:::warning Important +Depending on the use case, it may be necessary to add additional caveats to restrict the delegation further. +::: + + + + +```typescript +import { + createCaveatBuilder, + createDelegation, +} from "@metamask/delegation-toolkit"; +import { toHex } from "viem"; +import { delegatorSmartAccount } from "./config.ts"; + +const environment = delegatorSmartAccount.enviroment; + +// Replace this with the address where the AfterTimestampEnforcer.sol contract is deployed. +const afterTimestampEnforcer = "0x22Ae4c4919C3aB4B5FC309713Bf707569B74876F"; + +const caveatBuilder = createCaveatBuilder(environment); + +const tenAM = 10 * 60 * 60; // 10:00 AM as seconds since midnight. + +const caveats = caveatBuilder + .addCaveat("nativeTokenTransferAmount", 1_000_000) + .addCaveat({ + enforcer: afterTimestampEnforcer, + terms: toHex(tenAm) + }); + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats +}); +``` + + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + diff --git a/gator_versioned_docs/version-0.10.2/how-to/create-delegation/index.md b/gator_versioned_docs/version-0.10.2/how-to/create-delegation/index.md new file mode 100644 index 00000000000..8cd0f457f6f --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/how-to/create-delegation/index.md @@ -0,0 +1,349 @@ +--- +description: Learn how to create different types of delegations, and how to sign a delegation. +sidebar_position: 6 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a delegation + +The MetaMask Delegation Toolkit enables you to create [delegations](../../concepts/delegation.md) +from a delegator account to a delegate account. + +:::note +Delegations are compatible with [ERC-7710](https://eip.tools/eip/7710) and [ERC-7715](https://ethereum-magicians.org/t/erc-7715-grant-permissions-from-wallets/20100), to support a standardized minimal interface. +[Requesting ERC-7715 permissions](../../experimental/erc-7715-request-permissions.md) and [redeeming ERC-7710 delegations](../../experimental/erc-7710-redeem-delegations.md) +are experimental features. +::: + +:::warning +The examples on this page demonstrate delegations without any restrictions. +Unrestricted delegations grant complete control over the account to the delegate, which can pose significant security risks. +It is crucial to add caveats to limit the delegated authority. +Learn how to [restrict a delegation](./restrict-delegation.md) using caveat enforcers. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../get-started/install.md) +- [Configure the Delegation Toolkit.](../configure.md) +- [Create a delegator account.](../create-delegator-account.md) + +## Create a root delegation + +A *root delegation* is a delegation that doesn't derive its authority from another delegation. +It is when a delegator delegates its own authority away, as opposed to a [redelegation](#create-a-redelegation). +Create a root delegation as follows: + + + + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.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: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create an 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. +You must create open root delegations carefully, to ensure that they are not misused. +Create an open root delegation by setting the delegate property to the special address +`0x0000000000000000000000000000000000000a11` (available via the constant `ANY_BENEFICIARY`). + + + + +```typescript +import { createOpenDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +const openRootDelegation = createOpenDelegation({ + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create a redelegation + +A recipient of a delegation (the delegate), can *redelegate* that authority to a third party, potentially applying additional [restrictions](restrict-delegation.md). +Create a redelegation as follows: + + + + +```typescript +import { + createDelegation, + getDelegationHashOffchain + } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +// The address is used as the root delegator. While creating the redelegation, +// the root delegate address will be the delegator address. +const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +// The authority is the (typed data) hash of the delegation from which the authority is derived. +const parentDelegationHash = getDelegationHashOffchain(delegation); + +// The address is used as the delegate address while creating the redelegation. +const leafDelegate = "0xb4821Ab7d5942Bd2533387592068a12608B4a52C" + +const leafDelegation = createDelegation({ + to: leafDelegate, + from: delegate, + // You can also choose to pass the parent delegation object, and let function + // handle the rest. + parentDelegation: parentDelegationHash, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create an open redelegation + +An *open redelegation* is a [redelegation](#create-a-redelegation) that doesn't specify a delegate. +This means that any account can redeem the redelegation. +As with [open root delegations](#create-an-open-root-delegation), you must create open redelegations carefully, +to ensure that they are not misused. +Create an open redelegation as follows: + + + + +```typescript +import { + createDelegation, + createOpenDelegation, + getDelegationHashOffchain + } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +// The address is used as the root delegator. While creating the redelegation, +// the root delegate address will be the delegator address. +const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +// The authority is the (typed data) hash of the delegation from which the authority is derived. +const parentDelegationHash = getDelegationHashOffchain(delegation); + +const leafDelegation = createOpenDelegation({ + from: delegate, + // You can also choose to pass the parent delegation object, and let the function + // handle the rest. + parentDelegation: parentDelegationHash, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Sign a delegation + +A delegation must be signed by the delegator to be valid for redemption. The `MetaMaskSmartAccount` supports signing the delegation using [EIP-712 Typed Data](https://eips.ethereum.org/EIPS/eip-712) via the `signDelegation` method. +Sign a delegation as follows: + + + + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.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: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +const signature = await delegatorSmartAccount.signDelegation({ delegation }); + +const signedDelegation = { + ...delegation, + signature +}; +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); + +``` + + + \ No newline at end of file diff --git a/gator_versioned_docs/version-0.10.2/how-to/create-delegation/restrict-delegation.md b/gator_versioned_docs/version-0.10.2/how-to/create-delegation/restrict-delegation.md new file mode 100644 index 00000000000..83908aedd75 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/how-to/create-delegation/restrict-delegation.md @@ -0,0 +1,807 @@ +--- +description: Learn how to restrict a delegation using caveat enforcers, and the available caveat types. +sidebar_position: 1 +toc_max_heading_level: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Restrict a delegation + +Use [caveat enforcers](../../concepts/caveat-enforcers.md) to apply specific rules and restrictions +to a delegation, ensuring that delegated executions are only performed under predefined circumstances. + +A delegation has a `caveats` property, which is an array of `Caveat` objects. +Each caveat is specified as follows: + +```typescript +export type Caveat = { + enforcer: Hex; // The address of the caveat enforcer contract. + terms: Hex; // Data passed to the caveat enforcer, describing how the redemption should be validated. + args: Hex; // Data that may be specified by the redeemer when redeeming the delegation (only used in limited cases). +}; +``` + +The MetaMask Delegation Toolkit provides a `CaveatBuilder` interface, which offers an intuitive way to define the `caveats` array. +Use the `CaveatBuilder` to easily ensure that your delegations grant only the necessary authority. + +## Create the caveat builder + +To create the caveat builder, call the `createCaveatBuilder()` function, passing an instance of `DeleGatorEnvironment`. +The environment can be accessed from the `MetaMaskSmartAccount`, as in this example: + +```typescript +const environment = delegatorSmartAccount.environment; + +const caveatBuilder = createCaveatBuilder(environment); +``` + +:::note +By default, the `CaveatBuilder` does not allow empty caveats. To allow the `CaveatBuilder` to build an empty caveats array, provide the following configuration: + +```typescript +const caveatBuilder = createCaveatBuilder(environment, { allowEmptyCaveats: true }); +``` +::: + +## Add caveats to the builder + +Add caveats to the builder using the `addCaveat` method, specifying the [caveat type](#caveat-types) and its parameters. You can chain multiple calls to `addCaveat` as in the following example: + +```typescript +const caveats = caveatBuilder + // allowedTargets accepts an array of addresses. + // This caveat restricts the caller to only use the delegation to interact with the specified address. + .addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92"]) + // allowedMethods accepts an array of methods. + // This caveat restricts the caller to only use the delegation to invoke the specified methods. + .addCaveat("allowedMethods", [ + "approve(address,uint256)", + "transfer(address,uint256)" + ]) + // limitedCalls accepts a number. + // This caveat restricts the caller to only use the delegation one time. + .addCaveat("limitedCalls", 1) + .build(); +``` + +
+Important considerations when using caveat enforcers +

+ +- Delegations without caveats are entirely permissive. + It is crucial to add appropriate caveats to restrict the delegated authority sufficiently. + Failing to do so could result in unintended access or actions. +- Caveat enforcers safeguard the execution process but do not guarantee a final state post-redemption. + Always combine caveat enforcers thoughtfully to create comprehensive protection. +- When using multiple caveat enforcers that modify external contract states, the order matters. + For example, if you include both [`NativeBalanceGteEnforcer`](#nativebalancegte) to ensure a balance has increased and + [`NativeTokenPaymentEnforcer`](#nativetokenpayment) to deduct from that balance, + executing `NativeTokenPaymentEnforcer` first might cause `NativeBalanceGteEnforcer` to fail validation. + Consider the sequence of enforcers carefully when creating delegations with interdependent caveats. + +

+
+ +For convenience, you can also pass the `CaveatBuilder` directly to the various helper methods for creating a delegation. For example: + +```typescript +const caveats = caveatBuilder + // allowedTargets accepts an array of addresses. + .addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92"]) + // allowedMethods accepts an array of methods. + .addCaveat("allowedMethods", [ + "approve(address,uint256)", + "transfer(address,uint256)" + ]) + // limitedCalls accepts a number. + .addCaveat("limitedCalls", 1); + +const delegation = createDelegation({ + to: delegate, + from: delegator, + caveats +}); +``` + +## Caveat types + +The `CaveatBuilder` supports various caveat types, each serving a specific purpose. +These caveat types correspond to the out-of-the-box caveat enforcers +that the MetaMask Delegation Toolkit provides. + +For more granular or custom control, you can also [create custom caveat enforcers](create-custom-caveat-enforcer.md) +and add them to the caveat builder. + +### `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 + +1. Index in the calldata byte array (including the 4-byte method selector) where the expected calldata starts +2. Expected calldata as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("allowedCalldata", + 4, + encodeAbiParameters([ + { type: "string" }, + { type: "uint256" } + ], [ + "Hello Gator", + 12345n + ]) +); +``` + +:::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 + +1. An array of methods as 4-byte hex strings, ABI function signatures, or `ABIFunction` objects + +#### Example + +```typescript +caveatBuilder.addCaveat("allowedMethods", [ + "0xa9059cbb", + "transfer(address,uint256)", + { + 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 + +1. An array of addresses as hex strings + +#### Example + +```typescript +caveatBuilder.addCaveat("allowedTargets", [ + "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 + +1. The expected `args` as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("argsEqualityCheck", + "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 + +1. After threshold block number as a `bigint` +2. Before threshold block number as a `bigint` + +You can specify `0n` to indicate that there is no limitation on a threshold. + +#### Example + +```typescript +caveatBuilder.addCaveat("blocknumber", + 19426587n, + 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 + +1. A contract address as a hex string +2. The salt to use with the contract, as a hex string +3. The bytecode of the contract as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("deployed", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x0e3e8e2381fde0e8515ed47ec9caec8ba2bc12603bc2b36133fa3e3fa4d88587", + "0x..." // The deploy bytecode for the contract at 0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92 +); +``` + +### `erc1155BalanceGte` + +Ensures the ERC-1155 balance of a specified address has increased by at least a specified amount after the execution has been performed, regardless of what the execution is. + +**Caveat enforcer contract:** [`ERC1155BalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC1155BalanceGteEnforcer.sol) + +#### Parameters + +1. An ERC-1155 contract address as a hex string +2. The recipient's address as a hex string +3. The ID of the ERC-1155 token as a bigint +4. The amount by which the balance must have increased as a bigint + +#### Example + +```typescript +caveatBuilder.addCaveat("erc1155BalanceGte", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1n, + 1_000_000n +); +``` + +### `erc20BalanceGte` + +Ensures the delegator's ERC-20 balance increases by at least the specified amount after execution, regardless of the execution. + +**Caveat enforcer contract:** [`ERC20BalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC20BalanceGteEnforcer.sol) + +#### Parameters + +1. An ERC-20 contract address as a hex string +2. Amount as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20BalanceGte", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n +); +``` + +### `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 + +1. The address of the ERC-20 token contract. +2. The maximum amount of tokens that can be transferred per period, in wei. +3. The duration of each period in seconds. +4. The timestamp when the first period begins. + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20PeriodTransfer", + "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", // Address of the ERC-20 token + 1000000000000000000n, // 1 ERC-20 token - 18 decimals, in wei + 86400, // 1 day in seconds + 1743763600, // April 4th, 2025, at 00:00:00 UTC +); +``` + +### `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 + +1. An ERC-20 contract address as a hex string +2. Initial amount available at start time as a `bigint` +3. Maximum total amount that can be unlocked as a `bigint` +4. Rate at which tokens accrue per second as a `bigint` +5. Start timestamp as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20Streaming", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n, + 10_000_000n, + 100n, + 1703980800 +); +``` + +### `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 + +1. An ERC-20 contract address as a hex string +2. Amount as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20TransferAmount", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n +); +``` + +### `erc721BalanceGte` + +Ensures the ERC-721 balance of the specified recipient address increases by at least the specified amount after execution, regardless of execution type. + +**Caveat enforcer contract:** [`ERC721BalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC721BalanceGteEnforcer.sol) + +#### Parameters + +1. An ERC-721 contract address as a hex string +2. The recipient's address as a hex string +3. The amount by which the balance must have increased as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc721BalanceGte", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n +); +``` + +### `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 + +1. The permitted ERC-721 contract address as a hex string +2. The permitted ID of the ERC-721 token as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc721Transfer", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 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 + +1. A hex value for calldata. + +#### Example + +```typescript +caveatBuilder.addCaveat("exactCalldata", + "0x1234567890abcdef" // Calldata to be matched +); +``` + +### `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 + +1. Array of expected `ExecutionStruct`, each containing target address, value, and calldata. + +#### Example + +```typescript +const executions = [ + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + callData: "0x", + }, + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 0n, + callData: "0x", + }, +]; + +caveatBuilder.addCaveat("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 + +1. `ExecutionStruct` to be expected. + +#### Example + +```typescript +caveatBuilder.addCaveat("exactExecution", { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + 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 + +1. Array of expected `ExecutionStruct`, each containing target address, value, and calldata. + +#### Example + +```typescript +const executions = [ + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + callData: "0x", + }, + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 0n, + callData: "0x", + }, +]; + +caveatBuilder.addCaveat("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 + +1. An ID as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("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 + +1. A count as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("limitedCalls", + 1 +); +``` + +### `nativeBalanceGte` + +Ensures that the recipient's native token balance has increased by at least the specified amount. + +**Caveat enforcer contract:** [`NativeBalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NativeBalanceGteEnforcer.sol) + +#### Parameters + +1. The recipient's address as a hex string +2. The amount by which the balance must have increased as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeBalanceGte", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n +); +``` + +### `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 + +1. The recipient's address as a hex string +2. The amount that must be paid as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenPayment", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n +); +``` + +### `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 + +1. The maximum amount of tokens that can be transferred per period, in wei. +2. The duration of each period in seconds. +3. The timestamp when the first period begins. + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenPeriodTransfer", + 1000000000000000000n, // 1 ETH in wei + 86400, // 1 day in seconds + 1743763600, // April 4th, 2025, at 00:00:00 UTC +) +``` + +### `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 + +1. Initial amount available at start time as a `bigint` +2. Maximum total amount that can be unlocked as a `bigint` +3. Rate at which tokens accrue per second as a `bigint` +4. Start timestamp as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenStreaming", + 1_000_000n, + 10_000_000n, + 100n, + 1703980800 +); +``` + +### `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 + +1. The allowance as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenTransferAmount", + 1_000_000n +); +``` + +### `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 + +1. A nonce as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("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 + +1. The target contract address as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("ownershipTransfer", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92" +); +``` + +### `redeemer` + +Limits the addresses that can redeem the delegation. +This caveat is designed for restricting 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 + +1. An array of addresses as hex strings + +#### Example + +```typescript +caveatBuilder.addCaveat("redeemer", + [ + "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 + +1. The address of the ERC-20 token contract. +2. The address that will receive the tokens. +3. The amount of tokens to transfer, in wei. +4. The target address for the first transaction. +5. The calldata for the first transaction. + +#### Example + +```typescript +caveatBuilder.addCaveat("specificActionERC20TransferBatch", + "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da" // Address of ERC-20 token contract + "0x027aeAFF3E5C33c4018FDD302c20a1B83aDCD96C", // Address that will receive the tokens + 1000000000000000000n, // 1 ERC-20 token - 18 decimals, in wei + "0xb49830091403f1Aa990859832767B39c25a8006B", // Target address for first transaction + "0x1234567890abcdef" // Calldata to be matched for first transaction +) +``` + +### `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 + +1. After threshold timestamp as a number +2. Before threshold timestamp as a number + +You can specify `0` to indicate that there is no limitation on a threshold. + +#### Example + +```typescript +caveatBuilder.addCaveat("timestamp", + 499165200, + 1445412480 +); +``` + +### `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 + +1. A value as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("valueLte", + 1_000_000_000_000_000_000n // 1 ETH in wei +); +``` diff --git a/gator_versioned_docs/version-0.10.2/how-to/create-delegator-account.md b/gator_versioned_docs/version-0.10.2/how-to/create-delegator-account.md new file mode 100644 index 00000000000..73e132ee5a1 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/how-to/create-delegator-account.md @@ -0,0 +1,80 @@ +--- +description: Learn how to create a delegator account using Viem. +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a delegator account + +The MetaMask Delegation Toolkit is embedded, meaning that the end user can instantly interact with a dapp without wallet authorization, confirmations, or corporate logos. Enable users to create a [delegator account](../concepts/delegator-accounts.md) directly in your dapp. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) + +## Create a `MetaMaskSmartAccount` + +The following is an example of creating a delegator account using Viem Core SDK. +Viem Core SDK provides low-level interfaces to offer flexibility and control over the delegator +account creation lifecycle. + +In the example, the Viem [`privateKeyToAccount`](https://viem.sh/docs/accounts/privateKey.html) +function creates an externally owned account as the owner of the delegator account. + + + + +```typescript +import { publicClient, owner } from "./config.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const deploySalt = "0x"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner.address, [], [], []], + deploySalt, + signatory: { account: owner }, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); + +const privateKey = generatePrivateKey(); +export const owner = privateKeyToAccount(privateKey); +``` + + + + + +This example creates the `MetaMaskSmartAccount`, which can perform several functions: + +- In conjunction with [Viem Account Abstraction clients](configure.md), deploy the smart contract account, + and [send user operations](send-user-operation.md). +- [Sign delegations](create-delegation/index.md) that can be used to grant specific rights and permissions to other accounts. + +:::note +The example above uses the Hybrid Delegator smart contract account, which is configurable to have an EOA "owner" and any number of P256 (passkey) signers. +You can also [configure other delegator account types](configure-delegator-accounts-signers.md). +::: diff --git a/gator_versioned_docs/version-0.10.2/how-to/redeem-delegation.md b/gator_versioned_docs/version-0.10.2/how-to/redeem-delegation.md new file mode 100644 index 00000000000..23be468113e --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/how-to/redeem-delegation.md @@ -0,0 +1,347 @@ +--- +description: Learn how to redeem a delegation with a smart contract account (SCA) or an externally owned account (EOA). +sidebar_position: 7 +toc_max_heading_level: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Redeem a delegation + +A delegate can redeem a delegation by submitting either a user operation or a regular transaction, +depending on whether the delegate is a smart contract account (SCA) or externally owned account (EOA). + +The redeem transaction is sent to the `DelegationManager` contract, which validates the delegation and executes actions on the delegator's behalf. To prepare the call data for the redeem transaction, use the `redeemDelegation` utility function. The function supports batch redemption, allowing multiple delegations to be processed within a single transaction. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator account.](create-delegator-account.md) +- [Create a delegation.](create-delegation/index.md) + +## Redeem a delegation + +Redeem a delegation with a [smart contract account (SCA)](#redeem-with-an-sca) or an [externally owned account (EOA)](#redeem-with-an-eoa). + +### Redeem with an SCA + +The following example demonstrates how to submit a user operation to redeem a delegation. +It assumes you have a delegation signed by the delegator, and that the delegate is an SCA. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { bundlerClient, pimlicoClient } from "./client.ts"; +import { delegateSmartAccount } from "./account.ts"; + +const delegations: Delegation[] = [ signedDelegation ]; + +// SINGLE_DEFAULT_MODE is the default execution mode. +const mode: ExecutionMode = SINGLE_DEFAULT_MODE; + +// For SINGLE execution modes, the executions array must be length 1. +const executions: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const { fast: fee } = await pimlicoClient.getUserOperationGasPrice(); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: "", + data: redeemDelegationCalldata + } + ], + ...fee +}); +``` + + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +improt { publicClient } from "./client.ts" + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { createPimlicoClient } from "permissionless/clients/pimlico"; + +// You can get the API Key from the Pimlico dashboard. +const pimlicoUrl = "https://api.pimlico.io/v2/59141/rpc"; + +export const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http(pimlicoUrl), +}); + +export const pimlicoClient = createPimlicoClient({ + transport: http(pimlicoUrl), +}); +``` + + + + +### Redeem with an EOA + +The following example demonstrates how to submit a transaction to redeem a delegation. It assumes you have a delegation signed by the delegator, and that the delegate is an EOA. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { lineaSepolia as chain } from "viem/chains"; +import { delegateWalletClient } from "./account.ts"; + +const delegations: Delegation[] = [ signedDelegation ]; + +// SINGLE_DEFAULT_MODE is the default execution mode. +const mode: ExecutionMode = SINGLE_DEFAULT_MODE; + +// For SINGLE execution modes, the executions array must be length 1. +// Modify the executions to fit your use case. +const executions: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const transactionHash = await walletClient.sendTransaction({ + to: getDeleGatorEnvironment(chain.id).DelegationManager, + data: redeemDelegationCalldata, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { createWalletClient, http } from "viem"; + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateWalletClient = createWalletClient({ + account: delegateAccount, + chain, + transport: http(), +}) +``` + + + + +## Redeem multiple delegations + +You can redeem multiple delegations in a single user operation, each delegation independent of the others. +Each element in the `delegationsArray` must have a corresponding element in the `executionsArray` and `modes`. + +The following example assumes you already have multiple signed delegations and that the delegate is an SCA. +The preparation of the call data is the same when [using an EOA as the delegate](#redeem-with-an-eoa); +the primary difference is that an EOA submits a regular transaction instead of a user operation. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { bundlerClient, pimlicoClient } from "./client.ts"; +import { delegateSmartAccount } from "./account.ts"; + +const delegationsArray: Delegation[][] = [ + [ signedDelegation1 ] + [ signedDelegation2 ] + [ signedDelegation3 ] +]; + +const modes: ExecutionMode = [ + SINGLE_DEFAULT_MODE, + SINGLE_DEFAULT_MODE, + SINGLE_DEFAULT_MODE +]; + +const execution: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +// Modify the executions to fit your use case. For simplicity, we've +// included a basic example. The execution array can contain +// multiple different executions. +const executionsArray: ExecutionStruct:[][] = [ + execution, + execution, + execution +]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const { fast: fee } = await pimlicoClient.getUserOperationGasPrice(); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: "", + data: redeemDelegationCalldata + } + ], + ...fee +}); +``` + + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +import { publicClient } from "./client.ts"; + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { createPimlicoClient } from "permissionless/clients/pimlico"; + +// You can get the API Key from the Pimlico dashboard. +const pimlicoUrl = "https://api.pimlico.io/v2/59141/rpc"; + +export const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http(pimlicoUrl), +}); + +export const pimlicoClient = createPimlicoClient({ + transport: http(pimlicoUrl), +}); +``` + + + + +## Execution modes + +The Delegation Toolkit supports several execution modes based on [ERC-7579](https://erc7579.com/). +See the [ERC implementation](https://github.com/erc7579/erc7579-implementation/blob/main/src/lib/ModeLib.sol) for more details about the execution modes. + +The supported execution modes are `SINGLE_DEFAULT_MODE`, `SINGLE_TRY_MODE`, `BATCH_DEFAULT_MODE`, and `BATCH_TRY_MODE`. + +### `SINGLE` execution modes + +In `SINGLE` execution modes, only a single delegation chain and a single execution can be provided. This mode processes delegations sequentially: + +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. + +### `BATCH` execution modes + +In `BATCH` execution modes, multiple delegation chains and multiple executions can be provided. This mode executes delegations in an interleaved way: + +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. + +### `DEFAULT` modes + +In `DEFAULT` modes, if a revert occurs during redemption, the entire user operation reverts at that point. + +### `TRY` modes + +In `TRY` modes, if a revert occurs during redemption, execution of the user operation continues. diff --git a/gator_versioned_docs/version-0.10.2/how-to/send-user-operation.md b/gator_versioned_docs/version-0.10.2/how-to/send-user-operation.md new file mode 100644 index 00000000000..428d27bfe88 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/how-to/send-user-operation.md @@ -0,0 +1,176 @@ +--- +description: Learn how to send an ERC-4337 user operation using Viem. +sidebar_position: 4 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# 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 Delegation Toolkit 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 smart contract account that has not been deployed, the toolkit configures the user operation to automatically deploy the account. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator account.](create-delegator-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 +import { bundlerClient, smartAccount } from "./config.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, + maxPriortyFeePerGas +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { createBundlerClient } from "viem/account-abstraction"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +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", + signatory: { account }, +}); + +export const bundlerClient = createBundlerClient({ + publicClient, + transport: http("https://public.pimlico.io/v2/1/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). + +```typescript title="example.ts" +// add-next-line ++ import { createPimlicoClient } from "permissionless/clients/pimlico"; +import { bundlerClient, smartAccount } from "./config.ts" // The config.ts is the same as in the previous example. + +// remove-start +- const maxFeePerGas = 1n; +- const maxPriorityFeePerGas = 1n; +// remove-end + +// add-start ++ const pimlicoClient = createPimlicoClient({ ++ transport: http("https://api.pimlico.io/v2/59141/rpc"), // 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, +- maxPriortyFeePerGas + // 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" +import { createPimlicoClient } from "permissionless/clients/pimlico"; +import { bundlerClient, smartAccount } from "./config.ts" // The config.ts is the same as in the previous example. + +const pimlicoClient = createPimlicoClient({ + transport: http("https://api.pimlico.io/v2/59141/rpc"), // 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 +``` diff --git a/gator_versioned_docs/version-0.10.2/index.md b/gator_versioned_docs/version-0.10.2/index.md new file mode 100644 index 00000000000..3cdab43dc69 --- /dev/null +++ b/gator_versioned_docs/version-0.10.2/index.md @@ -0,0 +1,63 @@ +--- +title: Introduction to the MetaMask Delegation Toolkit +sidebar_label: Introduction +description: High-level overview of the Delegation Toolkit, its benefits, and where to start in the documentation. +sidebar_position: 1 +--- + +import CardList from "@site/src/components/CardList" + +# MetaMask Delegation Toolkit documentation + +## Why use the toolkit? + +The MetaMask Delegation Toolkit enables developers to create frictionless new experiences based +on granular permission sharing and trust. +The toolkit offers a suite of contracts, libraries, and services designed for maximum composability, +allowing developers to build and extend their dapps with ease. +The toolkit enables: + +- **Instant user onboarding.** Provide frictionless onboarding with no browser extension, mobile + app, or seed phrase required. + +- **New web3 experiences.** Unlock new experiences such as peer-to-peer social + coordination using incentive trees, or recurring subscription payments that don't require users + to connect to the dapp. + +- **Uninterrupted user experiences.** Keep users immersed in the dapp by embedding the wallet + experience and reassigning gas costs to where they make sense. + +The toolkit includes the [Delegation Framework](concepts/delegation.md#delegation-framework) – a +pioneering set of open-source, customizable smart contracts, allowing dapps and protocols to +implement custom permission control. +Developers can use the Delegation Framework to prepare their dapps for +[delegations](concepts/delegation.md) created from +[delegator accounts](concepts/delegator-accounts.md). + +## Where do I start? + +Check out the following sections to get started with the MetaMask Delegation Toolkit: + + + +## Questions? + +If you have questions, email hellogators@consensys.net. diff --git a/gator_versioned_docs/version-0.11.0/changelog/0.10.0.md b/gator_versioned_docs/version-0.11.0/changelog/0.10.0.md new file mode 100644 index 00000000000..29ceff089b4 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/changelog/0.10.0.md @@ -0,0 +1,114 @@ +--- +sidebar_label: 0.10.0 +sidebar_position: 4 +description: MetaMask Delegation Toolkit v0.10.0 changelog +--- + +# What's new in v0.10.0? + +:::warning Breaking changes +The ⚠️ symbol denotes potentially breaking API changes. +As per the [semantic versioning specification](https://semver.org/#spec-item-4), from v1.0.0 onwards, +breaking changes will be released only in major version bumps. +::: + +## Enhancements + +### ⚠️ ERC-7715 permission request +- Removed `required` parameter. +- Added the `isAdjustmentAllowed` parameter, allowing user to modify the permission request as needed. + +```typescript +const permissions = { + chainId: '0x7a69', + address: bob.address, + expiry: 1234567890, + permission: { + type: 'native-token-stream', + data: { + amountPerSecond: '0x1', + maxAmount: '0x2', + initialAmount: undefined, + startTime: 2, + justification: 'Test justification', + }, + }, + // remove-next-line +- required: false + // add-next-line ++ isAdjustmentAllowed: true, + signer: { + type: 'account', + data: { + address: alice.address, + }, + }, +} +``` + +## Contract addresses + +The following are the contract addresses for the +[Delegation Framework version 1.3.0](https://github.com/MetaMask/delegation-framework/blob/v1.3.0/documents/Deployments.md), +as used by this version of the toolkit. + +### Delegation Framework + +| Contract | Address | +|----------|---------| +| EntryPoint | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | +| SimpleFactory | `0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c` | +| DelegationManager | `0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3` | +| MultiSigDeleGatorImpl | `0x56a9EdB16a0105eb5a4C54f4C062e2868844f3A7` | +| HybridDeleGatorImpl | `0x48dBe696A4D990079e039489bA2053B36E8FFEC4` | + +### Caveat enforcers + +| Enforcer | Address | +|----------|---------| +| AllowedCalldataEnforcer | `0xc2b0d624c1c4319760C96503BA27C347F3260f55` | +| AllowedMethodsEnforcer | `0x2c21fD0Cb9DC8445CB3fb0DC5E7Bb0Aca01842B5` | +| AllowedTargetsEnforcer | `0x7F20f61b1f09b08D970938F6fa563634d65c4EeB` | +| ArgsEqualityCheckEnforcer | `0x44B8C6ae3C304213c3e298495e12497Ed3E56E41` | +| BlockNumberEnforcer | `0x5d9818dF0AE3f66e9c3D0c5029DAF99d1823ca6c` | +| DeployedEnforcer | `0x24ff2AA430D53a8CD6788018E902E098083dcCd2` | +| ERC20BalanceGteEnforcer | `0x433A6A4d9875D87510584fd6cc586eB1c5F8A1d2` | +| ERC20TransferAmountEnforcer | `0xf100b0819427117EcF76Ed94B358B1A5b5C6D2Fc` | +| ERC20PeriodTransferEnforcer| `0x474e3Ae7E169e940607cC624Da8A15Eb120139aB` | +| ERC20StreamingEnforcer | `0x56c97aE02f233B29fa03502Ecc0457266d9be00e` | +| ERC721BalanceGteEnforcer | `0xA5d03eb350FA89f854685f6313CeCA27A4212542` | +| ERC721TransferEnforcer | `0x3790e6B7233f779b09DA74C72b6e94813925b9aF` | +| ERC1155BalanceGteEnforcer | `0x831b76f53601f38BfaCa2e6b442D6A5408Ae375c` | +| ExactCalldataBatchEnforcer | `0x982FD5C86BBF425d7d1451f974192d4525113DfD` | +| ExactCalldataEnforcer | `0x99F2e9bF15ce5eC84685604836F71aB835DBBdED` | +| ExactExecutionBatchEnforcer | `0x1e141e455d08721Dd5BCDA1BaA6Ea5633Afd5017` | +| ExactExecutionEnforcer | `0x146713078D39eCC1F5338309c28405ccf85Abfbb` | +| IdEnforcer | `0xC8B5D93463c893401094cc70e66A206fb5987997` | +| LimitedCallsEnforcer | `0x04658B29F6b82ed55274221a06Fc97D318E25416` | +| NonceEnforcer | `0xDE4f2FAC4B3D87A1d9953Ca5FC09FCa7F366254f` | +| TimestampEnforcer | `0x1046bb45C8d673d4ea75321280DB34899413c069` | +| ValueLteEnforcer | `0x92Bf12322527cAA612fd31a0e810472BBB106A8F` | +| NativeBalanceGteEnforcer | `0x54e17146b9CCE2642881E0879e06e9D63F7d7606` | +| NativeTokenPaymentEnforcer | `0x4803a326ddED6dDBc60e659e5ed12d85c7582811` | +| NativeTokenTransferAmountEnforcer | `0xF71af580b9c3078fbc2BBF16FbB8EEd82b330320` | +| NativeTokenStreamingEnforcer | `0xD10b97905a320b13a0608f7E9cC506b56747df19` | +| NativeTokenPeriodTransferEnforcer | `0x9BC0FAf4Aca5AE429F4c06aEEaC517520CB16BD9` | +| OwnershipTransferEnforcer | `0x7EEf9734E7092032B5C56310Eb9BbD1f4A524681` | +| RedeemerEnforcer | `0xE144b0b2618071B4E56f746313528a669c7E65c5` | +| SpecificActionERC20TransferBatchEnforcer | `0x00e0251aaA263dfE3B3541B758A82D1CBA1c3B6D` | + +### Supported mainnet networks +- Ethereum +- Polygon +- Binance Smart Chain +- Optimism +- Arbitrum +- Linea +- Base +- Gnosis Chain + +### Supported testnet networks +- Ethereum Sepolia +- Linea Sepolia +- Base Sepolia +- MegaEth \ No newline at end of file diff --git a/gator_versioned_docs/version-0.11.0/changelog/0.10.1.md b/gator_versioned_docs/version-0.11.0/changelog/0.10.1.md new file mode 100644 index 00000000000..68b77054258 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/changelog/0.10.1.md @@ -0,0 +1,78 @@ +--- +sidebar_label: 0.10.1 +sidebar_position: 3 +description: MetaMask Delegation Toolkit v0.10.1 changelog +--- + +# What's new in v0.10.1? + +## Bug fixes + +- Fixed an issue where the ERC-7715 `PermissionRequest` object could not be serialized when `maxAmount` or `initialAmount` was set to `null`. + +## Contract addresses + +The following are the contract addresses for the +[Delegation Framework version 1.3.0](https://github.com/MetaMask/delegation-framework/blob/v1.3.0/documents/Deployments.md), +as used by this version of the toolkit. + +### Delegation Framework + +| Contract | Address | +|----------|---------| +| EntryPoint | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | +| SimpleFactory | `0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c` | +| DelegationManager | `0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3` | +| MultiSigDeleGatorImpl | `0x56a9EdB16a0105eb5a4C54f4C062e2868844f3A7` | +| HybridDeleGatorImpl | `0x48dBe696A4D990079e039489bA2053B36E8FFEC4` | + +### Caveat enforcers + +| Enforcer | Address | +|----------|---------| +| AllowedCalldataEnforcer | `0xc2b0d624c1c4319760C96503BA27C347F3260f55` | +| AllowedMethodsEnforcer | `0x2c21fD0Cb9DC8445CB3fb0DC5E7Bb0Aca01842B5` | +| AllowedTargetsEnforcer | `0x7F20f61b1f09b08D970938F6fa563634d65c4EeB` | +| ArgsEqualityCheckEnforcer | `0x44B8C6ae3C304213c3e298495e12497Ed3E56E41` | +| BlockNumberEnforcer | `0x5d9818dF0AE3f66e9c3D0c5029DAF99d1823ca6c` | +| DeployedEnforcer | `0x24ff2AA430D53a8CD6788018E902E098083dcCd2` | +| ERC20BalanceGteEnforcer | `0x433A6A4d9875D87510584fd6cc586eB1c5F8A1d2` | +| ERC20TransferAmountEnforcer | `0xf100b0819427117EcF76Ed94B358B1A5b5C6D2Fc` | +| ERC20PeriodTransferEnforcer| `0x474e3Ae7E169e940607cC624Da8A15Eb120139aB` | +| ERC20StreamingEnforcer | `0x56c97aE02f233B29fa03502Ecc0457266d9be00e` | +| ERC721BalanceGteEnforcer | `0xA5d03eb350FA89f854685f6313CeCA27A4212542` | +| ERC721TransferEnforcer | `0x3790e6B7233f779b09DA74C72b6e94813925b9aF` | +| ERC1155BalanceGteEnforcer | `0x831b76f53601f38BfaCa2e6b442D6A5408Ae375c` | +| ExactCalldataBatchEnforcer | `0x982FD5C86BBF425d7d1451f974192d4525113DfD` | +| ExactCalldataEnforcer | `0x99F2e9bF15ce5eC84685604836F71aB835DBBdED` | +| ExactExecutionBatchEnforcer | `0x1e141e455d08721Dd5BCDA1BaA6Ea5633Afd5017` | +| ExactExecutionEnforcer | `0x146713078D39eCC1F5338309c28405ccf85Abfbb` | +| IdEnforcer | `0xC8B5D93463c893401094cc70e66A206fb5987997` | +| LimitedCallsEnforcer | `0x04658B29F6b82ed55274221a06Fc97D318E25416` | +| NonceEnforcer | `0xDE4f2FAC4B3D87A1d9953Ca5FC09FCa7F366254f` | +| TimestampEnforcer | `0x1046bb45C8d673d4ea75321280DB34899413c069` | +| ValueLteEnforcer | `0x92Bf12322527cAA612fd31a0e810472BBB106A8F` | +| NativeBalanceGteEnforcer | `0x54e17146b9CCE2642881E0879e06e9D63F7d7606` | +| NativeTokenPaymentEnforcer | `0x4803a326ddED6dDBc60e659e5ed12d85c7582811` | +| NativeTokenTransferAmountEnforcer | `0xF71af580b9c3078fbc2BBF16FbB8EEd82b330320` | +| NativeTokenStreamingEnforcer | `0xD10b97905a320b13a0608f7E9cC506b56747df19` | +| NativeTokenPeriodTransferEnforcer | `0x9BC0FAf4Aca5AE429F4c06aEEaC517520CB16BD9` | +| OwnershipTransferEnforcer | `0x7EEf9734E7092032B5C56310Eb9BbD1f4A524681` | +| RedeemerEnforcer | `0xE144b0b2618071B4E56f746313528a669c7E65c5` | +| SpecificActionERC20TransferBatchEnforcer | `0x00e0251aaA263dfE3B3541B758A82D1CBA1c3B6D` | + +### Supported mainnet networks +- Ethereum +- Polygon +- Binance Smart Chain +- Optimism +- Arbitrum +- Linea +- Base +- Gnosis Chain + +### Supported testnet networks +- Ethereum Sepolia +- Linea Sepolia +- Base Sepolia +- MegaEth \ No newline at end of file diff --git a/gator_versioned_docs/version-0.11.0/changelog/0.10.2.md b/gator_versioned_docs/version-0.11.0/changelog/0.10.2.md new file mode 100644 index 00000000000..2bb399e7025 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/changelog/0.10.2.md @@ -0,0 +1,79 @@ +--- +sidebar_label: 0.10.2 +sidebar_position: 2 +description: MetaMask Delegation Toolkit v0.10.2 changelog +--- + +# What's new in v0.10.2? + +## Bug fixes + +- Fixed the ERC-7715 request permission issue on Firefox. +- Dropped ESM exports to enforce CommonJS module resolution. + +## Contract addresses + +The following are the contract addresses for the +[Delegation Framework version 1.3.0](https://github.com/MetaMask/delegation-framework/blob/v1.3.0/documents/Deployments.md), +as used by this version of the toolkit. + +### Delegation Framework + +| Contract | Address | +|----------|---------| +| EntryPoint | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | +| SimpleFactory | `0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c` | +| DelegationManager | `0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3` | +| MultiSigDeleGatorImpl | `0x56a9EdB16a0105eb5a4C54f4C062e2868844f3A7` | +| HybridDeleGatorImpl | `0x48dBe696A4D990079e039489bA2053B36E8FFEC4` | + +### Caveat enforcers + +| Enforcer | Address | +|----------|---------| +| AllowedCalldataEnforcer | `0xc2b0d624c1c4319760C96503BA27C347F3260f55` | +| AllowedMethodsEnforcer | `0x2c21fD0Cb9DC8445CB3fb0DC5E7Bb0Aca01842B5` | +| AllowedTargetsEnforcer | `0x7F20f61b1f09b08D970938F6fa563634d65c4EeB` | +| ArgsEqualityCheckEnforcer | `0x44B8C6ae3C304213c3e298495e12497Ed3E56E41` | +| BlockNumberEnforcer | `0x5d9818dF0AE3f66e9c3D0c5029DAF99d1823ca6c` | +| DeployedEnforcer | `0x24ff2AA430D53a8CD6788018E902E098083dcCd2` | +| ERC20BalanceGteEnforcer | `0x433A6A4d9875D87510584fd6cc586eB1c5F8A1d2` | +| ERC20TransferAmountEnforcer | `0xf100b0819427117EcF76Ed94B358B1A5b5C6D2Fc` | +| ERC20PeriodTransferEnforcer| `0x474e3Ae7E169e940607cC624Da8A15Eb120139aB` | +| ERC20StreamingEnforcer | `0x56c97aE02f233B29fa03502Ecc0457266d9be00e` | +| ERC721BalanceGteEnforcer | `0xA5d03eb350FA89f854685f6313CeCA27A4212542` | +| ERC721TransferEnforcer | `0x3790e6B7233f779b09DA74C72b6e94813925b9aF` | +| ERC1155BalanceGteEnforcer | `0x831b76f53601f38BfaCa2e6b442D6A5408Ae375c` | +| ExactCalldataBatchEnforcer | `0x982FD5C86BBF425d7d1451f974192d4525113DfD` | +| ExactCalldataEnforcer | `0x99F2e9bF15ce5eC84685604836F71aB835DBBdED` | +| ExactExecutionBatchEnforcer | `0x1e141e455d08721Dd5BCDA1BaA6Ea5633Afd5017` | +| ExactExecutionEnforcer | `0x146713078D39eCC1F5338309c28405ccf85Abfbb` | +| IdEnforcer | `0xC8B5D93463c893401094cc70e66A206fb5987997` | +| LimitedCallsEnforcer | `0x04658B29F6b82ed55274221a06Fc97D318E25416` | +| NonceEnforcer | `0xDE4f2FAC4B3D87A1d9953Ca5FC09FCa7F366254f` | +| TimestampEnforcer | `0x1046bb45C8d673d4ea75321280DB34899413c069` | +| ValueLteEnforcer | `0x92Bf12322527cAA612fd31a0e810472BBB106A8F` | +| NativeBalanceGteEnforcer | `0x54e17146b9CCE2642881E0879e06e9D63F7d7606` | +| NativeTokenPaymentEnforcer | `0x4803a326ddED6dDBc60e659e5ed12d85c7582811` | +| NativeTokenTransferAmountEnforcer | `0xF71af580b9c3078fbc2BBF16FbB8EEd82b330320` | +| NativeTokenStreamingEnforcer | `0xD10b97905a320b13a0608f7E9cC506b56747df19` | +| NativeTokenPeriodTransferEnforcer | `0x9BC0FAf4Aca5AE429F4c06aEEaC517520CB16BD9` | +| OwnershipTransferEnforcer | `0x7EEf9734E7092032B5C56310Eb9BbD1f4A524681` | +| RedeemerEnforcer | `0xE144b0b2618071B4E56f746313528a669c7E65c5` | +| SpecificActionERC20TransferBatchEnforcer | `0x00e0251aaA263dfE3B3541B758A82D1CBA1c3B6D` | + +### Supported mainnet networks +- Ethereum +- Polygon +- Binance Smart Chain +- Optimism +- Arbitrum +- Linea +- Base +- Gnosis Chain + +### Supported testnet networks +- Ethereum Sepolia +- Linea Sepolia +- Base Sepolia +- MegaEth \ No newline at end of file diff --git a/gator_versioned_docs/version-0.11.0/changelog/0.11.0.md b/gator_versioned_docs/version-0.11.0/changelog/0.11.0.md new file mode 100644 index 00000000000..3064c9e1dc3 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/changelog/0.11.0.md @@ -0,0 +1,186 @@ +--- +sidebar_label: 0.11.0 +sidebar_position: 1 +description: MetaMask Delegation Toolkit v0.11.0 changelog +--- + +# What's new in v0.11.0? + +:::warning Breaking changes +The ⚠️ symbol denotes potentially breaking API changes. +As per the [semantic versioning specification](https://semver.org/#spec-item-4), from v1.0.0 onwards, +breaking changes will be released only in major version bumps. +::: + + +## Breaking Changes + +### ⚠️ `nativeBalanceGte` caveat + +- Renames `nativeBalanceGte` caveat to `nativeBalanceChange`. +- Previously, the caveat only considered an increase in the native token +balance. It now accepts a new parameter, `BalanceChangeType`, which defines the +expected type of balance change. This parameter allows specifying whether the +native token balance should have increased or decreased, using the valid +options: `BalanceChangeType.Increase` and `BalanceChangeType.Decrease`. + +```typescript +// remove-next-line +caveatBuilder.addCaveat("nativeBalanceGte", +// add-next-line +caveatBuilder.addCaveat("nativeBalanceChange", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n, + // add-next-line + BalanceChangeType.Increase, +); +``` + +### ⚠️ `erc20BalanceGte` caveat + +- Renames `erc20BalanceGte` caveat to `erc20BalanceChange`. +- Previously, the caveat only considered an increase in the ERC-20 token +balance. It now accepts a new parameter, `BalanceChangeType`, which defines the +expected type of balance change. This parameter allows specifying whether the +ERC-20 token balance should have increased or decreased, using the valid +options: `BalanceChangeType.Increase` and `BalanceChangeType.Decrease`. +- Introduces a new parameter to specify the recipient address associated with the ERC-20 balance change. + +```typescript +// remove-next-line +caveatBuilder.addCaveat("erc20BalanceGte", +// add-next-line +caveatBuilder.addCaveat("erc20BalanceChange", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + // add-start + // Specifies the address associated with the ERC-20 balance change + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + // add-end + 1_000_000n, + // add-next-line + BalanceChangeType.Increase, +); +``` + +### ⚠️ `erc721BalanceGte` caveat + +- Renames `erc721BalanceGte` caveat to `erc721BalanceChange`. +- Previously, the caveat only considered an increase in the ERC-721 token +balance. It now accepts a new parameter, `BalanceChangeType`, which defines the +expected type of balance change. This parameter allows specifying whether the +ERC-721 token balance should have increased or decreased, using the valid +options: `BalanceChangeType.Increase` and `BalanceChangeType.Decrease`. + +```typescript +// remove-next-line +caveatBuilder.addCaveat("erc721BalanceGte", +// add-next-line +caveatBuilder.addCaveat("erc721BalanceChange", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n, + // add-next-line + BalanceChangeType.Increase, +); +``` + +### ⚠️ `erc1155BalanceGte` caveat + +- Renames `erc1155BalanceGte` caveat to `erc1155BalanceChange`. +- Previously, the caveat only considered an increase in the ERC-1155 token +balance. It now accepts a new parameter, `BalanceChangeType`, which defines the +expected type of balance change. This parameter allows specifying whether the +ERC-1155 token balance should have increased or decreased, using the valid +options: `BalanceChangeType.Increase` and `BalanceChangeType.Decrease`. + +```typescript +// remove-next-line +caveatBuilder.addCaveat("erc1155BalanceGte", +// add-next-line +caveatBuilder.addCaveat("erc1155BalanceChange", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1n, + 1_000_000n, + // add-next-line + BalanceChangeType.Increase, +); +``` + +## Enhancements + +- Delegation Framework now supports the Gnosis Chiado testnet 🎉. +- `isValidSignature` function has been added to verify signatures from smart contract accounts. +- `aggregateSignature` function is now exposed in the toolkit, allowing +multiple required signatures to be combined into a single aggregated signature. +[Learn how to generate a multisig signature](../how-to/generate-multisig-signature) +- A new caveat, `multiTokenPeriod`, has been introduced to ensure that transfers involving multiple tokens remain within defined limits over specified time periods. See [`multiTokenPeriod`](../how-to/create-delegation/restrict-delegation#multitokenperiod) to learn more. + +## Contract addresses + +The following are the contract addresses for the +[Delegation Framework version 1.3.0](https://github.com/MetaMask/delegation-framework/blob/v1.3.0/documents/Deployments.md), +as used by this version of the toolkit. + +### Delegation Framework + +| Contract | Address | +|----------|---------| +| EntryPoint | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | +| SimpleFactory | `0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c` | +| DelegationManager | `0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3` | +| MultiSigDeleGatorImpl | `0x56a9EdB16a0105eb5a4C54f4C062e2868844f3A7` | +| HybridDeleGatorImpl | `0x48dBe696A4D990079e039489bA2053B36E8FFEC4` | + +### Caveat enforcers + +| Enforcer | Address | +|----------|---------| +| AllowedCalldataEnforcer | `0xc2b0d624c1c4319760C96503BA27C347F3260f55` | +| AllowedMethodsEnforcer | `0x2c21fD0Cb9DC8445CB3fb0DC5E7Bb0Aca01842B5` | +| AllowedTargetsEnforcer | `0x7F20f61b1f09b08D970938F6fa563634d65c4EeB` | +| ArgsEqualityCheckEnforcer | `0x44B8C6ae3C304213c3e298495e12497Ed3E56E41` | +| BlockNumberEnforcer | `0x5d9818dF0AE3f66e9c3D0c5029DAF99d1823ca6c` | +| DeployedEnforcer | `0x24ff2AA430D53a8CD6788018E902E098083dcCd2` | +| ERC20BalanceChangeEnforcer | `0xcdF6aB796408598Cea671d79506d7D48E97a5437` | +| ERC20TransferAmountEnforcer | `0xf100b0819427117EcF76Ed94B358B1A5b5C6D2Fc` | +| ERC20PeriodTransferEnforcer| `0x474e3Ae7E169e940607cC624Da8A15Eb120139aB` | +| ERC20StreamingEnforcer | `0x56c97aE02f233B29fa03502Ecc0457266d9be00e` | +| ERC721BalanceChangeEnforcer | `0x8aFdf96eDBbe7e1eD3f5Cd89C7E084841e12A09e` | +| ERC721TransferEnforcer | `0x3790e6B7233f779b09DA74C72b6e94813925b9aF` | +| ERC1155BalanceChangeEnforcer | `0x63c322732695cAFbbD488Fc6937A0A7B66fC001A` | +| ExactCalldataBatchEnforcer | `0x982FD5C86BBF425d7d1451f974192d4525113DfD` | +| ExactCalldataEnforcer | `0x99F2e9bF15ce5eC84685604836F71aB835DBBdED` | +| ExactExecutionBatchEnforcer | `0x1e141e455d08721Dd5BCDA1BaA6Ea5633Afd5017` | +| ExactExecutionEnforcer | `0x146713078D39eCC1F5338309c28405ccf85Abfbb` | +| IdEnforcer | `0xC8B5D93463c893401094cc70e66A206fb5987997` | +| LimitedCallsEnforcer | `0x04658B29F6b82ed55274221a06Fc97D318E25416` | +| MultiTokenPeriodEnforcer | `0xFB2f1a9BD76d3701B730E5d69C3219D42D80eBb7` | +| NonceEnforcer | `0xDE4f2FAC4B3D87A1d9953Ca5FC09FCa7F366254f` | +| NativeBalanceChangeEnforcer | `0xbD7B277507723490Cd50b12EaaFe87C616be6880` | +| NativeTokenPaymentEnforcer | `0x4803a326ddED6dDBc60e659e5ed12d85c7582811` | +| NativeTokenTransferAmountEnforcer | `0xF71af580b9c3078fbc2BBF16FbB8EEd82b330320` | +| NativeTokenStreamingEnforcer | `0xD10b97905a320b13a0608f7E9cC506b56747df19` | +| NativeTokenPeriodTransferEnforcer | `0x9BC0FAf4Aca5AE429F4c06aEEaC517520CB16BD9` | +| OwnershipTransferEnforcer | `0x7EEf9734E7092032B5C56310Eb9BbD1f4A524681` | +| RedeemerEnforcer | `0xE144b0b2618071B4E56f746313528a669c7E65c5` | +| SpecificActionERC20TransferBatchEnforcer | `0x00e0251aaA263dfE3B3541B758A82D1CBA1c3B6D` | +| TimestampEnforcer | `0x1046bb45C8d673d4ea75321280DB34899413c069` | +| ValueLteEnforcer | `0x92Bf12322527cAA612fd31a0e810472BBB106A8F` | + +### Supported mainnet networks +- Ethereum +- Polygon +- Binance Smart Chain +- Optimism +- Arbitrum +- Linea +- Base +- Gnosis Chain + +### Supported testnet networks +- Ethereum Sepolia +- Linea Sepolia +- Base Sepolia +- MegaEth +- Gnosis Chiado \ No newline at end of file diff --git a/gator_versioned_docs/version-0.11.0/changelog/0.9.0.md b/gator_versioned_docs/version-0.11.0/changelog/0.9.0.md new file mode 100644 index 00000000000..8345b0d9a6e --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/changelog/0.9.0.md @@ -0,0 +1,117 @@ +--- +sidebar_label: 0.9.0 +sidebar_position: 5 +description: MetaMask Delegation Toolkit v0.9.0 changelog +--- + +# What's new in v0.9.0? + +:::warning Breaking changes +The ⚠️ symbol denotes potentially breaking API changes. +As per the [semantic versioning specification](https://semver.org/#spec-item-4), from v1.0.0 onwards, +breaking changes will be released only in major version bumps. +::: + +## Breaking Changes + +### ⚠️ Package references + +- Moved the package from `@metamask-private` to `@metamask` organization. +- Renames the `delegator-core-viem` entrypoint to `delegation-toolkit`. + +```typescript +// remove-next-line +- import { toMetaMaskSmartAccount } from "@metamask-private/delegator-core-viem"; +// add-next-line ++ import { toMetaMaskSmartAccount } from "@metamask/delegation-toolkit"; +``` + +### ⚠️ SimpleFactory address + +Updates the `SimpleFactory` contract address. Please note, this will result in a change to the smart account address for `MetaMaskSmartAccount`. + +```typescript +// Delegation enviroment for 1.3.0 +export const deployment_1_3_0 = { +//... +// remove-next-line +- SimpleFactory: '0x6ff518884f21168c30c58CB21184D6AdBC18Ad90', +// add-next-line ++ SimpleFactory: '0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c', +//... +} + +``` + +## Enhancements + +### ⚠️ Package installation + +Package installation no longer requires an authentication token, as it has transitioned out of private alpha. 🎉 + +## Contract addresses + +The following are the contract addresses for the +[Delegation Framework version 1.3.0](https://github.com/MetaMask/delegation-framework/blob/v1.3.0/documents/Deployments.md), +as used by this version of the toolkit. + +### Delegation Framework + +| Contract | Address | +|----------|---------| +| EntryPoint | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | +| SimpleFactory | `0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c` | +| DelegationManager | `0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3` | +| MultiSigDeleGatorImpl | `0x56a9EdB16a0105eb5a4C54f4C062e2868844f3A7` | +| HybridDeleGatorImpl | `0x48dBe696A4D990079e039489bA2053B36E8FFEC4` | + +### Caveat enforcers + +| Enforcer | Address | +|----------|---------| +| AllowedCalldataEnforcer | `0xc2b0d624c1c4319760C96503BA27C347F3260f55` | +| AllowedMethodsEnforcer | `0x2c21fD0Cb9DC8445CB3fb0DC5E7Bb0Aca01842B5` | +| AllowedTargetsEnforcer | `0x7F20f61b1f09b08D970938F6fa563634d65c4EeB` | +| ArgsEqualityCheckEnforcer | `0x44B8C6ae3C304213c3e298495e12497Ed3E56E41` | +| BlockNumberEnforcer | `0x5d9818dF0AE3f66e9c3D0c5029DAF99d1823ca6c` | +| DeployedEnforcer | `0x24ff2AA430D53a8CD6788018E902E098083dcCd2` | +| ERC20BalanceGteEnforcer | `0x433A6A4d9875D87510584fd6cc586eB1c5F8A1d2` | +| ERC20TransferAmountEnforcer | `0xf100b0819427117EcF76Ed94B358B1A5b5C6D2Fc` | +| ERC20PeriodTransferEnforcer| `0x474e3Ae7E169e940607cC624Da8A15Eb120139aB` | +| ERC20StreamingEnforcer | `0x56c97aE02f233B29fa03502Ecc0457266d9be00e` | +| ERC721BalanceGteEnforcer | `0xA5d03eb350FA89f854685f6313CeCA27A4212542` | +| ERC721TransferEnforcer | `0x3790e6B7233f779b09DA74C72b6e94813925b9aF` | +| ERC1155BalanceGteEnforcer | `0x831b76f53601f38BfaCa2e6b442D6A5408Ae375c` | +| ExactCalldataBatchEnforcer | `0x982FD5C86BBF425d7d1451f974192d4525113DfD` | +| ExactCalldataEnforcer | `0x99F2e9bF15ce5eC84685604836F71aB835DBBdED` | +| ExactExecutionBatchEnforcer | `0x1e141e455d08721Dd5BCDA1BaA6Ea5633Afd5017` | +| ExactExecutionEnforcer | `0x146713078D39eCC1F5338309c28405ccf85Abfbb` | +| IdEnforcer | `0xC8B5D93463c893401094cc70e66A206fb5987997` | +| LimitedCallsEnforcer | `0x04658B29F6b82ed55274221a06Fc97D318E25416` | +| NonceEnforcer | `0xDE4f2FAC4B3D87A1d9953Ca5FC09FCa7F366254f` | +| TimestampEnforcer | `0x1046bb45C8d673d4ea75321280DB34899413c069` | +| ValueLteEnforcer | `0x92Bf12322527cAA612fd31a0e810472BBB106A8F` | +| NativeBalanceGteEnforcer | `0x54e17146b9CCE2642881E0879e06e9D63F7d7606` | +| NativeTokenPaymentEnforcer | `0x4803a326ddED6dDBc60e659e5ed12d85c7582811` | +| NativeTokenTransferAmountEnforcer | `0xF71af580b9c3078fbc2BBF16FbB8EEd82b330320` | +| NativeTokenStreamingEnforcer | `0xD10b97905a320b13a0608f7E9cC506b56747df19` | +| NativeTokenPeriodTransferEnforcer | `0x9BC0FAf4Aca5AE429F4c06aEEaC517520CB16BD9` | +| OwnershipTransferEnforcer | `0x7EEf9734E7092032B5C56310Eb9BbD1f4A524681` | +| RedeemerEnforcer | `0xE144b0b2618071B4E56f746313528a669c7E65c5` | +| SpecificActionERC20TransferBatchEnforcer | `0x00e0251aaA263dfE3B3541B758A82D1CBA1c3B6D` | + +### Supported mainnet networks +- Ethereum +- Polygon +- Binance Smart Chain +- Optimism +- Arbitrum +- Linea +- Base +- Gnosis Chain + +### Supported testnet networks +- Ethereum Sepolia +- Linea Sepolia +- Base Sepolia +- MegaEth \ No newline at end of file diff --git a/gator_versioned_docs/version-0.11.0/changelog/_category_.json b/gator_versioned_docs/version-0.11.0/changelog/_category_.json new file mode 100644 index 00000000000..f33608bfdf1 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/changelog/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Changelog" +} diff --git a/gator_versioned_docs/version-0.11.0/concepts/_category_.json b/gator_versioned_docs/version-0.11.0/concepts/_category_.json new file mode 100644 index 00000000000..122abce7702 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/concepts/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Concepts", + "position": 3, + "link": { + "type": "generated-index", + "slug": "/concepts", + "title": "Delegation Toolkit concepts" + } +} diff --git a/gator_versioned_docs/version-0.11.0/concepts/caveat-enforcers.md b/gator_versioned_docs/version-0.11.0/concepts/caveat-enforcers.md new file mode 100644 index 00000000000..6055b796cbc --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/concepts/caveat-enforcers.md @@ -0,0 +1,185 @@ +--- +description: Learn about caveat enforcers and how they restrict delegations. +sidebar_position: 4 +--- + +# Caveat enforcers + +The MetaMask Delegation Toolkit 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](delegation.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; + +import { ModeCode } from "../utils/Types.sol"; + +/** + * 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`](../how-to/create-delegation/restrict-delegation.md) interface in the MetaMask Delegation Toolkit. + +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](../how-to/create-delegation/restrict-delegation.md#caveat-types) 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`](../how-to/create-delegation/restrict-delegation.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`](../how-to/create-delegation/restrict-delegation.md#nativetokenpayment) before + [`NativeBalanceChangeEnforcer`](../how-to/create-delegation/restrict-delegation.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 Delegation Toolkit provides [many out-of-the-box caveat enforcers](../how-to/create-delegation/restrict-delegation.md#caveat-types) +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 more complex scenarios, you can also [create custom caveat enforcers](../how-to/create-delegation/create-custom-caveat-enforcer.md) by implementing the `ICaveatEnforcer` interface. + +## Attenuating authority with redelegations + +When [creating chains of delegations](../how-to/create-delegation/index.md#create-a-redelegation), 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. diff --git a/gator_versioned_docs/version-0.11.0/concepts/delegation.md b/gator_versioned_docs/version-0.11.0/concepts/delegation.md new file mode 100644 index 00000000000..9db9beb87d2 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/concepts/delegation.md @@ -0,0 +1,138 @@ +--- +description: Learn about delegation, the delegation lifecycle, and the Delegation Framework. +sidebar_position: 2 +--- + +# Delegation + +*Delegation* is the ability for a [delegator account](delegator-accounts.md) to grant permission to another smart contract account (SCA) +or externally owned account (EOA) to perform specific executions on the delegator's behalf, under defined rules and restrictions. + +The MetaMask Delegation Toolkit includes the following delegation features: + +- **Caveats** - 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. + +- **Chain of delegations** - Users can redelegate permissions that have been delegated to them, creating a chain of delegations across trusted parties. + +Delegations are created using the `Delegation` type, which is specified as follows: + +```typescript +export type Delegation = { + delegate: Hex; // The address to which the delegation is being granted. + delegator: Hex; // The address that is granting the delegation. + authority: Hex; // Hash of the parent delegation, or the constant ROOT_AUTHORITY. + caveats: Caveat[]; // Caveats that restrict the authority being granted. + salt: Hex; // Used to avoid hash collisions between identical delegations. + signature: Hex; // Signature from the delegator account. +}; +``` + +## Delegation lifecycle + +The delegation lifecycle is as follows: + +1. **Delegation creation** - A delegation is initialized, and the delegator account signs it. + +2. **Caveat enforcement** - The caveats applied to the delegation specify conditions under which + the delegation can be redeemed. + +3. **Delegation storage** - The delegation can be stored, enabling retrieval for future redemption. + + :::note + [Storing and retrieving delegations](../experimental/store-retrieve-delegations.md) using the toolkit's + `DelegationStorageClient` is an experimental feature. + ::: + +4. **Delegation redemption** - The delegate (the account being granted the permission) redeems the + delegation through an [ERC-4337 user operation](delegator-accounts.md#account-abstraction-erc-4337), + which verifies that the delegated authority is valid in order to perform the execution. + +See [how to create a delegation](../how-to/create-delegation/index.md) to get started with the +delegation lifecycle. + +## Delegation Framework + +The MetaMask Delegation Toolkit 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** - There are [multiple delegator account implementations](delegator-accounts.md#delegator-account-types), + with the main difference being the signature scheme 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 a delegation is redeemed, 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 `permissionContexts`, `modes`, and + `executionCallDatas` match, or throws `BatchDataLengthMismatch`. + 2. Decodes and validates the delegation, checking that the caller (`msg.sender`) is the delegate + and that there are no empty signatures, or throws `InvalidDelegate`. + 3. Verifies delegation signatures, ensuring validity using `ECDSA` (for EOAs) or + `isValidSignature` (for contracts), or throws `InvalidSignature`. + 4. Validates the delegation chain's authority and ensures delegations are not disabled. + 5. Executes the `beforeHook` for each `caveat` 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 redemption flow + +This diagram illustrates how a delegation is created and subsequently redeemed on 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': 25, + 'width': 200 + } + } +}%% + +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 +``` diff --git a/gator_versioned_docs/version-0.11.0/concepts/delegator-accounts.md b/gator_versioned_docs/version-0.11.0/concepts/delegator-accounts.md new file mode 100644 index 00000000000..60448c2b7d6 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/concepts/delegator-accounts.md @@ -0,0 +1,79 @@ +--- +description: Learn about account abstraction, the delegator account flow, and account types. +sidebar_position: 1 +--- + +# Delegator accounts + +The MetaMask Delegation Toolkit enables you to create and manage *delegator accounts*. +Delegator accounts are [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) smart contract accounts (SCAs) +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 delegator +accounts use smart contracts to govern account logic. + +## 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 SCAs containing arbitrary verification logic. +ERC-4337 enables SCAs 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 SCA 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 SCAs to facilitate gas abstraction. + +## Delegator account flow + +The MetaMask delegator account flow is as follows: + +1. **Account setup** - A user creates an SCA by deploying a smart contract, and initializing it with + ownership and security settings. + The user can customize the SCA 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 signatory. + +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 account types + +The MetaMask Delegation Toolkit supports two types of delegator accounts, each offering unique features and use cases. +See [Configure accounts and signers](../how-to/configure-delegator-accounts-signers.md) to learn how to use these different account types. + +### Hybrid Delegator + +The Hybrid Delegator is a flexible implementation that supports both an externally owned account (EOA) "owner" and any number of P256 (passkey) signers. +You can configure any of these signers as the signatory, and use them to sign on behalf of the delegator. + +This type is referenced in the toolkit as `Implementation.Hybrid`. + +### Multisig Delegator + +The Multisig Delegator is an implementation that supports multiple signers with a configurable threshold for valid signatures, allowing for enhanced security and flexibility in account management. The signatory must have at least as many signers include as the threshold is configured for the account. + +This type is referenced in the Toolkit as `Implementation.Multisig`. diff --git a/gator_versioned_docs/version-0.11.0/concepts/environment.md b/gator_versioned_docs/version-0.11.0/concepts/environment.md new file mode 100644 index 00000000000..8c4cccf8cda --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/concepts/environment.md @@ -0,0 +1,228 @@ +--- +description: Learn about the delegator environment object `DeleGatorEnvironment` and how to use it. +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Delegator environment + +The `DeleGatorEnvironment` object is a component of the MetaMask Delegation Toolkit that defines the contract addresses necessary for interacting with the [Delegation Framework](delegation.md#delegation-framework) on a specific network. + +The delegator environment 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 delegator environment + +When you create a [`MetaMaskSmartAccount`](../how-to/create-delegator-account.md) instance, the Delegation 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 +import { DeleGatorEnvironment } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +const environment: DeleGatorEnvironment = delegatorSmartAccount.environment; +``` + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +const delegatorAccount = privateKeyToAccount("0x..."); + +const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegatorAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegatorAccount }, +}); + +export delegatorSmartAccount; +``` + + + + +:::note +See the [changelog](../changelog/0.11.0.md) of the toolkit version you are using for supported chains. +::: + +Alternatively, you can use the `getDelegatorEnvironment` function to resolve the environment. +This function is especially useful if your delegator is not a smart contract account when +[creating a redelegation](../how-to/create-delegation/index.md#create-a-redelegation). + +```typescript +import { + getDeleGatorEnvironment, + DeleGatorEnvironment, +} from "@metamask/delegation-toolkit"; + +// Resolves the DeleGatorEnvironment for Linea Sepolia +const environment: DeleGatorEnvironment = getDelegatorEnvironment(59141); +``` + +## Deploy custom delegator environment + +You can deploy the contracts using any method, but the toolkit provides a convenient `deployDelegatorEnvironment` 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.html), [Wallet Client](https://viem.sh/docs/clients/wallet.html), and [Chain](https://viem.sh/docs/glossary/types#chain) +to deploy the contracts and resolve the `DeleGatorEnvironment`. + +Your wallet must have sufficient native token balance to deploy the contracts. + + + + +```typescript +import { walletClient, publicClient } from "./config.ts"; +import { lineaSepolia as chain } from "viem/chains"; + +const environment = await deployDeleGatorEnvironment( + walletClient, + publicClient, + chain +); +``` + + + + +```typescript +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient, createPublicClient } from "viem"; + +// 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 `deployDelegatorEnvironment`. +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. +import { walletClient, publicClient } from "./config.ts"; +import { lineaSepolia as chain } from "viem/chains"; + +const environment = await deployDeleGatorEnvironment( + walletClient, + publicClient, + chain, + // add-start ++ { ++ EntryPoint: "0x0000000071727De22E5E9d8BAf0edAc6f37da032" ++ } + // add-end +); +``` + +Once the contracts are deployed, you can use them to override the delegator environment. + +## Override delegator environment + +To override the delegator environment, the toolkit provides a `overrideDeployedEnvironment` function to resolve +`DeleGatorEnvironment` with specified contracts for the given chain and contract version. + +```typescript +// The config.ts is the same as in the previous example. +import { walletClient, publicClient } from "./config.ts"; +import { lineaSepolia as chain } from "viem/chains"; +import { + DeleGatorEnvironment, + overrideDeployedEnvironment, + deployDeleGatorEnvironment +} from "@metamask/delegation-toolkit"; + +const environment: DeleGatorEnvironment = await deployDeleGatorEnvironment( + 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 `DelegatorEnvironment` instance with the required contract addresses, and pass it to the function. + +```typescript +// remove-start +- import { walletClient, publicClient } from "./config.ts"; +- import { lineaSepolia as chain } from "viem/chains"; +// remove-end +import { + DeleGatorEnvironment, + overrideDeployedEnvironment, + // remove-next-line +- deployDeleGatorEnvironment +} from "@metamask/delegation-toolkit"; + +// remove-start +- const environment: DeleGatorEnvironment = await deployDeleGatorEnvironment( +- walletClient, +- publicClient, +- chain +- ); +// remove-end + +// add-start ++ const evniroment: DeleGatorEnvironment = { ++ 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](../changelog/0.11.0.md) of the toolkit version you are using for its required Framework version. +::: diff --git a/gator_versioned_docs/version-0.11.0/experimental/_category_.json b/gator_versioned_docs/version-0.11.0/experimental/_category_.json new file mode 100644 index 00000000000..2592b227a06 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/experimental/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Experimental", + "position": 5, + "link": { + "type": "generated-index", + "slug": "/experimental", + "title": "Delegation Toolkit experimental features" + } +} \ No newline at end of file diff --git a/gator_versioned_docs/version-0.11.0/experimental/erc-7710-redeem-delegations.md b/gator_versioned_docs/version-0.11.0/experimental/erc-7710-redeem-delegations.md new file mode 100644 index 00000000000..85325565000 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/experimental/erc-7710-redeem-delegations.md @@ -0,0 +1,237 @@ +--- +description: Learn how to redeem ERC-7710 delegations with a smart contract account or an externally owned account (EOA). +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# ERC-7710: Redeem delegations + +:::caution Experimental +This is an experimental feature and may change in future releases. +::: + +[ERC-7710](https://eip.tools/eip/7710) introduces a standard way for smart contract accounts (SCAs) to delegate capabilities to other +SCAs or externally owned accounts (EOAs). + +The MetaMask Delegation Toolkit provides two experimental functions, `erc7710BundlerActions()` and `erc7710WalletActions()`, that let +a caller redeem delegations granted by MetaMask's permissions system. + +## Extract relevant data + +Refer to [ERC-7715: Request permissions](erc-7715-request-permissions.md) for information on how to request user permissions. +Once the permission has been granted, extract the relevant data from the response. +For example: + +```typescript +// Response received from the ERC-7715 wallet_grantPermissions request. +const permissionsResponse = [{ + chainId: "0xe715", + account: "0xD6f56C2B10b1e02D841E4a97c60Afe914E884DBd", + expiry: 1234567890, + permission: { + type: "native-token-stream", + data: { + amountPerSecond: "0x1", + maxAmount: "0x2", + initialAmount: undefined, + startTime: 2, + }, + }, + context: "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d" + signer: { + type: "account", + data: { + account: "0x07bfc7230D5BD2544059816D88A895BB000Abe00" + } + }, + signerMeta: { + delegationManager: "0xDC7e12b41E5e61BfCc7F56AAFB7B93288F61e841" + }, + accountMetadata: [{ + factory: "0x65E726b404149fE37F4b291c81Dc6eddd44763A7", + factoryData: "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b" + }] +}]; + +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +// accountMeta is only present when the smart contract account is not deployed. +const accountMetadata = permissionsResponse[0].accountMeta; +``` + +This data encodes the authority that lets the delegate redeem the permission. + +### Security considerations for `accountMeta` + +When a user grants a permission, they can provide `accountMeta` which is an array of `factory` and `factoryData` values. +These calls must be executed before redeeming the permission (this is handled for you in `sendUserOperationWithDelegation`). + +Because each `accountMeta` is an arbitrary call specified by the granter, it is important that these are executed carefully. +We recommend taking the following precautions: + +- **Only grant permissions to session accounts** - When requesting permissions, use an account that is only used for that single purpose, and does not contain tokens. +This way, any `accountMeta` executed can't perform any damaging actions. + +- **Only execute `accountMeta` against trusted factory addresses** - Ensure that only `accountMeta` targeting a known factory address is executed. +The bundler action `sendUserOperationWithDelegation` only executes `accountMeta` that targets the `SimpleFactory` address for the current Delegation Framework. +If you redeem delegations in any other way, it is your responsibility to validate trusted factory addresses. + +## Redeem the permission + +Redeem a delegation using one of two methods. Choose the method based on your account type: + +- If redeeming with an SCA, call `sendUserOperationWithDelegation`. +- If redeeming with an EOA, call `sendTransactionWithDelegation`. + +### Redeem with an SCA + +To redeem a delegation with a smart contract account, create a [`MetaMaskSmartAccount`](../how-to/create-delegator-account.md#create-a-metamasksmartaccount) +and a [Viem Bundler Client](https://viem.sh/account-abstraction/clients/bundler). + +After setting up your Bundler Client, you can extend its functionality with `erc7710BundlerActions` actions to support ERC-7710. Once extended, use `sendUserOperationWithDelegation` to redeem the permission. + + + + +```typescript +import { sessionAccount, bundlerClient, publicClient } from "./config.ts"; + +// These properties must be extracted from the permission response. +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +const accountMetadata = permissionsResponse[0].accountMeta; + +// 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 + accountMetadata, +}); +``` + + + + +```typescript +import { createPublicClient, http, createBundlerClient } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { erc7710BundlerActions } from "@metamask/delegation-toolkit/experimental"; +import { toMetaMaskSmartAccount, Implementation } from "@metamask/delegation-toolkit"; + +export const publicClient = createPublicClient({ + chain: chain, + transport: http(), +}); + +// Your session account for requesting and redeeming should be same. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + + +export const sessionAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { 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()); +``` + + + +:::note +`sendUserOperationWithDelegation` is similar to the `sendUserOperation` function, but does not accept `callData` directly. +::: + +### Redeem with an EOA + +To redeem a delegation with an EOA, create a [Viem Wallet Client](https://viem.sh/docs/clients/wallet). + +After creating your Wallet Client, you can extend its functionality with `erc7710WalletActions` actions to support ERC-7710. Once extended, use `sendTransactionWithDelegation` to redeem the permission. + + + + +```typescript +import { walletClient, publicClient } from "./config.ts"; + +// These properties must be extracted from the permission response. +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +const accountMetadata = permissionsResponse[0].accountMeta; + +if (accountMetadata?.length !== 0) { + // If the granted permission contains accountMetadata, this must be executed before attempting to + // redeem the delegation. + + // This transaction will deploy the delegator account. + const hash = walletClient.sendTransaction({ + to: accountMetadata.factory, + data: accountMetadata.factoryData, + }); + + // You should wait for transaction to be successfully executed. + // You can use the TransactionReceipt.status to verify the state. + await publicClient.waitForTransactionReceipt( { hash }); +} + +const hash = walletClient.sendTransactionWithDelegation({ + chain, + to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + value: 1n, + permissionsContext, + delegationManager +}); +``` + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { erc7710WalletActions } from "@metamask/delegation-toolkit/experimental"; + +export const publicClient = createPublicClient({ + chain, + transport: http() +}); + +// Your session account for requesting and redeeming should be same. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + +const walletClient = createWalletClient({ + account, + transport: http(), + chain, +}).extend(erc7710WalletActions()); +``` + + diff --git a/gator_versioned_docs/version-0.11.0/experimental/erc-7715-request-permissions.md b/gator_versioned_docs/version-0.11.0/experimental/erc-7715-request-permissions.md new file mode 100644 index 00000000000..8d605b4a76a --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/experimental/erc-7715-request-permissions.md @@ -0,0 +1,182 @@ +--- +description: Learn how to request ERC-7715 permissions. +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# ERC-7715: Request permissions + +:::caution Experimental +This is an experimental feature. +It requires MetaMask Flask 12.14.2 or later, and may change in future releases. +::: + +[ERC-7715](https://eip.tools/eip/7715) introduces a standard way for dapps to request permissions from a wallet to execute +transactions on a user's behalf. + +The MetaMask Delegation Toolkit provides the experimental actions for ERC-7715 that lets a caller request permissions from MetaMask's permissions system. + +## Request permissions + +To request permissions, extend your [Viem Wallet Client](https://viem.sh/docs/clients/wallet) with `erc7715ProviderActions` actions. +You'll need a session account to request the permission, which can be either an externally owned account (EOA) or a smart contract account (SCA). +This example uses an SCA: + + + + +```typescript +import { sepolia as chain } from "viem/chains"; +import { sessionAccount, walletClient } from "./config.ts"; + +const expiry = Math.floor(Date.now() / 1000 + 604_800); // 1 week from now. +const currentTime = Math.floor(Date.now() / 1000); // now + +const grantedPermissions = await walletClient.grantPermissions([{ + chainId: chain.id, + expiry, + signer: { + type: "account", + data: { + address: sessionAccount.address, + }, + }, + permission: { + type: "native-token-stream", + data: { + initialAmount: 1n, // 1 wei + amountPerSecond: 1n, // 1 wei per second + maxAmount: 10n, // 10 wei + startTime: currentTime, + justification: "Payment for a week long subscription", + }, + }, +}]); +``` + + + + + +```typescript +import { createWalletClient, custom, createPublicClient, http } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { erc7715ProviderActions } from "@metamask/delegation-toolkit/experimental"; +import { toMetaMaskSmartAccount, Implementation } from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain: chain, + transport: http(), +}); + +// The private key of the session owner. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + +export const sessionAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); + +export const walletClient = createWalletClient({ + transport: custom(window.ethereum), +}).extend(erc7715ProviderActions()); +``` + + + +
+ ERC-7715 request permission sample + + Here's what your ERC-7715 native token streaming allowance request looks like: + + ```ts + [{ + chainId: "0xaa36a7", + expiry: 1745041429, + permission: { + type: "native-token-stream", + data: { + amountPerSecond: "0x1", + maxAmount: "0x1", + initialAmount: "0xa", + startTime: 1744955029, + justification: "Payment for a week long subscription", + }, + }, + signer: { + type: "account", + data: { + address: "0x1234...", + }, + }, + }] + ``` + + Learn more about the [ERC-7715 permission schema](https://eip.tools/eip/7715). +
+ + +Additionally, you can allow users to modify the requested permission by setting the `isAdjustmentAllowed` parameter to `true` in the request. + +```typescript +import { sepolia as chain } from "viem/chains"; +// The config.ts is the same as in the previous example. +import { sessionAccount, walletClient } from "./config.ts"; + +const expiry = Math.floor(Date.now() / 1000 + 604_800); // 1 week from now. +const currentTime = Math.floor(Date.now() / 1000); // now + +const grantedPermissions = await walletClient.grantPermissions([{ + chainId: chain.id, + expiry, + signer: { + type: "account", + data: { + address: sessionAccount.address, + }, + }, + permission: { + type: "native-token-stream", + // add-next-line ++ isAdjustmentAllowed: true, + data: { + initialAmount: 1n, // 1 wei + amountPerSecond: 1n, // 1 wei per second + maxAmount: 10n, // 10 wei + startTime: currentTime, + justification: "Payment for a week long subscription", + }, + }, +}]); +``` + +:::note +Users have full control over the permissions they grant—depending on the permission you request, they may choose to grant more limited permissions than requested. +You should always verify the granted permissions and adjust your dapp's behavior accordingly. +::: + +## Security considerations for `accountMeta` + +When a user grants a permission, they can provide [`accountMeta`](erc-7710-redeem-delegations.md#extract-relevant-data) which is an array of `factory` and `factoryData` values. +These calls must be executed before redeeming the permission (this is handled for you in `sendUserOperationWithDelegation`). + +Because each `accountMeta` is an arbitrary call specified by the granter, it is important that these are executed carefully. +We recommend taking the following precautions: + +- **Only grant permissions to session accounts** - When requesting permissions, use an account that is only used for that single purpose, and does not contain tokens. + This way, any `accountMeta` executed can't perform any damaging actions. + +- **Only execute `accountMeta` against trusted factory addresses** - Ensure that only `accountMeta` targeting a known factory address is executed. + The bundler action `sendUserOperationWithDelegation` only executes `accountMeta` that targets the `SimpleFactory` address for the current Delegation Framework. + If you redeem delegations in any other way, it is your responsibility to validate trusted factory addresses. + +## Next steps + +You can redeem the granted permission using the experimental [ERC-7710 `erc7710WalletActions()`](erc-7710-redeem-delegations.md). diff --git a/gator_versioned_docs/version-0.11.0/experimental/store-retrieve-delegations.md b/gator_versioned_docs/version-0.11.0/experimental/store-retrieve-delegations.md new file mode 100644 index 00000000000..3b6cde29fd6 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/experimental/store-retrieve-delegations.md @@ -0,0 +1,172 @@ +--- +description: Store and retrieve delegations using the `DelegationStorageClient`. +sidebar_position: 1 +toc_max_heading_level: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Store and retrieve delegations + +:::caution Experimental +This is an experimental feature and may change in future releases. +::: + +You can use methods provided by the `DelegationStorageClient` of the MetaMask Delegation Toolkit to store and retrieve +[delegations](../concepts/delegation.md). + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](../how-to/configure.md) +- Ensure you have an API key and API key ID to interact with the `DelegationStorageClient`. + If you need to gain access, email hellogators@consensys.net. + +## Configure the storage client + +Create the `DelegationStorageClient` instance, and configure it using your API key and API key ID. + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + +## Store a delegation + +To store a delegation, use the `storeDelegation` method of the `DelegationStorageClient`. This method takes one parameter: + +1. `delegation` - A `Delegation` object representing the delegation to be stored. + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; + +const delegationHash = await delegationStorageClient.storeDelegation(delegation); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + + +## Retrieve a delegation chain + +To retrieve a delegation chain, use the `getDelegationChain` method of the `DelegationStorageClient`. This method takes one parameter: + +1. `leafDelegationOrDelegationHash` - Either a `Delegation` object or the delegation hash as a hex string. + +:::note +A delegation can be a root delegation, where its `authority` is `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`. It can also be a child of another delegation, where its `authority` is the hash of its parent delegation. This method returns the delegation referenced by `leafDelegationOrDelegationHash` and any ancestors. +::: + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; +import { getDelegationHashOffchain } from "@metamask/delegation-toolkit"; + +// Assuming you have the leaf delegation +const delegationHash = getDelegationHashOffchain(leafDelegation); + +const delegationChain: Delegation[] = await delegationStorageClient.getDelegationChain( + delegationHash +); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + + +## Retrieve delegations for a specific account + +To retrieve delegations stored for a specific account, use the `fetchDelegations` method of the `DelegationStorageClient`. This method allows you to fetch delegations where the specified account is either the delegator or the delegate. +It takes two parameters: + +1. `account` - The address of the account for which you want to retrieve delegations. +2. `filter` - The nature of the delegations. Possible values are: + - `DelegationStoreFilter.Given` - For delegations where the specified `account` is the `delegator`. + - `DelegationStoreFilter.Received` - For delegations where the specified `account` is the `delegate`. + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; + +const address = "0x027aeAFF3E5C33c4018FDD302c20a1B83aDCD96C" + +// Fetch the delegations given by address. +const grantedDelegations = await delegationStorageClient.fetchDelegations( + address, + DelegationStoreFilter.Given, +); + +// Fetch the delegations received by the address. +const receivedDelegations = await delegationStore.fetchDelegations( + address, + DelegationStoreFilter.Received, +); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + diff --git a/gator_versioned_docs/version-0.11.0/get-started/_category_.json b/gator_versioned_docs/version-0.11.0/get-started/_category_.json new file mode 100644 index 00000000000..4a23260d67d --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/get-started/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Get started", + "position": 2, + "link": { + "type": "generated-index", + "slug": "/get-started", + "title": "Get started with the Delegation Toolkit" + } +} diff --git a/gator_versioned_docs/version-0.11.0/get-started/install.md b/gator_versioned_docs/version-0.11.0/get-started/install.md new file mode 100644 index 00000000000..060d67f0ecd --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/get-started/install.md @@ -0,0 +1,48 @@ +--- +sidebar_label: Install and set up +description: Learn how to install and set up the MetaMask Delegation Toolkit. +sidebar_position: 1 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Install and set up the Delegation Toolkit + +This page provides instructions to install and set up the MetaMask Delegation Toolkit. + +## 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](../how-to/create-delegation/create-custom-caveat-enforcer.md)), install + [Foundry](https://book.getfoundry.sh/getting-started/installation). + +## Steps + +### 1. Install the toolkit + +Install the MetaMask Delegation Toolkit dependencies: + +```bash npm2yarn +npm install @metamask/delegation-toolkit +``` + +### 2. (Optional) Install the contracts + +If you plan to extend the Delegation Framework smart contracts (for example, to +[create a custom caveat enforcer](../how-to/create-delegation/create-custom-caveat-enforcer.md)), 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 MetaMask Delegation Toolkit. +Check out the [Delegation Toolkit quickstart](quickstart.md) to walk through a simple example. \ No newline at end of file diff --git a/gator_versioned_docs/version-0.11.0/get-started/llm-context.md b/gator_versioned_docs/version-0.11.0/get-started/llm-context.md new file mode 100644 index 00000000000..3129fa57229 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/get-started/llm-context.md @@ -0,0 +1,15 @@ +--- +sidebar_label: LLM context +description: Context about the MetaMask Delegation Toolkit that can be provided to an LLM. +sidebar_position: 3 +sidebar_class_name: hidden +--- + +# Delegation Toolkit LLM context + +This website contains [`LLMs.txt`](/llms.txt) and [`LLMs-full.txt`](/llms-full.txt) files that are intended for use by large language models (LLMs). +They provide information about the content and structure of the MetaMask Delegation Toolkit documentation, +to facilitate better indexing, summarization, and understanding by LLMs. + +You can add these files to an LLM-based tool like [ChatGPT](https://chatgpt.com/) or [Cursor](https://docs.cursor.com/context/@-symbols/@-docs), +to provide detailed context about the Delegation Toolkit. diff --git a/gator_versioned_docs/version-0.11.0/get-started/quickstart.md b/gator_versioned_docs/version-0.11.0/get-started/quickstart.md new file mode 100644 index 00000000000..41b7d5462cc --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/get-started/quickstart.md @@ -0,0 +1,173 @@ +--- +description: Get started quickly with the MetaMask Delegation Toolkit. +sidebar_position: 2 +sidebar_label: Quickstart +--- + +# Delegation Toolkit quickstart + +This page demonstrates how to get started quickly with the MetaMask Delegation Toolkit, +by creating a delegator account and completing the delegation lifecycle (creating, signing, and redeeming a delegation). + +## Prerequisites + +[Install and set up the Delegation Toolkit.](install.md) + +## Steps + +### 1. 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 delegator account query the signer's account state and interact with smart contracts. + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const publicClient = createPublicClient({ + chain, + transport: http(), +}); +``` + +### 2. 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 +import { createBundlerClient } from "viem/account-abstraction"; + +const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http("https://your-bundler-rpc.com"), +}); +``` + +### 3. Create a delegator account + +[Create a delegator account](../how-to/create-delegator-account.md) to set up a delegation. +The delegator must be a smart account. + +This example configures a [Hybrid Delegator](../how-to/configure-delegator-accounts-signers.md#configure-a-hybrid-delegator): + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; + +const delegatorAccount = privateKeyToAccount("0x..."); + +const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegatorAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegatorAccount }, +}); +``` + +### 4. Create a delegate account + +Create a delegate account to receive the delegation. +The delegate can be either a smart contract account (SCA) or an externally owned account (EOA). + +This example uses an SCA: + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; + +const delegateAccount = privateKeyToAccount("0x..."); + +const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + +### 5. Create a delegation + +[Create a root delegation](../how-to/create-delegation/index.md#create-a-root-delegation) from the +delegator account to the delegate account. + +This example passes an empty `caveats` array, which means the delegate can perform any action on the delegator's behalf. +We recommend [restricting the delegation](../how-to/create-delegation/restrict-delegation.md) by adding caveat enforcers. + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +const delegation = createDelegation({ + to: delegateSmartAccount.address, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + +### 6. Sign the delegation + +[Sign the delegation](../how-to/create-delegation/index.md#sign-a-delegation) using the `signDelegation` method from `MetaMaskSmartAccount`. +Alternatively, you can use the Delegation Toolkit's `signDelegation` utility. +The signed delegation will be used later to perform actions on behalf of the delegator. + +```typescript +const signature = await delegatorSmartAccount.signDelegation({ + delegation +}); + +const signedDelegation = { + ...delegation, + signature, +}; +``` + +### 7. Redeem the delegation + +The delegate account can now [redeem the delegation](../how-to/redeem-delegation.md). +The redeem transaction is sent to the `DelegationManager` contract, which validates the delegation and +executes actions on the delegator's behalf. + +To prepare the call data for the redeem transaction, use the `redeemDelegation` utility function from the Delegation Toolkit. + +```typescript +import { + createExecution, + DelegationFramework, + SINGLE_DEFAULT_MODE, +} from "@metamask/delegation-toolkit"; +import { zeroAddress } from "viem"; + +const delegations = [ signedDelegation ]; + +const executions = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ SINGLE_DEFAULT_MODE ], + executions: [ executions ] +}); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: delegateSmartAccount.address, + data: redeemDelegationCalldata + } + ], + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, +}); +``` diff --git a/gator_versioned_docs/version-0.11.0/how-to/_category_.json b/gator_versioned_docs/version-0.11.0/how-to/_category_.json new file mode 100644 index 00000000000..2b6df81f83d --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/how-to/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "How to", + "position": 4, + "link": { + "type": "generated-index", + "slug": "/how-to", + "title": "Delegation Toolkit how-to guides" + } +} diff --git a/gator_versioned_docs/version-0.11.0/how-to/configure-delegator-accounts-signers.md b/gator_versioned_docs/version-0.11.0/how-to/configure-delegator-accounts-signers.md new file mode 100644 index 00000000000..c48275d57f6 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/how-to/configure-delegator-accounts-signers.md @@ -0,0 +1,303 @@ +--- +sidebar_label: Configure accounts and signers +description: Learn how to configure different types of delegator accounts and signers using Viem. +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Configure delegator accounts and signers + +The MetaMask Delegation Toolkit supports different [delegator account types](../concepts/delegator-accounts.md#delegator-account-types), +each with its own configuration and support for different signing mechanisms. +You can create flexible and secure delegator accounts tailored to your specific needs. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator account.](create-delegator-account.md) + +## Configure a Hybrid Delegator + +The [Hybrid Delegator](../concepts/delegator-accounts.md#hybrid-delegator) supports both an EOA "owner" and any number of P256 (passkey) signers. + +To configure a Hybrid Delegator, provide the following parameters: + +- `owner`: The owner's account address as a hex string. + The owner can be the zero address, indicating that there is no owner configured. +- `p256KeyIds`: An array of key identifiers for P256 signers as hex strings. +- `p256XValues`: An array of public key x-values for P256 signers as `bigint`s. +- `p256YValues`: An array of public key y-values for P256 signers as `bigint`s. +- `signatory`: A signer that will sign on behalf of the delegator account. + +:::note +You can set all `p256` parameters to empty arrays to configure no WebAuthn signer. +However, we recommend configuring at least one signer for account recoverability. +::: + +For a Hybrid Delegator, you can configure the following types of signatories: + +### Configure an account signatory + +This example creates a signatory from a private key using Viem's [`privateKeyToAccount`](https://viem.sh/docs/accounts/local/privateKeyToAccount) function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const signatory = { account }; +``` + + + + +### Configure a Wallet Client signatory + +This example creates a [Viem Wallet Client](https://viem.sh/docs/clients/wallet) as the signatory, +using Viem's `createWalletClient` function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient } from "viem"; + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +const walletClient = createWalletClient({ + account, + chain, + transport: http() +}) + +export const signatory = { walletClient }; +``` + + + + +### Configure a WebAuthn (passkey) signatory + +This example creates a [Viem WebAuthn Account](https://viem.sh/account-abstraction/accounts/webauthn) as the signatory, +using Viem's `toWebAuthnAccount` function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { createCredential, parsePublicKey } from "webauthn-p256"; +import { toWebAuthnAccount } from "viem/account-abstraction"; +import { toHex } from "viem"; + +const credential = await createCredential({ name: "Your Delegator Passkey" }); +const webAuthnAccount = toWebAuthnAccount({ credential }); +const keyId = toHex("my-key-id"); + +const signatory = { webAuthnAccount, keyId }; +``` + + + + + +## Configure a Multisig Delegator + +The [Multisig Delegator](../concepts/delegator-accounts.md#multisig-delegator) supports multiple EOA signers with a configurable threshold for execution. + +To configure a Multisig Delegator, provide the following parameters: + +- `signers`: An array of EOA signer addresses as hex strings. +- `threshold`: The number of signers required to execute a transaction, as a `bigint`. +- `signatory`: An array of signatories that will sign on behalf of the delegator account. + +### Configure signatories + +For a Multisig Delegator, you can use a combination of account signatories and Wallet Client signatories. +For example: + + + + +```typescript +import { publicClient } from "./client.ts"; +import { account, walletClient } from "./signers.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const signers = [ account.address, walletClient.address ]; +const signatory = [ { account }, { walletClient } ]; +const threshold = 2n + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.MultiSig, + deployParams: [signers, threshold], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient } from "viem"; + +// 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. +::: diff --git a/gator_versioned_docs/version-0.11.0/how-to/configure.md b/gator_versioned_docs/version-0.11.0/how-to/configure.md new file mode 100644 index 00000000000..e85115c1408 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/how-to/configure.md @@ -0,0 +1,60 @@ +--- +description: Learn how to configure the MetaMask Delegation Toolkit using Viem. +sidebar_position: 1 +sidebar_label: Configure the toolkit +--- + +# Configure the Delegation Toolkit + +The MetaMask Delegation Toolkit enables you to easily integrate delegator accounts into your dapp, enabling a more flexible, secure, and frictionless experience for your users. + +The toolkit is highly configurable, allowing you to tailor it to your project's specific needs. It includes support for custom signers, multiple signatory schemes, custom paymasters and bundlers, and more. + +:::note +The MetaMask Delegation Toolkit provides custom middleware for [Pimlico's](https://docs.pimlico.io/) gas fee resolver, paymaster, and bundler. Additional options will be made available soon. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit](../get-started/install.md). +- Optionally, complete the [Delegation Toolkit quickstart](../get-started/quickstart.md) to + familiarize yourself with the toolkit's capabilities. + +## Viem's Account Abstraction API + +The toolkit uses Viem's Account Abstraction API. This provides a robust and flexible foundation for creating and managing smart contract accounts. +See Viem's [Smart Account documentation](https://viem.sh/account-abstraction/accounts/smart) for more information on the API's features, methods, and best practices. + + +## Configure Viem bundler and paymaster clients + +To use the bundler and paymaster clients with the toolkit, create instances of these clients and configure them as follows: + +```typescript +import { + createPaymasterClient, + createBundlerClient, +} from "viem/account-abstraction"; +import { http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +// 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, +}); +``` + +:::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 sufficient funds to pay for gas fees directly. +::: + diff --git a/gator_versioned_docs/version-0.11.0/how-to/create-delegation/create-custom-caveat-enforcer.md b/gator_versioned_docs/version-0.11.0/how-to/create-delegation/create-custom-caveat-enforcer.md new file mode 100644 index 00000000000..77416e901f5 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/how-to/create-delegation/create-custom-caveat-enforcer.md @@ -0,0 +1,149 @@ +--- +description: Learn how to create, deploy, and apply a custom caveat enforcer +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a custom caveat enforcer + +When [restricting a delegation](restrict-delegation.md), the MetaMask Delegation Toolkit provides some [out-of-the-box caveat enforcers](restrict-delegation.md#caveat-types) +that cover common use cases. +For more granular or custom control, you can follow the instructions on this page to create custom caveat enforcers from scratch. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../get-started/install.md) +- [Configure the Delegation Toolkit.](../configure.md) + +## Steps + +### 1. Create the caveat enforcer + +Create a contract that extends the +[`ICaveatEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/interfaces/ICaveatEnforcer.sol) +interface. + +For example, the following is a simple caveat enforcer that only allows a delegation to be redeemed after a specific timestamp. + +```solidity title="AfterTimestampEnforcer.sol" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import { CaveatEnforcer } from "@delegator/src/enforcers/CaveatEnforcer.sol"; +import { ModeCode } from "../utils/Types.sol"; + +contract AfterTimestampEnforcer is CaveatEnforcer { + /** + * @notice The delegation may only be redeemed after the specified timestamp - validAfter in seconds. + * @param _terms The validAfter, timestamp in seconds after which the delegation may be redeemed. + * @param _delegationHash - The hash of the delegation being operated on. + */ + function beforeHook( + bytes calldata _terms, + bytes calldata, + ModeCode, + bytes calldata, + bytes32 _delegationHash, + address, + address _redeemer + ) + public + override + { + // Enforces the conditions that should hold before a transaction is performed. + // This function MUST revert if the conditions are not met. + // Get the current timestamp + uint256 timestamp = block.timestamp; + + uint256 validAfter = uint256(bytes32(_terms)); + + require(timestamp > validAfter, "AfterTimestampEnforcer:cannot-redeem-too-early"); + } +} +``` + +### 2. Deploy the caveat enforcer + +Deploy your custom caveat enforcer to obtain its contract address. +For example, you can [deploy your smart contract using Forge](https://book.getfoundry.sh/forge/deploying). + +### 3. Apply the caveat enforcer + +When creating a delegation, add the `Caveat` for the custom caveat to the `CaveatBuilder`. +Learn more about [applying caveats to a delegation](restrict-delegation.md). + +The following example uses the custom `AfterTimestampEnforcer.sol` caveat enforcer to create a delegation granting +an allowance of 1,000,000 wei that can only be spent after one hour from when the delegation is created. + +:::warning Important +Depending on the use case, it may be necessary to add additional caveats to restrict the delegation further. +::: + + + + +```typescript +import { + createCaveatBuilder, + createDelegation, +} from "@metamask/delegation-toolkit"; +import { toHex } from "viem"; +import { delegatorSmartAccount } from "./config.ts"; + +const environment = delegatorSmartAccount.enviroment; + +// Replace this with the address where the AfterTimestampEnforcer.sol contract is deployed. +const afterTimestampEnforcer = "0x22Ae4c4919C3aB4B5FC309713Bf707569B74876F"; + +const caveatBuilder = createCaveatBuilder(environment); + +const tenAM = 10 * 60 * 60; // 10:00 AM as seconds since midnight. + +const caveats = caveatBuilder + .addCaveat("nativeTokenTransferAmount", 1_000_000) + .addCaveat({ + enforcer: afterTimestampEnforcer, + terms: toHex(tenAm) + }); + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats +}); +``` + + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + diff --git a/gator_versioned_docs/version-0.11.0/how-to/create-delegation/index.md b/gator_versioned_docs/version-0.11.0/how-to/create-delegation/index.md new file mode 100644 index 00000000000..8cd0f457f6f --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/how-to/create-delegation/index.md @@ -0,0 +1,349 @@ +--- +description: Learn how to create different types of delegations, and how to sign a delegation. +sidebar_position: 6 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a delegation + +The MetaMask Delegation Toolkit enables you to create [delegations](../../concepts/delegation.md) +from a delegator account to a delegate account. + +:::note +Delegations are compatible with [ERC-7710](https://eip.tools/eip/7710) and [ERC-7715](https://ethereum-magicians.org/t/erc-7715-grant-permissions-from-wallets/20100), to support a standardized minimal interface. +[Requesting ERC-7715 permissions](../../experimental/erc-7715-request-permissions.md) and [redeeming ERC-7710 delegations](../../experimental/erc-7710-redeem-delegations.md) +are experimental features. +::: + +:::warning +The examples on this page demonstrate delegations without any restrictions. +Unrestricted delegations grant complete control over the account to the delegate, which can pose significant security risks. +It is crucial to add caveats to limit the delegated authority. +Learn how to [restrict a delegation](./restrict-delegation.md) using caveat enforcers. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../get-started/install.md) +- [Configure the Delegation Toolkit.](../configure.md) +- [Create a delegator account.](../create-delegator-account.md) + +## Create a root delegation + +A *root delegation* is a delegation that doesn't derive its authority from another delegation. +It is when a delegator delegates its own authority away, as opposed to a [redelegation](#create-a-redelegation). +Create a root delegation as follows: + + + + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.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: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create an 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. +You must create open root delegations carefully, to ensure that they are not misused. +Create an open root delegation by setting the delegate property to the special address +`0x0000000000000000000000000000000000000a11` (available via the constant `ANY_BENEFICIARY`). + + + + +```typescript +import { createOpenDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +const openRootDelegation = createOpenDelegation({ + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create a redelegation + +A recipient of a delegation (the delegate), can *redelegate* that authority to a third party, potentially applying additional [restrictions](restrict-delegation.md). +Create a redelegation as follows: + + + + +```typescript +import { + createDelegation, + getDelegationHashOffchain + } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +// The address is used as the root delegator. While creating the redelegation, +// the root delegate address will be the delegator address. +const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +// The authority is the (typed data) hash of the delegation from which the authority is derived. +const parentDelegationHash = getDelegationHashOffchain(delegation); + +// The address is used as the delegate address while creating the redelegation. +const leafDelegate = "0xb4821Ab7d5942Bd2533387592068a12608B4a52C" + +const leafDelegation = createDelegation({ + to: leafDelegate, + from: delegate, + // You can also choose to pass the parent delegation object, and let function + // handle the rest. + parentDelegation: parentDelegationHash, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create an open redelegation + +An *open redelegation* is a [redelegation](#create-a-redelegation) that doesn't specify a delegate. +This means that any account can redeem the redelegation. +As with [open root delegations](#create-an-open-root-delegation), you must create open redelegations carefully, +to ensure that they are not misused. +Create an open redelegation as follows: + + + + +```typescript +import { + createDelegation, + createOpenDelegation, + getDelegationHashOffchain + } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +// The address is used as the root delegator. While creating the redelegation, +// the root delegate address will be the delegator address. +const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +// The authority is the (typed data) hash of the delegation from which the authority is derived. +const parentDelegationHash = getDelegationHashOffchain(delegation); + +const leafDelegation = createOpenDelegation({ + from: delegate, + // You can also choose to pass the parent delegation object, and let the function + // handle the rest. + parentDelegation: parentDelegationHash, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Sign a delegation + +A delegation must be signed by the delegator to be valid for redemption. The `MetaMaskSmartAccount` supports signing the delegation using [EIP-712 Typed Data](https://eips.ethereum.org/EIPS/eip-712) via the `signDelegation` method. +Sign a delegation as follows: + + + + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.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: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +const signature = await delegatorSmartAccount.signDelegation({ delegation }); + +const signedDelegation = { + ...delegation, + signature +}; +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); + +``` + + + \ No newline at end of file diff --git a/gator_versioned_docs/version-0.11.0/how-to/create-delegation/restrict-delegation.md b/gator_versioned_docs/version-0.11.0/how-to/create-delegation/restrict-delegation.md new file mode 100644 index 00000000000..e3fd940e288 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/how-to/create-delegation/restrict-delegation.md @@ -0,0 +1,862 @@ +--- +description: Learn how to restrict a delegation using caveat enforcers, and the available caveat types. +sidebar_position: 1 +toc_max_heading_level: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Restrict a delegation + +Use [caveat enforcers](../../concepts/caveat-enforcers.md) to apply specific rules and restrictions +to a delegation, ensuring that delegated executions are only performed under predefined circumstances. + +A delegation has a `caveats` property, which is an array of `Caveat` objects. +Each caveat is specified as follows: + +```typescript +export type Caveat = { + enforcer: Hex; // The address of the caveat enforcer contract. + terms: Hex; // Data passed to the caveat enforcer, describing how the redemption should be validated. + args: Hex; // Data that may be specified by the redeemer when redeeming the delegation (only used in limited cases). +}; +``` + +The MetaMask Delegation Toolkit provides a `CaveatBuilder` interface, which offers an intuitive way to define the `caveats` array. +Use the `CaveatBuilder` to easily ensure that your delegations grant only the necessary authority. + +## Create the caveat builder + +To create the caveat builder, call the `createCaveatBuilder()` function, passing an instance of `DeleGatorEnvironment`. +The environment can be accessed from the `MetaMaskSmartAccount`, as in this example: + +```typescript +const environment = delegatorSmartAccount.environment; + +const caveatBuilder = createCaveatBuilder(environment); +``` + +:::note +By default, the `CaveatBuilder` does not allow empty caveats. To allow the `CaveatBuilder` to build an empty caveats array, provide the following configuration: + +```typescript +const caveatBuilder = createCaveatBuilder(environment, { allowEmptyCaveats: true }); +``` +::: + +## Add caveats to the builder + +Add caveats to the builder using the `addCaveat` method, specifying the [caveat type](#caveat-types) and its parameters. You can chain multiple calls to `addCaveat` as in the following example: + +```typescript +const caveats = caveatBuilder + // allowedTargets accepts an array of addresses. + // This caveat restricts the caller to only use the delegation to interact with the specified address. + .addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92"]) + // allowedMethods accepts an array of methods. + // This caveat restricts the caller to only use the delegation to invoke the specified methods. + .addCaveat("allowedMethods", [ + "approve(address,uint256)", + "transfer(address,uint256)" + ]) + // limitedCalls accepts a number. + // This caveat restricts the caller to only use the delegation one time. + .addCaveat("limitedCalls", 1) + .build(); +``` + +
+Important considerations when using caveat enforcers +

+ +- Delegations without caveats are entirely permissive. + It is crucial to add appropriate caveats to restrict the delegated authority sufficiently. + Failing to do so could result in unintended access or actions. +- Caveat enforcers safeguard the execution process but do not guarantee a final state post-redemption. + Always combine caveat enforcers thoughtfully to create comprehensive protection. +- When using multiple caveat enforcers that modify external contract states, the order matters. + For example, if you include both [`NativeBalanceChangeEnforcer`](#nativebalancechange) to ensure a balance has increased and + [`NativeTokenPaymentEnforcer`](#nativetokenpayment) to deduct from that balance, + executing `NativeTokenPaymentEnforcer` first might cause `NativeBalanceChangeEnforcer` to fail validation. + Consider the sequence of enforcers carefully when creating delegations with interdependent caveats. + +

+
+ +For convenience, you can also pass the `CaveatBuilder` directly to the various helper methods for creating a delegation. For example: + +```typescript +const caveats = caveatBuilder + // allowedTargets accepts an array of addresses. + .addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92"]) + // allowedMethods accepts an array of methods. + .addCaveat("allowedMethods", [ + "approve(address,uint256)", + "transfer(address,uint256)" + ]) + // limitedCalls accepts a number. + .addCaveat("limitedCalls", 1); + +const delegation = createDelegation({ + to: delegate, + from: delegator, + caveats +}); +``` + +## Caveat types + +The `CaveatBuilder` supports various caveat types, each serving a specific purpose. +These caveat types correspond to the out-of-the-box caveat enforcers +that the MetaMask Delegation Toolkit provides. + +For more granular or custom control, you can also [create custom caveat enforcers](create-custom-caveat-enforcer.md) +and add them to the caveat builder. + +### `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 + +1. Index in the calldata byte array (including the 4-byte method selector) where the expected calldata starts +2. Expected calldata as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("allowedCalldata", + 4, + encodeAbiParameters([ + { type: "string" }, + { type: "uint256" } + ], [ + "Hello Gator", + 12345n + ]) +); +``` + +:::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 + +1. An array of methods as 4-byte hex strings, ABI function signatures, or `ABIFunction` objects + +#### Example + +```typescript +caveatBuilder.addCaveat("allowedMethods", [ + "0xa9059cbb", + "transfer(address,uint256)", + { + 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 + +1. An array of addresses as hex strings + +#### Example + +```typescript +caveatBuilder.addCaveat("allowedTargets", [ + "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 + +1. The expected `args` as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("argsEqualityCheck", + "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 + +1. After threshold block number as a `bigint` +2. Before threshold block number as a `bigint` + +You can specify `0n` to indicate that there is no limitation on a threshold. + +#### Example + +```typescript +caveatBuilder.addCaveat("blocknumber", + 19426587n, + 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 + +1. A contract address as a hex string +2. The salt to use with the contract, as a hex string +3. The bytecode of the contract as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("deployed", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x0e3e8e2381fde0e8515ed47ec9caec8ba2bc12603bc2b36133fa3e3fa4d88587", + "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:** [`ERC1155BalanceBalanceEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC1155BalanceBalanceEnforcer.sol) + +#### Parameters + +1. An ERC-1155 contract address as a hex string +2. The recipient's address as a hex string +3. The ID of the ERC-1155 token as a bigint +4. The amount by which the balance must have changed as a `bigint` +5. 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 +caveatBuilder.addCaveat("erc1155BalanceChange", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1n, + 1_000_000n, + 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 + +1. An ERC-20 contract address as a hex string +2. The recipient's address as a hex string +3. The amount by which the balance must have changed as a `bigint` +4. 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 +caveatBuilder.addCaveat("erc20BalanceChange", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n, + 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 + +1. The address of the ERC-20 token contract. +2. The maximum amount of tokens that can be transferred per period, in wei. +3. The duration of each period in seconds. +4. The timestamp when the first period begins. + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20PeriodTransfer", + "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", // Address of the ERC-20 token + 1000000000000000000n, // 1 ERC-20 token - 18 decimals, in wei + 86400, // 1 day in seconds + 1743763600, // April 4th, 2025, at 00:00:00 UTC +); +``` + +### `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 + +1. An ERC-20 contract address as a hex string +2. Initial amount available at start time as a `bigint` +3. Maximum total amount that can be unlocked as a `bigint` +4. Rate at which tokens accrue per second as a `bigint` +5. Start timestamp as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20Streaming", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n, + 10_000_000n, + 100n, + 1703980800 +); +``` + +### `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 + +1. An ERC-20 contract address as a hex string +2. Amount as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20TransferAmount", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n +); +``` + +### `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 + +1. An ERC-721 contract address as a hex string +2. The recipient's address as a hex string +3. The amount by which the balance must have changed as a `bigint` +4. 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 +caveatBuilder.addCaveat("erc721BalanceChange", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n, + 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 + +1. The permitted ERC-721 contract address as a hex string +2. The permitted ID of the ERC-721 token as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc721Transfer", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 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 + +1. A hex value for calldata. + +#### Example + +```typescript +caveatBuilder.addCaveat("exactCalldata", + "0x1234567890abcdef" // Calldata to be matched +); +``` + +### `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 + +1. Array of expected `ExecutionStruct`, each containing target address, value, and calldata. + +#### Example + +```typescript +const executions = [ + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + callData: "0x", + }, + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 0n, + callData: "0x", + }, +]; + +caveatBuilder.addCaveat("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 + +1. `ExecutionStruct` to be expected. + +#### Example + +```typescript +caveatBuilder.addCaveat("exactExecution", { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + 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 + +1. Array of expected `ExecutionStruct`, each containing target address, value, and calldata. + +#### Example + +```typescript +const executions = [ + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + callData: "0x", + }, + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 0n, + callData: "0x", + }, +]; + +caveatBuilder.addCaveat("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 + +1. An ID as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("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 + +1. A count as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("limitedCalls", + 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 + +An array of token period configuration objects, where each object contains: + - `token`: The address of the token contract as a hex string. + - `periodAmount`: The maximum amount of tokens that can be transferred per period, in wei. + - `periodDuration`: The duration of each period in seconds. + - `startDate`: A Unix epoch timestamp (in seconds) representing when the first period begins. + +#### Example + +```typescript +caveatBuilder.addCaveat("multiTokenPeriod", [ + { + token: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", // First token contract + periodAmount: 1000000000000000000n, // 1 token with 18 decimals + periodDuration: 86400, // 1 day in seconds + startDate: 1743763600, // April 4th, 2025, at 00:00:00 UTC + }, + { + token: "0x6be97c23596ECed7170fdFb28e8dA1Ca5cdc54C5", // Second token contract + periodAmount: 500000000n, // 0.5 tokens with 9 decimals + periodDuration: 3600, // 1 hour in seconds + startDate: 1743763600, // April 4th, 2025, at 00:00:00 UTC + } +]); +``` + +### `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 + +1. The recipient's address as a hex string +2. The amount by which the balance must have changed as a `bigint` +3. 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 +caveatBuilder.addCaveat("nativeBalanceChange", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n, + 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 + +1. The recipient's address as a hex string +2. The amount that must be paid as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenPayment", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n +); +``` + +### `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 + +1. The maximum amount of tokens that can be transferred per period, in wei. +2. The duration of each period in seconds. +3. The timestamp when the first period begins. + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenPeriodTransfer", + 1000000000000000000n, // 1 ETH in wei + 86400, // 1 day in seconds + 1743763600, // April 4th, 2025, at 00:00:00 UTC +) +``` + +### `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 + +1. Initial amount available at start time as a `bigint` +2. Maximum total amount that can be unlocked as a `bigint` +3. Rate at which tokens accrue per second as a `bigint` +4. Start timestamp as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenStreaming", + 1_000_000n, + 10_000_000n, + 100n, + 1703980800 +); +``` + +### `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 + +1. The allowance as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenTransferAmount", + 1_000_000n +); +``` + +### `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 + +1. A nonce as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("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 + +1. The target contract address as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("ownershipTransfer", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92" +); +``` + +### `redeemer` + +Limits the addresses that can redeem the delegation. +This caveat is designed for restricting 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 + +1. An array of addresses as hex strings + +#### Example + +```typescript +caveatBuilder.addCaveat("redeemer", + [ + "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 + +1. The address of the ERC-20 token contract. +2. The address that will receive the tokens. +3. The amount of tokens to transfer, in wei. +4. The target address for the first transaction. +5. The calldata for the first transaction. + +#### Example + +```typescript +caveatBuilder.addCaveat("specificActionERC20TransferBatch", + "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da" // Address of ERC-20 token contract + "0x027aeAFF3E5C33c4018FDD302c20a1B83aDCD96C", // Address that will receive the tokens + 1000000000000000000n, // 1 ERC-20 token - 18 decimals, in wei + "0xb49830091403f1Aa990859832767B39c25a8006B", // Target address for first transaction + "0x1234567890abcdef" // Calldata to be matched for first transaction +) +``` + +### `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 + +1. After threshold timestamp as a number +2. Before threshold timestamp as a number + +You can specify `0` to indicate that there is no limitation on a threshold. + +#### Example + +```typescript +caveatBuilder.addCaveat("timestamp", + 499165200, + 1445412480 +); +``` + +### `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 + +1. A value as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("valueLte", + 1_000_000_000_000_000_000n // 1 ETH in wei +); +``` diff --git a/gator_versioned_docs/version-0.11.0/how-to/create-delegator-account.md b/gator_versioned_docs/version-0.11.0/how-to/create-delegator-account.md new file mode 100644 index 00000000000..73e132ee5a1 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/how-to/create-delegator-account.md @@ -0,0 +1,80 @@ +--- +description: Learn how to create a delegator account using Viem. +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a delegator account + +The MetaMask Delegation Toolkit is embedded, meaning that the end user can instantly interact with a dapp without wallet authorization, confirmations, or corporate logos. Enable users to create a [delegator account](../concepts/delegator-accounts.md) directly in your dapp. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) + +## Create a `MetaMaskSmartAccount` + +The following is an example of creating a delegator account using Viem Core SDK. +Viem Core SDK provides low-level interfaces to offer flexibility and control over the delegator +account creation lifecycle. + +In the example, the Viem [`privateKeyToAccount`](https://viem.sh/docs/accounts/privateKey.html) +function creates an externally owned account as the owner of the delegator account. + + + + +```typescript +import { publicClient, owner } from "./config.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const deploySalt = "0x"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner.address, [], [], []], + deploySalt, + signatory: { account: owner }, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); + +const privateKey = generatePrivateKey(); +export const owner = privateKeyToAccount(privateKey); +``` + + + + + +This example creates the `MetaMaskSmartAccount`, which can perform several functions: + +- In conjunction with [Viem Account Abstraction clients](configure.md), deploy the smart contract account, + and [send user operations](send-user-operation.md). +- [Sign delegations](create-delegation/index.md) that can be used to grant specific rights and permissions to other accounts. + +:::note +The example above uses the Hybrid Delegator smart contract account, which is configurable to have an EOA "owner" and any number of P256 (passkey) signers. +You can also [configure other delegator account types](configure-delegator-accounts-signers.md). +::: diff --git a/gator_versioned_docs/version-0.11.0/how-to/generate-multisig-signature.md b/gator_versioned_docs/version-0.11.0/how-to/generate-multisig-signature.md new file mode 100644 index 00000000000..f98c2f765ba --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/how-to/generate-multisig-signature.md @@ -0,0 +1,124 @@ +--- +description: Learn how to generate a Multisig signature. +sidebar_position: 5 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Generate a multisig signature + +The MetaMask Delegation Toolkit supports Multisig smart contract +account (SCAs), allowing you to add multiple externally owned account (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` function to combine them +into a single aggregated signature. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a Multisig Delegator account.](configure-delegator-accounts-signers#configure-a-multisig-delegator) + +## Generate a multisig signature + +In this example, we'll create a Multisig SCA with two different signers: Alice +and Bob. The account will have a threshold of 2, meaning that signatures from +both parties are required for any execution. + + + + + +```typescript +import { + bundlerClient, + aliceSmartAccount, + bobSmartAccount, + aliceAccount, + bobAccount, +} from "./config.ts"; +import { aggregateSignature } from "@metamask/delegation-toolkit"; + +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 +import { createPublicClient, http } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { createBundlerClient } from "viem/account-abstraction"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +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", + signatory: [ { account: aliceAccount } ], +}); + +export const bobSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.MultiSig, + deployParams: [signers, threshold], + deploySalt: "0x", + signatory: [ { account: bobAccount } ], +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http("https://public.pimlico.io/v2/rpc") +}); +``` + + + + + diff --git a/gator_versioned_docs/version-0.11.0/how-to/redeem-delegation.md b/gator_versioned_docs/version-0.11.0/how-to/redeem-delegation.md new file mode 100644 index 00000000000..23be468113e --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/how-to/redeem-delegation.md @@ -0,0 +1,347 @@ +--- +description: Learn how to redeem a delegation with a smart contract account (SCA) or an externally owned account (EOA). +sidebar_position: 7 +toc_max_heading_level: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Redeem a delegation + +A delegate can redeem a delegation by submitting either a user operation or a regular transaction, +depending on whether the delegate is a smart contract account (SCA) or externally owned account (EOA). + +The redeem transaction is sent to the `DelegationManager` contract, which validates the delegation and executes actions on the delegator's behalf. To prepare the call data for the redeem transaction, use the `redeemDelegation` utility function. The function supports batch redemption, allowing multiple delegations to be processed within a single transaction. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator account.](create-delegator-account.md) +- [Create a delegation.](create-delegation/index.md) + +## Redeem a delegation + +Redeem a delegation with a [smart contract account (SCA)](#redeem-with-an-sca) or an [externally owned account (EOA)](#redeem-with-an-eoa). + +### Redeem with an SCA + +The following example demonstrates how to submit a user operation to redeem a delegation. +It assumes you have a delegation signed by the delegator, and that the delegate is an SCA. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { bundlerClient, pimlicoClient } from "./client.ts"; +import { delegateSmartAccount } from "./account.ts"; + +const delegations: Delegation[] = [ signedDelegation ]; + +// SINGLE_DEFAULT_MODE is the default execution mode. +const mode: ExecutionMode = SINGLE_DEFAULT_MODE; + +// For SINGLE execution modes, the executions array must be length 1. +const executions: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const { fast: fee } = await pimlicoClient.getUserOperationGasPrice(); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: "", + data: redeemDelegationCalldata + } + ], + ...fee +}); +``` + + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +improt { publicClient } from "./client.ts" + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { createPimlicoClient } from "permissionless/clients/pimlico"; + +// You can get the API Key from the Pimlico dashboard. +const pimlicoUrl = "https://api.pimlico.io/v2/59141/rpc"; + +export const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http(pimlicoUrl), +}); + +export const pimlicoClient = createPimlicoClient({ + transport: http(pimlicoUrl), +}); +``` + + + + +### Redeem with an EOA + +The following example demonstrates how to submit a transaction to redeem a delegation. It assumes you have a delegation signed by the delegator, and that the delegate is an EOA. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { lineaSepolia as chain } from "viem/chains"; +import { delegateWalletClient } from "./account.ts"; + +const delegations: Delegation[] = [ signedDelegation ]; + +// SINGLE_DEFAULT_MODE is the default execution mode. +const mode: ExecutionMode = SINGLE_DEFAULT_MODE; + +// For SINGLE execution modes, the executions array must be length 1. +// Modify the executions to fit your use case. +const executions: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const transactionHash = await walletClient.sendTransaction({ + to: getDeleGatorEnvironment(chain.id).DelegationManager, + data: redeemDelegationCalldata, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { createWalletClient, http } from "viem"; + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateWalletClient = createWalletClient({ + account: delegateAccount, + chain, + transport: http(), +}) +``` + + + + +## Redeem multiple delegations + +You can redeem multiple delegations in a single user operation, each delegation independent of the others. +Each element in the `delegationsArray` must have a corresponding element in the `executionsArray` and `modes`. + +The following example assumes you already have multiple signed delegations and that the delegate is an SCA. +The preparation of the call data is the same when [using an EOA as the delegate](#redeem-with-an-eoa); +the primary difference is that an EOA submits a regular transaction instead of a user operation. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { bundlerClient, pimlicoClient } from "./client.ts"; +import { delegateSmartAccount } from "./account.ts"; + +const delegationsArray: Delegation[][] = [ + [ signedDelegation1 ] + [ signedDelegation2 ] + [ signedDelegation3 ] +]; + +const modes: ExecutionMode = [ + SINGLE_DEFAULT_MODE, + SINGLE_DEFAULT_MODE, + SINGLE_DEFAULT_MODE +]; + +const execution: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +// Modify the executions to fit your use case. For simplicity, we've +// included a basic example. The execution array can contain +// multiple different executions. +const executionsArray: ExecutionStruct:[][] = [ + execution, + execution, + execution +]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const { fast: fee } = await pimlicoClient.getUserOperationGasPrice(); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: "", + data: redeemDelegationCalldata + } + ], + ...fee +}); +``` + + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +import { publicClient } from "./client.ts"; + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { createPimlicoClient } from "permissionless/clients/pimlico"; + +// You can get the API Key from the Pimlico dashboard. +const pimlicoUrl = "https://api.pimlico.io/v2/59141/rpc"; + +export const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http(pimlicoUrl), +}); + +export const pimlicoClient = createPimlicoClient({ + transport: http(pimlicoUrl), +}); +``` + + + + +## Execution modes + +The Delegation Toolkit supports several execution modes based on [ERC-7579](https://erc7579.com/). +See the [ERC implementation](https://github.com/erc7579/erc7579-implementation/blob/main/src/lib/ModeLib.sol) for more details about the execution modes. + +The supported execution modes are `SINGLE_DEFAULT_MODE`, `SINGLE_TRY_MODE`, `BATCH_DEFAULT_MODE`, and `BATCH_TRY_MODE`. + +### `SINGLE` execution modes + +In `SINGLE` execution modes, only a single delegation chain and a single execution can be provided. This mode processes delegations sequentially: + +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. + +### `BATCH` execution modes + +In `BATCH` execution modes, multiple delegation chains and multiple executions can be provided. This mode executes delegations in an interleaved way: + +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. + +### `DEFAULT` modes + +In `DEFAULT` modes, if a revert occurs during redemption, the entire user operation reverts at that point. + +### `TRY` modes + +In `TRY` modes, if a revert occurs during redemption, execution of the user operation continues. diff --git a/gator_versioned_docs/version-0.11.0/how-to/send-user-operation.md b/gator_versioned_docs/version-0.11.0/how-to/send-user-operation.md new file mode 100644 index 00000000000..fcb3c63d5f1 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/how-to/send-user-operation.md @@ -0,0 +1,180 @@ +--- +description: Learn how to send an ERC-4337 user operation using Viem. +sidebar_position: 4 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# 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 Delegation Toolkit 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 smart contract account that has not been deployed, the toolkit configures the user operation to automatically deploy the account. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator account.](create-delegator-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 +import { bundlerClient, smartAccount } from "./config.ts"; +import { parseEther } from "viem"; + +// 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 +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { createBundlerClient } from "viem/account-abstraction"; +import { lineaSepolia as chain } from "viem/chains"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + + +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", + signatory: { account }, +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http("https://public.pimlico.io/v2/1/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). + +```typescript title="example.ts" +// add-next-line ++ import { createPimlicoClient } from "permissionless/clients/pimlico"; +import { parseEther } from "viem"; +import { bundlerClient, smartAccount } from "./config.ts" // The config.ts is the same as in the previous example. + +// remove-start +- const maxFeePerGas = 1n; +- const maxPriorityFeePerGas = 1n; +// remove-end + +// add-start ++ const pimlicoClient = createPimlicoClient({ ++ transport: http("https://api.pimlico.io/v2/59141/rpc"), // 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" +import { createPimlicoClient } from "permissionless/clients/pimlico"; +import { bundlerClient, smartAccount } from "./config.ts" // The config.ts is the same as in the previous example. + +const pimlicoClient = createPimlicoClient({ + transport: http("https://api.pimlico.io/v2/59141/rpc"), // 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 +``` diff --git a/gator_versioned_docs/version-0.11.0/index.md b/gator_versioned_docs/version-0.11.0/index.md new file mode 100644 index 00000000000..3cdab43dc69 --- /dev/null +++ b/gator_versioned_docs/version-0.11.0/index.md @@ -0,0 +1,63 @@ +--- +title: Introduction to the MetaMask Delegation Toolkit +sidebar_label: Introduction +description: High-level overview of the Delegation Toolkit, its benefits, and where to start in the documentation. +sidebar_position: 1 +--- + +import CardList from "@site/src/components/CardList" + +# MetaMask Delegation Toolkit documentation + +## Why use the toolkit? + +The MetaMask Delegation Toolkit enables developers to create frictionless new experiences based +on granular permission sharing and trust. +The toolkit offers a suite of contracts, libraries, and services designed for maximum composability, +allowing developers to build and extend their dapps with ease. +The toolkit enables: + +- **Instant user onboarding.** Provide frictionless onboarding with no browser extension, mobile + app, or seed phrase required. + +- **New web3 experiences.** Unlock new experiences such as peer-to-peer social + coordination using incentive trees, or recurring subscription payments that don't require users + to connect to the dapp. + +- **Uninterrupted user experiences.** Keep users immersed in the dapp by embedding the wallet + experience and reassigning gas costs to where they make sense. + +The toolkit includes the [Delegation Framework](concepts/delegation.md#delegation-framework) – a +pioneering set of open-source, customizable smart contracts, allowing dapps and protocols to +implement custom permission control. +Developers can use the Delegation Framework to prepare their dapps for +[delegations](concepts/delegation.md) created from +[delegator accounts](concepts/delegator-accounts.md). + +## Where do I start? + +Check out the following sections to get started with the MetaMask Delegation Toolkit: + + + +## Questions? + +If you have questions, email hellogators@consensys.net. diff --git a/gator_versioned_docs/version-0.9.0/changelog/0.9.0.md b/gator_versioned_docs/version-0.9.0/changelog/0.9.0.md new file mode 100644 index 00000000000..f7bf7345bdb --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/changelog/0.9.0.md @@ -0,0 +1,108 @@ +--- +sidebar_label: 0.9.0 +sidebar_position: 1 +description: MetaMask Delegation Toolkit v0.9.0 changelog +--- + +# What's new in v0.9.0? + +:::warning Breaking changes +The ⚠️ symbol denotes potentially breaking API changes. +As per the [semantic versioning specification](https://semver.org/#spec-item-4), from v1.0.0 onwards, +breaking changes will be released only in major version bumps. +::: + +## Breaking Changes + +### ⚠️ Package references + +- Moves the package from `@metamask-private` to `@metamask` organization. +- Renames the `delegator-core-viem` entrypoint to `delegation-toolkit`. + +```typescript +// remove-next-line +- import { toMetaMaskSmartAccount } from "@metamask-private/delegator-core-viem"; +// add-next-line ++ import { toMetaMaskSmartAccount } from "@metamask/delegation-toolkit"; +``` + +### ⚠️ SimpleFactory address + +Updates the `SimpleFactory` contract address. Please note, this will result in a change to the smart account address for `MetaMaskSmartAccount`. + +```typescript +// Delegation enviroment for 1.3.0 +export const deployment_1_3_0 = { +//... +// remove-next-line +- SimpleFactory: '0x6ff518884f21168c30c58CB21184D6AdBC18Ad90', +// add-next-line ++ SimpleFactory: '0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c', +//... +} + +``` + +## Enhancements + +### ⚠️ Package installation + +Package installation no longer requires an authentication token, as it has transitioned out of private alpha. 🎉 + +## Contract addresses + +The following are the contract addresses for the +[Delegation Framework version 1.3.0](https://github.com/MetaMask/delegation-framework/blob/v1.3.0/documents/Deployments.md), +as used by this version of the toolkit. + +### Delegation Framework + +| Contract | Address | +|----------|---------| +| EntryPoint | `0x0000000071727De22E5E9d8BAf0edAc6f37da032` | +| SimpleFactory | `0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c` | +| DelegationManager | `0x739309deED0Ae184E66a427ACa432aE1D91d022e` | +| MultiSigDeleGatorImpl | `0xB4ab520FF1761f7b6dc221fEaCaf79367629Ed12` | +| HybridDeleGatorImpl | `0xf4E57F579ad8169D0d4Da7AedF71AC3f83e8D2b4` | + +### Caveat enforcers + +| Enforcer | Address | +|----------|---------| +| AllowedCalldataEnforcer | `0x1021300501f6aDc446d4e506053F55a8a63cB1d7` | +| AllowedMethodsEnforcer | `0x371f95c92Be3A916B824A2aE086Ed6db7A6193Fb` | +| AllowedTargetsEnforcer | `0x91e043a13c61f9ddC02BDfe38dCA02A7F5b7Cc88` | +| ArgsEqualityCheckEnforcer | `0xACEC09a804020B307eFF00df9AAfb1Cf656DF9Cf` | +| BlockNumberEnforcer | `0x955C7732562c6Dc4760dF749440f3ab28F46F608` | +| DeployedEnforcer | `0xd2c8c04E2070c13CCB97FEAa25D1915676AAC191` | +| ERC20BalanceGteEnforcer | `0x262A37B51798c44F1BEAd1A076703E4488887b78` | +| ERC20TransferAmountEnforcer | `0x272E92835B12F014353E8754808C67682e9dddFA` | +| ERC721BalanceGteEnforcer | `0xd0960BC7324235DE4FAFe0f2eDCff64313220CC8` | +| ERC721TransferEnforcer | `0x82621E65240f67D8F60a920F709127743A8D20A9` | +| ERC1155BalanceGteEnforcer | `0x01a84C60B0B5c3EbB504fDa60a8236eB7e2D6655` | +| IdEnforcer | `0xd6403989C2cc145102c2AE76E70D1317947ef587` | +| LimitedCallsEnforcer | `0xA45dd3D90447640eB76085637132a74E18b310E3` | +| NonceEnforcer | `0x1ba53a54eDa7021E08065C1C1943bCE91e0FceA3` | +| TimestampEnforcer | `0xABc2591a40db08eD7045D91A29B3DBC33082DB54` | +| ValueLteEnforcer | `0x9C458b17Cd6570e322Ee9a4180b309dAFD08e24C` | +| NativeTokenTransferAmountEnforcer | `0xcfD1BD7922D123Caa194597BF7A0073899a284Df` | +| NativeBalanceGteEnforcer | `0xDb5BAF405159f47Ab70DD424021ef114A450E101` | +| NativeTokenPaymentEnforcer | `0x6e21bABB3779bc694DC3A4DCeB35C1ecC1d9087b` | +| OwnershipTransferEnforcer | `0x5f263261676d24Dd146545F22E485708900B2B83` | +| RedeemerEnforcer | `0x596CC67C2Da64ED51E27B6d61f46e3F687E9182d` | + +### Supported mainnet networks +- Ethereum +- Polygon +- Binance Smart Chain +- Optimism +- Arbitrum +- Linea +- Base +- Gnosis Chain + +### Supported testnet networks +- Ethereum Sepolia +- Linea Sepolia +- Base Sepolia +- MegaEth \ No newline at end of file diff --git a/gator_versioned_docs/version-0.9.0/changelog/_category_.json b/gator_versioned_docs/version-0.9.0/changelog/_category_.json new file mode 100644 index 00000000000..f33608bfdf1 --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/changelog/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Changelog" +} diff --git a/gator_versioned_docs/version-0.9.0/concepts/_category_.json b/gator_versioned_docs/version-0.9.0/concepts/_category_.json new file mode 100644 index 00000000000..a7d86ae06fa --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/concepts/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Concepts", + "position": 3, + "link": { + "type": "generated-index", + "slug": "/concepts", + "title": "Delegation Toolkit concepts" + } +} \ No newline at end of file diff --git a/gator_versioned_docs/version-0.9.0/concepts/caveat-enforcers.md b/gator_versioned_docs/version-0.9.0/concepts/caveat-enforcers.md new file mode 100644 index 00000000000..b394cb4870d --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/concepts/caveat-enforcers.md @@ -0,0 +1,185 @@ +--- +description: Learn about caveat enforcers and how they restrict delegations. +sidebar_position: 4 +--- + +# Caveat enforcers + +The MetaMask Delegation Toolkit 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](delegation.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; + +import { ModeCode } from "../utils/Types.sol"; + +/** + * 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`](../how-to/create-delegation/restrict-delegation.md) interface in the MetaMask Delegation Toolkit. + +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](../how-to/create-delegation/restrict-delegation.md#caveat-types) 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`](../how-to/create-delegation/restrict-delegation.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`](../how-to/create-delegation/restrict-delegation.md#nativetokenpayment) before + [`NativeBalanceGteEnforcer`](../how-to/create-delegation/restrict-delegation.md#nativebalancegte) 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 Delegation Toolkit provides [many out-of-the-box caveat enforcers](../how-to/create-delegation/restrict-delegation.md#caveat-types) +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 more complex scenarios, you can also [create custom caveat enforcers](../how-to/create-delegation/create-custom-caveat-enforcer.md) by implementing the `ICaveatEnforcer` interface. + +## Attenuating authority with redelegations + +When [creating chains of delegations](../how-to/create-delegation/index.md#create-a-redelegation), 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. diff --git a/gator_versioned_docs/version-0.9.0/concepts/delegation.md b/gator_versioned_docs/version-0.9.0/concepts/delegation.md new file mode 100644 index 00000000000..6c07b28bb2d --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/concepts/delegation.md @@ -0,0 +1,90 @@ +--- +description: Learn about delegation, the delegation lifecycle, and the Delegation Framework. +sidebar_position: 2 +--- + +# Delegation + +*Delegation* is the ability for a [delegator account](delegator-accounts.md) to grant permission to another smart contract account (SCA) +or externally owned account (EOA) to perform specific executions on the delegator's behalf, under defined rules and restrictions. + +The MetaMask Delegation Toolkit includes the following delegation features: + +- **Caveats** - 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. + +- **Chain of delegations** - Users can redelegate permissions that have been delegated to them, creating a chain of delegations across trusted parties. + +Delegations are created using the `Delegation` type, which is specified as follows: + +```typescript +export type Delegation = { + delegate: Hex; // The address to which the delegation is being granted. + delegator: Hex; // The address that is granting the delegation. + authority: Hex; // Hash of the parent delegation, or the constant ROOT_AUTHORITY. + caveats: Caveat[]; // Caveats that restrict the authority being granted. + salt: Hex; // Used to avoid hash collisions between identical delegations. + signature: Hex; // Signature from the delegator account. +}; +``` + +## Delegation lifecycle + +The delegation lifecycle is as follows: + +1. **Delegation creation** - A delegation is initialized, and the delegator account signs it. + +2. **Caveat enforcement** - The caveats applied to the delegation specify conditions under which + the delegation can be redeemed. + +3. **Delegation storage** - The delegation can be stored, enabling retrieval for future redemption. + + :::note + [Storing and retrieving delegations](../experimental/store-retrieve-delegations.md) using the toolkit's + `DelegationStorageClient` is an experimental feature. + ::: + +4. **Delegation redemption** - The delegate (the account being granted the permission) redeems the + delegation through an [ERC-4337 user operation](delegator-accounts.md#account-abstraction-erc-4337), + which verifies that the delegated authority is valid in order to perform the execution. + +See [how to create a delegation](../how-to/create-delegation/index.md) to get started with the +delegation lifecycle. + +## Delegation Framework + +The MetaMask Delegation Toolkit 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** - There are [multiple delegator account implementations](delegator-accounts.md#delegator-account-types), + with the main difference being the signature scheme 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 a delegation is redeemed, 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 `permissionContexts`, `modes`, and + `executionCallDatas` match, or throws `BatchDataLengthMismatch`. + 2. Decodes and validates the delegation, checking that the caller (`msg.sender`) is the delegate + and that there are no empty signatures, or throws `InvalidDelegate`. + 3. Verifies delegation signatures, ensuring validity using `ECDSA` (for EOAs) or + `isValidSignature` (for contracts), or throws `InvalidSignature`. + 4. Validates the delegation chain's authority and ensures delegations are not disabled. + 5. Executes the `beforeHook` for each `caveat` 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. diff --git a/gator_versioned_docs/version-0.9.0/concepts/delegator-accounts.md b/gator_versioned_docs/version-0.9.0/concepts/delegator-accounts.md new file mode 100644 index 00000000000..60448c2b7d6 --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/concepts/delegator-accounts.md @@ -0,0 +1,79 @@ +--- +description: Learn about account abstraction, the delegator account flow, and account types. +sidebar_position: 1 +--- + +# Delegator accounts + +The MetaMask Delegation Toolkit enables you to create and manage *delegator accounts*. +Delegator accounts are [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) smart contract accounts (SCAs) +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 delegator +accounts use smart contracts to govern account logic. + +## 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 SCAs containing arbitrary verification logic. +ERC-4337 enables SCAs 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 SCA 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 SCAs to facilitate gas abstraction. + +## Delegator account flow + +The MetaMask delegator account flow is as follows: + +1. **Account setup** - A user creates an SCA by deploying a smart contract, and initializing it with + ownership and security settings. + The user can customize the SCA 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 signatory. + +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 account types + +The MetaMask Delegation Toolkit supports two types of delegator accounts, each offering unique features and use cases. +See [Configure accounts and signers](../how-to/configure-delegator-accounts-signers.md) to learn how to use these different account types. + +### Hybrid Delegator + +The Hybrid Delegator is a flexible implementation that supports both an externally owned account (EOA) "owner" and any number of P256 (passkey) signers. +You can configure any of these signers as the signatory, and use them to sign on behalf of the delegator. + +This type is referenced in the toolkit as `Implementation.Hybrid`. + +### Multisig Delegator + +The Multisig Delegator is an implementation that supports multiple signers with a configurable threshold for valid signatures, allowing for enhanced security and flexibility in account management. The signatory must have at least as many signers include as the threshold is configured for the account. + +This type is referenced in the Toolkit as `Implementation.Multisig`. diff --git a/gator_versioned_docs/version-0.9.0/concepts/environment.md b/gator_versioned_docs/version-0.9.0/concepts/environment.md new file mode 100644 index 00000000000..54215893785 --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/concepts/environment.md @@ -0,0 +1,70 @@ +--- +description: Learn about the delegator environment object and how to use it. +sidebar_position: 3 +--- + +# Delegator environment + +The delegator environment object, `DeleGatorEnvironment`, is a component of the MetaMask Delegation Toolkit that defines the contract addresses +necessary for interacting with the [Delegation Framework](delegation.md#delegation-framework) on a specific network. + +The delegator environment 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. + +## Resolving the environment for a `DeleGatorClient` + +When you create a `DeleGatorClient`, the Delegation Toolkit automatically resolves the environment based on the version it requires, and the `chainId` configured in the `DeleGatorClient`. If no environment is found, an error is thrown. + +## Deploying and using a custom delegator environment + +You can deploy the contracts using any method, but the toolkit provides a `deployDeleGatorEnvironment` function for convenience. +This function requires a Viem [Public Client](https://viem.sh/docs/clients/public.html), [Wallet Client](https://viem.sh/docs/clients/wallet.html), and [Chain](https://viem.sh/docs/glossary/types#chain), +and resolves to the `DeleGatorEnvironment` object: + +```typescript +const environment = await deployDeleGatorEnvironment(walletClient, publicClient, chain); +``` + +You can also override any contracts when calling `deployDeleGatorEnvironment` (for example, if you have already deployed the `EntryPoint` contract to your local Anvil node): + +```typescript +const environment = await deployDeleGatorEnvironment(walletClient, publicClient, chain, { + EntryPoint: "0x0000000071727De22E5E9d8BAf0edAc6f37da032" +}); +``` + +You can now use this `environment` object when creating a `DeleGatorClient`: + +```typescript +const client = createDeleGatorClient({ + transport, + chain, + account, + environment +}); +``` + +## Overriding a configured environment + +To simplify code when interacting with a `DeleGatorClient`, and to enable using the same code for production and development environments, +you can override the `DeleGatorEnvironment` that is resolved internally for a given chain ID and contract version: + +```typescript +const environment = await deployDeleGatorEnvironment(walletClient, publicClient, chain); + +overrideDeployedEnvironment( + chainId, + "1.1.0", + environment, +); +``` + +With this code, your environment object is used when you create a `DeleGatorClient` with the specified `chainId`. + +:::note +Make sure to specify the Framework version required by the toolkit. +See the [changelog](../changelog/0.9.0.md) of the toolkit version you are using for its required Framework version. +::: diff --git a/gator_versioned_docs/version-0.9.0/experimental/_category_.json b/gator_versioned_docs/version-0.9.0/experimental/_category_.json new file mode 100644 index 00000000000..5661f467b80 --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/experimental/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Experimental", + "position": 5, + "link": { + "type": "generated-index", + "slug": "/experimental", + "title": "Delegation Toolkit experimental features" + } +} diff --git a/gator_versioned_docs/version-0.9.0/experimental/erc-7710-redeem-delegations.md b/gator_versioned_docs/version-0.9.0/experimental/erc-7710-redeem-delegations.md new file mode 100644 index 00000000000..85325565000 --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/experimental/erc-7710-redeem-delegations.md @@ -0,0 +1,237 @@ +--- +description: Learn how to redeem ERC-7710 delegations with a smart contract account or an externally owned account (EOA). +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# ERC-7710: Redeem delegations + +:::caution Experimental +This is an experimental feature and may change in future releases. +::: + +[ERC-7710](https://eip.tools/eip/7710) introduces a standard way for smart contract accounts (SCAs) to delegate capabilities to other +SCAs or externally owned accounts (EOAs). + +The MetaMask Delegation Toolkit provides two experimental functions, `erc7710BundlerActions()` and `erc7710WalletActions()`, that let +a caller redeem delegations granted by MetaMask's permissions system. + +## Extract relevant data + +Refer to [ERC-7715: Request permissions](erc-7715-request-permissions.md) for information on how to request user permissions. +Once the permission has been granted, extract the relevant data from the response. +For example: + +```typescript +// Response received from the ERC-7715 wallet_grantPermissions request. +const permissionsResponse = [{ + chainId: "0xe715", + account: "0xD6f56C2B10b1e02D841E4a97c60Afe914E884DBd", + expiry: 1234567890, + permission: { + type: "native-token-stream", + data: { + amountPerSecond: "0x1", + maxAmount: "0x2", + initialAmount: undefined, + startTime: 2, + }, + }, + context: "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d" + signer: { + type: "account", + data: { + account: "0x07bfc7230D5BD2544059816D88A895BB000Abe00" + } + }, + signerMeta: { + delegationManager: "0xDC7e12b41E5e61BfCc7F56AAFB7B93288F61e841" + }, + accountMetadata: [{ + factory: "0x65E726b404149fE37F4b291c81Dc6eddd44763A7", + factoryData: "0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b" + }] +}]; + +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +// accountMeta is only present when the smart contract account is not deployed. +const accountMetadata = permissionsResponse[0].accountMeta; +``` + +This data encodes the authority that lets the delegate redeem the permission. + +### Security considerations for `accountMeta` + +When a user grants a permission, they can provide `accountMeta` which is an array of `factory` and `factoryData` values. +These calls must be executed before redeeming the permission (this is handled for you in `sendUserOperationWithDelegation`). + +Because each `accountMeta` is an arbitrary call specified by the granter, it is important that these are executed carefully. +We recommend taking the following precautions: + +- **Only grant permissions to session accounts** - When requesting permissions, use an account that is only used for that single purpose, and does not contain tokens. +This way, any `accountMeta` executed can't perform any damaging actions. + +- **Only execute `accountMeta` against trusted factory addresses** - Ensure that only `accountMeta` targeting a known factory address is executed. +The bundler action `sendUserOperationWithDelegation` only executes `accountMeta` that targets the `SimpleFactory` address for the current Delegation Framework. +If you redeem delegations in any other way, it is your responsibility to validate trusted factory addresses. + +## Redeem the permission + +Redeem a delegation using one of two methods. Choose the method based on your account type: + +- If redeeming with an SCA, call `sendUserOperationWithDelegation`. +- If redeeming with an EOA, call `sendTransactionWithDelegation`. + +### Redeem with an SCA + +To redeem a delegation with a smart contract account, create a [`MetaMaskSmartAccount`](../how-to/create-delegator-account.md#create-a-metamasksmartaccount) +and a [Viem Bundler Client](https://viem.sh/account-abstraction/clients/bundler). + +After setting up your Bundler Client, you can extend its functionality with `erc7710BundlerActions` actions to support ERC-7710. Once extended, use `sendUserOperationWithDelegation` to redeem the permission. + + + + +```typescript +import { sessionAccount, bundlerClient, publicClient } from "./config.ts"; + +// These properties must be extracted from the permission response. +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +const accountMetadata = permissionsResponse[0].accountMeta; + +// 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 + accountMetadata, +}); +``` + + + + +```typescript +import { createPublicClient, http, createBundlerClient } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { erc7710BundlerActions } from "@metamask/delegation-toolkit/experimental"; +import { toMetaMaskSmartAccount, Implementation } from "@metamask/delegation-toolkit"; + +export const publicClient = createPublicClient({ + chain: chain, + transport: http(), +}); + +// Your session account for requesting and redeeming should be same. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + + +export const sessionAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { 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()); +``` + + + +:::note +`sendUserOperationWithDelegation` is similar to the `sendUserOperation` function, but does not accept `callData` directly. +::: + +### Redeem with an EOA + +To redeem a delegation with an EOA, create a [Viem Wallet Client](https://viem.sh/docs/clients/wallet). + +After creating your Wallet Client, you can extend its functionality with `erc7710WalletActions` actions to support ERC-7710. Once extended, use `sendTransactionWithDelegation` to redeem the permission. + + + + +```typescript +import { walletClient, publicClient } from "./config.ts"; + +// These properties must be extracted from the permission response. +const permissionsContext = permissionsResponse[0].context; +const delegationManager = permissionsResponse[0].signerMeta.delegationManager; +const accountMetadata = permissionsResponse[0].accountMeta; + +if (accountMetadata?.length !== 0) { + // If the granted permission contains accountMetadata, this must be executed before attempting to + // redeem the delegation. + + // This transaction will deploy the delegator account. + const hash = walletClient.sendTransaction({ + to: accountMetadata.factory, + data: accountMetadata.factoryData, + }); + + // You should wait for transaction to be successfully executed. + // You can use the TransactionReceipt.status to verify the state. + await publicClient.waitForTransactionReceipt( { hash }); +} + +const hash = walletClient.sendTransactionWithDelegation({ + chain, + to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + value: 1n, + permissionsContext, + delegationManager +}); +``` + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { erc7710WalletActions } from "@metamask/delegation-toolkit/experimental"; + +export const publicClient = createPublicClient({ + chain, + transport: http() +}); + +// Your session account for requesting and redeeming should be same. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + +const walletClient = createWalletClient({ + account, + transport: http(), + chain, +}).extend(erc7710WalletActions()); +``` + + diff --git a/gator_versioned_docs/version-0.9.0/experimental/erc-7715-request-permissions.md b/gator_versioned_docs/version-0.9.0/experimental/erc-7715-request-permissions.md new file mode 100644 index 00000000000..8d605b4a76a --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/experimental/erc-7715-request-permissions.md @@ -0,0 +1,182 @@ +--- +description: Learn how to request ERC-7715 permissions. +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# ERC-7715: Request permissions + +:::caution Experimental +This is an experimental feature. +It requires MetaMask Flask 12.14.2 or later, and may change in future releases. +::: + +[ERC-7715](https://eip.tools/eip/7715) introduces a standard way for dapps to request permissions from a wallet to execute +transactions on a user's behalf. + +The MetaMask Delegation Toolkit provides the experimental actions for ERC-7715 that lets a caller request permissions from MetaMask's permissions system. + +## Request permissions + +To request permissions, extend your [Viem Wallet Client](https://viem.sh/docs/clients/wallet) with `erc7715ProviderActions` actions. +You'll need a session account to request the permission, which can be either an externally owned account (EOA) or a smart contract account (SCA). +This example uses an SCA: + + + + +```typescript +import { sepolia as chain } from "viem/chains"; +import { sessionAccount, walletClient } from "./config.ts"; + +const expiry = Math.floor(Date.now() / 1000 + 604_800); // 1 week from now. +const currentTime = Math.floor(Date.now() / 1000); // now + +const grantedPermissions = await walletClient.grantPermissions([{ + chainId: chain.id, + expiry, + signer: { + type: "account", + data: { + address: sessionAccount.address, + }, + }, + permission: { + type: "native-token-stream", + data: { + initialAmount: 1n, // 1 wei + amountPerSecond: 1n, // 1 wei per second + maxAmount: 10n, // 10 wei + startTime: currentTime, + justification: "Payment for a week long subscription", + }, + }, +}]); +``` + + + + + +```typescript +import { createWalletClient, custom, createPublicClient, http } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; +import { sepolia as chain } from "viem/chains"; +import { erc7715ProviderActions } from "@metamask/delegation-toolkit/experimental"; +import { toMetaMaskSmartAccount, Implementation } from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain: chain, + transport: http(), +}); + +// The private key of the session owner. +const privateKey = "0x..."; +const account = privateKeyToAccount(privateKey); + +export const sessionAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); + +export const walletClient = createWalletClient({ + transport: custom(window.ethereum), +}).extend(erc7715ProviderActions()); +``` + + + +
+ ERC-7715 request permission sample + + Here's what your ERC-7715 native token streaming allowance request looks like: + + ```ts + [{ + chainId: "0xaa36a7", + expiry: 1745041429, + permission: { + type: "native-token-stream", + data: { + amountPerSecond: "0x1", + maxAmount: "0x1", + initialAmount: "0xa", + startTime: 1744955029, + justification: "Payment for a week long subscription", + }, + }, + signer: { + type: "account", + data: { + address: "0x1234...", + }, + }, + }] + ``` + + Learn more about the [ERC-7715 permission schema](https://eip.tools/eip/7715). +
+ + +Additionally, you can allow users to modify the requested permission by setting the `isAdjustmentAllowed` parameter to `true` in the request. + +```typescript +import { sepolia as chain } from "viem/chains"; +// The config.ts is the same as in the previous example. +import { sessionAccount, walletClient } from "./config.ts"; + +const expiry = Math.floor(Date.now() / 1000 + 604_800); // 1 week from now. +const currentTime = Math.floor(Date.now() / 1000); // now + +const grantedPermissions = await walletClient.grantPermissions([{ + chainId: chain.id, + expiry, + signer: { + type: "account", + data: { + address: sessionAccount.address, + }, + }, + permission: { + type: "native-token-stream", + // add-next-line ++ isAdjustmentAllowed: true, + data: { + initialAmount: 1n, // 1 wei + amountPerSecond: 1n, // 1 wei per second + maxAmount: 10n, // 10 wei + startTime: currentTime, + justification: "Payment for a week long subscription", + }, + }, +}]); +``` + +:::note +Users have full control over the permissions they grant—depending on the permission you request, they may choose to grant more limited permissions than requested. +You should always verify the granted permissions and adjust your dapp's behavior accordingly. +::: + +## Security considerations for `accountMeta` + +When a user grants a permission, they can provide [`accountMeta`](erc-7710-redeem-delegations.md#extract-relevant-data) which is an array of `factory` and `factoryData` values. +These calls must be executed before redeeming the permission (this is handled for you in `sendUserOperationWithDelegation`). + +Because each `accountMeta` is an arbitrary call specified by the granter, it is important that these are executed carefully. +We recommend taking the following precautions: + +- **Only grant permissions to session accounts** - When requesting permissions, use an account that is only used for that single purpose, and does not contain tokens. + This way, any `accountMeta` executed can't perform any damaging actions. + +- **Only execute `accountMeta` against trusted factory addresses** - Ensure that only `accountMeta` targeting a known factory address is executed. + The bundler action `sendUserOperationWithDelegation` only executes `accountMeta` that targets the `SimpleFactory` address for the current Delegation Framework. + If you redeem delegations in any other way, it is your responsibility to validate trusted factory addresses. + +## Next steps + +You can redeem the granted permission using the experimental [ERC-7710 `erc7710WalletActions()`](erc-7710-redeem-delegations.md). diff --git a/gator_versioned_docs/version-0.9.0/experimental/store-retrieve-delegations.md b/gator_versioned_docs/version-0.9.0/experimental/store-retrieve-delegations.md new file mode 100644 index 00000000000..3b6cde29fd6 --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/experimental/store-retrieve-delegations.md @@ -0,0 +1,172 @@ +--- +description: Store and retrieve delegations using the `DelegationStorageClient`. +sidebar_position: 1 +toc_max_heading_level: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Store and retrieve delegations + +:::caution Experimental +This is an experimental feature and may change in future releases. +::: + +You can use methods provided by the `DelegationStorageClient` of the MetaMask Delegation Toolkit to store and retrieve +[delegations](../concepts/delegation.md). + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](../how-to/configure.md) +- Ensure you have an API key and API key ID to interact with the `DelegationStorageClient`. + If you need to gain access, email hellogators@consensys.net. + +## Configure the storage client + +Create the `DelegationStorageClient` instance, and configure it using your API key and API key ID. + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + +## Store a delegation + +To store a delegation, use the `storeDelegation` method of the `DelegationStorageClient`. This method takes one parameter: + +1. `delegation` - A `Delegation` object representing the delegation to be stored. + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; + +const delegationHash = await delegationStorageClient.storeDelegation(delegation); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + + +## Retrieve a delegation chain + +To retrieve a delegation chain, use the `getDelegationChain` method of the `DelegationStorageClient`. This method takes one parameter: + +1. `leafDelegationOrDelegationHash` - Either a `Delegation` object or the delegation hash as a hex string. + +:::note +A delegation can be a root delegation, where its `authority` is `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`. It can also be a child of another delegation, where its `authority` is the hash of its parent delegation. This method returns the delegation referenced by `leafDelegationOrDelegationHash` and any ancestors. +::: + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; +import { getDelegationHashOffchain } from "@metamask/delegation-toolkit"; + +// Assuming you have the leaf delegation +const delegationHash = getDelegationHashOffchain(leafDelegation); + +const delegationChain: Delegation[] = await delegationStorageClient.getDelegationChain( + delegationHash +); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + + +## Retrieve delegations for a specific account + +To retrieve delegations stored for a specific account, use the `fetchDelegations` method of the `DelegationStorageClient`. This method allows you to fetch delegations where the specified account is either the delegator or the delegate. +It takes two parameters: + +1. `account` - The address of the account for which you want to retrieve delegations. +2. `filter` - The nature of the delegations. Possible values are: + - `DelegationStoreFilter.Given` - For delegations where the specified `account` is the `delegator`. + - `DelegationStoreFilter.Received` - For delegations where the specified `account` is the `delegate`. + +### Example + + + + +```typescript +import { delegationStorageClient } from "./config.ts"; + +const address = "0x027aeAFF3E5C33c4018FDD302c20a1B83aDCD96C" + +// Fetch the delegations given by address. +const grantedDelegations = await delegationStorageClient.fetchDelegations( + address, + DelegationStoreFilter.Given, +); + +// Fetch the delegations received by the address. +const receivedDelegations = await delegationStore.fetchDelegations( + address, + DelegationStoreFilter.Received, +); +``` + + + + +```typescript +import { + DelegationStorageClient, + DelegationStorageEnvironment +} from "@metamask/delegation-toolkit/experimental"; + +export const delegationStorageClient = new DelegationStorageClient({ + apiKey: "", + apiKeyId: "", + environment: DelegationStorageEnvironment.prod +}); +``` + + diff --git a/gator_versioned_docs/version-0.9.0/get-started/_category_.json b/gator_versioned_docs/version-0.9.0/get-started/_category_.json new file mode 100644 index 00000000000..4a23260d67d --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/get-started/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "Get started", + "position": 2, + "link": { + "type": "generated-index", + "slug": "/get-started", + "title": "Get started with the Delegation Toolkit" + } +} diff --git a/gator_versioned_docs/version-0.9.0/get-started/install.md b/gator_versioned_docs/version-0.9.0/get-started/install.md new file mode 100644 index 00000000000..b371b10fbd0 --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/get-started/install.md @@ -0,0 +1,48 @@ +--- +sidebar_label: Install and set up +description: Learn how to install and set up the MetaMask Delegation Toolkit. +sidebar_position: 1 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Install and set up the Delegation Toolkit + +This page provides instructions to install and set up the MetaMask Delegation Toolkit. + +## 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](../how-to/create-delegation/create-custom-caveat-enforcer.md)), install + [Foundry](https://book.getfoundry.sh/getting-started/installation). + +## Steps + +### 1. Install the toolkit + +Install the MetaMask Delegation Toolkit dependencies: + +```bash npm2yarn +npm install @metamask/delegation-toolkit +``` + +### 2. (Optional) Install the contracts + +If you plan to extend the Delegation Framework smart contracts (for example, to +[create a custom caveat enforcer](../how-to/create-delegation/create-custom-caveat-enforcer.md)), 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 MetaMask Delegation Toolkit. +Check out the [Delegation Toolkit quickstart](quickstart.md) to walk through a simple example. diff --git a/gator_versioned_docs/version-0.9.0/get-started/quickstart.md b/gator_versioned_docs/version-0.9.0/get-started/quickstart.md new file mode 100644 index 00000000000..41b7d5462cc --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/get-started/quickstart.md @@ -0,0 +1,173 @@ +--- +description: Get started quickly with the MetaMask Delegation Toolkit. +sidebar_position: 2 +sidebar_label: Quickstart +--- + +# Delegation Toolkit quickstart + +This page demonstrates how to get started quickly with the MetaMask Delegation Toolkit, +by creating a delegator account and completing the delegation lifecycle (creating, signing, and redeeming a delegation). + +## Prerequisites + +[Install and set up the Delegation Toolkit.](install.md) + +## Steps + +### 1. 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 delegator account query the signer's account state and interact with smart contracts. + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const publicClient = createPublicClient({ + chain, + transport: http(), +}); +``` + +### 2. 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 +import { createBundlerClient } from "viem/account-abstraction"; + +const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http("https://your-bundler-rpc.com"), +}); +``` + +### 3. Create a delegator account + +[Create a delegator account](../how-to/create-delegator-account.md) to set up a delegation. +The delegator must be a smart account. + +This example configures a [Hybrid Delegator](../how-to/configure-delegator-accounts-signers.md#configure-a-hybrid-delegator): + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; + +const delegatorAccount = privateKeyToAccount("0x..."); + +const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegatorAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegatorAccount }, +}); +``` + +### 4. Create a delegate account + +Create a delegate account to receive the delegation. +The delegate can be either a smart contract account (SCA) or an externally owned account (EOA). + +This example uses an SCA: + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; + +const delegateAccount = privateKeyToAccount("0x..."); + +const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + +### 5. Create a delegation + +[Create a root delegation](../how-to/create-delegation/index.md#create-a-root-delegation) from the +delegator account to the delegate account. + +This example passes an empty `caveats` array, which means the delegate can perform any action on the delegator's behalf. +We recommend [restricting the delegation](../how-to/create-delegation/restrict-delegation.md) by adding caveat enforcers. + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +const delegation = createDelegation({ + to: delegateSmartAccount.address, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + +### 6. Sign the delegation + +[Sign the delegation](../how-to/create-delegation/index.md#sign-a-delegation) using the `signDelegation` method from `MetaMaskSmartAccount`. +Alternatively, you can use the Delegation Toolkit's `signDelegation` utility. +The signed delegation will be used later to perform actions on behalf of the delegator. + +```typescript +const signature = await delegatorSmartAccount.signDelegation({ + delegation +}); + +const signedDelegation = { + ...delegation, + signature, +}; +``` + +### 7. Redeem the delegation + +The delegate account can now [redeem the delegation](../how-to/redeem-delegation.md). +The redeem transaction is sent to the `DelegationManager` contract, which validates the delegation and +executes actions on the delegator's behalf. + +To prepare the call data for the redeem transaction, use the `redeemDelegation` utility function from the Delegation Toolkit. + +```typescript +import { + createExecution, + DelegationFramework, + SINGLE_DEFAULT_MODE, +} from "@metamask/delegation-toolkit"; +import { zeroAddress } from "viem"; + +const delegations = [ signedDelegation ]; + +const executions = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ SINGLE_DEFAULT_MODE ], + executions: [ executions ] +}); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: delegateSmartAccount.address, + data: redeemDelegationCalldata + } + ], + maxFeePerGas: 1n, + maxPriorityFeePerGas: 1n, +}); +``` diff --git a/gator_versioned_docs/version-0.9.0/how-to/_category_.json b/gator_versioned_docs/version-0.9.0/how-to/_category_.json new file mode 100644 index 00000000000..2b6df81f83d --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/how-to/_category_.json @@ -0,0 +1,9 @@ +{ + "label": "How to", + "position": 4, + "link": { + "type": "generated-index", + "slug": "/how-to", + "title": "Delegation Toolkit how-to guides" + } +} diff --git a/gator_versioned_docs/version-0.9.0/how-to/configure-delegator-accounts-signers.md b/gator_versioned_docs/version-0.9.0/how-to/configure-delegator-accounts-signers.md new file mode 100644 index 00000000000..c25833669be --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/how-to/configure-delegator-accounts-signers.md @@ -0,0 +1,303 @@ +--- +sidebar_label: Configure accounts and signers +description: Learn how to configure different types of delegator accounts and signers using Viem. +sidebar_position: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Configure delegator accounts and signers + +The MetaMask Delegation Toolkit supports different [delegator account types](../concepts/delegator-accounts.md#delegator-account-types), +each with its own configuration and support for different signing mechanisms. +You can create flexible and secure delegator accounts tailored to your specific needs. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator account.](create-delegator-account.md) + +## Configure a Hybrid Delegator + +The [Hybrid Delegator](../concepts/delegator-accounts.md#hybrid-delegator) supports both an EOA "owner" and any number of P256 (passkey) signers. + +To configure a Hybrid Delegator, provide the following parameters: + +- `owner`: The owner's account address as a hex string. + The owner can be the zero address, indicating that there is no owner configured. +- `p256KeyIds`: An array of key identifiers for P256 signers as hex strings. +- `p256XValues`: An array of public key x-values for P256 signers as `bigint`s. +- `p256YValues`: An array of public key y-values for P256 signers as `bigint`s. +- `signatory`: A signer that will sign on behalf of the delegator account. + +:::note +You can set all `p256` parameters to empty arrays to configure no WebAuthn signer. +However, we recommend configuring at least one signer for account recoverability. +::: + +For a Hybrid Delegator, you can configure the following types of signatories: + +### Configure an account signatory + +This example creates a signatory from a private key using Viem's [`privateKeyToAccount`](https://viem.sh/docs/accounts/local/privateKeyToAccount) function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const signatory = { account }; +``` + + + + +### Configure a Wallet Client signatory + +This example creates a [Viem Wallet Client](https://viem.sh/docs/clients/wallet) as the signatory, +using Viem's `createWalletClient` function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient } from "viem"; + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +const walletClient = createWalletClient({ + account, + chain, + transport: http() +}) + +export const signatory = { walletClient }; +``` + + + + +### Configure a WebAuthn (passkey) signatory + +This example creates a [Viem WebAuthn Account](https://viem.sh/account-abstraction/accounts/webauthn) as the signatory, +using Viem's `toWebAuthnAccount` function. + + + + +```typescript +import { publicClient } from "./client.ts" +import { signatory } from "./signatory.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, p256KeyIds, p256XValues, p256YValues], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + + +```typescript +import { createCredential, parsePublicKey } from "webauthn-p256"; +import { toWebAuthnAccount } from "viem/account-abstraction"; +import { toHex } from "viem"; + +const credential = await createCredential({ name: "Your Delegator Passkey" }); +const webAuthnAccount = toWebAuthnAccount({ credential }); +const keyId = toHex("my-key-id"); + +const signatory = { webAuthnAccount, keyId }; +``` + + + + + +## Configure a Multisig Delegator + +The [Multisig Delegator](../concepts/delegator-accounts.md#multisig-delegator) supports multiple EOA signers with a configurable threshold for execution. + +To configure a Multisig Delegator, provide the following parameters: + +- `signers`: An array of EOA signer addresses as hex strings. +- `threshold`: The number of signers required to execute a transaction, as a `bigint`. +- `signatory`: An array of signatories that will sign on behalf of the delegator account. + +### Configure signatories + +For a Multisig Delegator, you can use a combination of account signatories and Wallet Client signatories. +For example: + + + + +```typescript +import { publiClient } from "./client.ts"; +import { account, walletClient } from "./signers.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const signers = [ account.address, walletClient.address ]; +const signatory = { account, walletClient }; +const threshold = 2n + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.MultiSig, + deployParams: [signers, threshold], + deploySalt: "0x", + signatory, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); +``` + + + + +```typescript +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { http, createWalletClient } from "viem"; + +// 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 walletClientPivatekey = generatePrivateKey(); +const walletClientAccount = privateKeyToAccount(walletClientPivatekey); + +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. +::: diff --git a/gator_versioned_docs/version-0.9.0/how-to/configure.md b/gator_versioned_docs/version-0.9.0/how-to/configure.md new file mode 100644 index 00000000000..e85115c1408 --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/how-to/configure.md @@ -0,0 +1,60 @@ +--- +description: Learn how to configure the MetaMask Delegation Toolkit using Viem. +sidebar_position: 1 +sidebar_label: Configure the toolkit +--- + +# Configure the Delegation Toolkit + +The MetaMask Delegation Toolkit enables you to easily integrate delegator accounts into your dapp, enabling a more flexible, secure, and frictionless experience for your users. + +The toolkit is highly configurable, allowing you to tailor it to your project's specific needs. It includes support for custom signers, multiple signatory schemes, custom paymasters and bundlers, and more. + +:::note +The MetaMask Delegation Toolkit provides custom middleware for [Pimlico's](https://docs.pimlico.io/) gas fee resolver, paymaster, and bundler. Additional options will be made available soon. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit](../get-started/install.md). +- Optionally, complete the [Delegation Toolkit quickstart](../get-started/quickstart.md) to + familiarize yourself with the toolkit's capabilities. + +## Viem's Account Abstraction API + +The toolkit uses Viem's Account Abstraction API. This provides a robust and flexible foundation for creating and managing smart contract accounts. +See Viem's [Smart Account documentation](https://viem.sh/account-abstraction/accounts/smart) for more information on the API's features, methods, and best practices. + + +## Configure Viem bundler and paymaster clients + +To use the bundler and paymaster clients with the toolkit, create instances of these clients and configure them as follows: + +```typescript +import { + createPaymasterClient, + createBundlerClient, +} from "viem/account-abstraction"; +import { http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; + +// 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, +}); +``` + +:::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 sufficient funds to pay for gas fees directly. +::: + diff --git a/gator_versioned_docs/version-0.9.0/how-to/create-delegation/create-custom-caveat-enforcer.md b/gator_versioned_docs/version-0.9.0/how-to/create-delegation/create-custom-caveat-enforcer.md new file mode 100644 index 00000000000..77416e901f5 --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/how-to/create-delegation/create-custom-caveat-enforcer.md @@ -0,0 +1,149 @@ +--- +description: Learn how to create, deploy, and apply a custom caveat enforcer +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a custom caveat enforcer + +When [restricting a delegation](restrict-delegation.md), the MetaMask Delegation Toolkit provides some [out-of-the-box caveat enforcers](restrict-delegation.md#caveat-types) +that cover common use cases. +For more granular or custom control, you can follow the instructions on this page to create custom caveat enforcers from scratch. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../get-started/install.md) +- [Configure the Delegation Toolkit.](../configure.md) + +## Steps + +### 1. Create the caveat enforcer + +Create a contract that extends the +[`ICaveatEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/interfaces/ICaveatEnforcer.sol) +interface. + +For example, the following is a simple caveat enforcer that only allows a delegation to be redeemed after a specific timestamp. + +```solidity title="AfterTimestampEnforcer.sol" +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import { CaveatEnforcer } from "@delegator/src/enforcers/CaveatEnforcer.sol"; +import { ModeCode } from "../utils/Types.sol"; + +contract AfterTimestampEnforcer is CaveatEnforcer { + /** + * @notice The delegation may only be redeemed after the specified timestamp - validAfter in seconds. + * @param _terms The validAfter, timestamp in seconds after which the delegation may be redeemed. + * @param _delegationHash - The hash of the delegation being operated on. + */ + function beforeHook( + bytes calldata _terms, + bytes calldata, + ModeCode, + bytes calldata, + bytes32 _delegationHash, + address, + address _redeemer + ) + public + override + { + // Enforces the conditions that should hold before a transaction is performed. + // This function MUST revert if the conditions are not met. + // Get the current timestamp + uint256 timestamp = block.timestamp; + + uint256 validAfter = uint256(bytes32(_terms)); + + require(timestamp > validAfter, "AfterTimestampEnforcer:cannot-redeem-too-early"); + } +} +``` + +### 2. Deploy the caveat enforcer + +Deploy your custom caveat enforcer to obtain its contract address. +For example, you can [deploy your smart contract using Forge](https://book.getfoundry.sh/forge/deploying). + +### 3. Apply the caveat enforcer + +When creating a delegation, add the `Caveat` for the custom caveat to the `CaveatBuilder`. +Learn more about [applying caveats to a delegation](restrict-delegation.md). + +The following example uses the custom `AfterTimestampEnforcer.sol` caveat enforcer to create a delegation granting +an allowance of 1,000,000 wei that can only be spent after one hour from when the delegation is created. + +:::warning Important +Depending on the use case, it may be necessary to add additional caveats to restrict the delegation further. +::: + + + + +```typescript +import { + createCaveatBuilder, + createDelegation, +} from "@metamask/delegation-toolkit"; +import { toHex } from "viem"; +import { delegatorSmartAccount } from "./config.ts"; + +const environment = delegatorSmartAccount.enviroment; + +// Replace this with the address where the AfterTimestampEnforcer.sol contract is deployed. +const afterTimestampEnforcer = "0x22Ae4c4919C3aB4B5FC309713Bf707569B74876F"; + +const caveatBuilder = createCaveatBuilder(environment); + +const tenAM = 10 * 60 * 60; // 10:00 AM as seconds since midnight. + +const caveats = caveatBuilder + .addCaveat("nativeTokenTransferAmount", 1_000_000) + .addCaveat({ + enforcer: afterTimestampEnforcer, + terms: toHex(tenAm) + }); + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats +}); +``` + + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + diff --git a/gator_versioned_docs/version-0.9.0/how-to/create-delegation/index.md b/gator_versioned_docs/version-0.9.0/how-to/create-delegation/index.md new file mode 100644 index 00000000000..1f5094ee9af --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/how-to/create-delegation/index.md @@ -0,0 +1,349 @@ +--- +description: Learn how to create different types of delegations, and how to sign a delegation. +sidebar_position: 6 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a delegation + +The MetaMask Delegation Toolkit enables you to create [delegations](../../concepts/delegation.md) +from a delegator account to a delegate account. + +:::note +Delegations are compatible with [ERC-7710](https://eip.tools/eip/7710) and [ERC-7715](https://ethereum-magicians.org/t/erc-7715-grant-permissions-from-wallets/20100), to support a standardized minimal interface. +[Requesting ERC-7715 permissions](../../experimental/erc-7715-request-permissions.md) and [redeeming ERC-7710 delegations](../../experimental/erc-7710-redeem-delegations.md) +are experimental features. +::: + +:::warning +The examples on this page demonstrate delegations without any restrictions. +Unrestricted delegations grant complete control over the account to the delegate, which can pose significant security risks. +It is crucial to add caveats to limit the delegated authority. +Learn how to [restrict a delegation](./restrict-delegation.md) using caveat enforcers. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../get-started/install.md) +- [Configure the Delegation Toolkit.](../configure.md) +- [Create a delegator account.](../create-delegator-account.md) + +## Create a root delegation + +A *root delegation* is a delegation that doesn't derive its authority from another delegation. +It is when a delegator delegates its own authority away, as opposed to a [redelegation](#create-a-redelegation). +Create a root delegation as follows: + + + + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.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: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create an 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. +You must create open root delegations carefully, to ensure that they are not misused. +Create an open root delegation by setting the delegate property to the special address +`0x0000000000000000000000000000000000000a11` (available via the constant `ANY_BENEFICIARY`). + + + + +```typescript +import { createOpenDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +const openRootDelegation = createOpenDelegation({ + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create a redelegation + +A recipient of a delegation (the delegate), can *redelegate* that authority to a third party, potentially applying additional [restrictions](restrict-delegation.md). +Create a redelegation as follows: + + + + +```typescript +import { + createDelegation, + getDelegationHashOffchain + } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +// The address is used as the root delegator. While creating the leaf delegation, +// the root delegate address will be the delegator address. +const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +// The authority is the (typed data) hash of the delegation from which the authority is derived. +const parentDelegationHash = getDelegationHashOffchain(delegation); + +// The address is used as the delegate address while creating the redelegation. +const leafDelegate = "0xb4821Ab7d5942Bd2533387592068a12608B4a52C" + +const leafDelegation = createDelegation({ + to: leafDelegate, + from: delegate, + // You can also choose to pass the parent delegation object, and let function + // handle the rest. + parentDelegation: parentDelegationHash, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Create an open redelegation + +An *open redelegation* is a [redelegation](#create-a-redelegation) that doesn't specify a delegate. +This means that any account can redeem the redelegation. +As with [open root delegations](#create-an-open-root-delegation), you must create open redelegations carefully, +to ensure that they are not misused. +Create an open redelegation as follows: + + + + +```typescript +import { + createDelegation, + createOpenDelegation, + getDelegationHashOffchain + } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.ts"; + +// The address is used as the root delegator. While creating the leaf delegation, +// the root delegate address will be the delegator address. +const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; + +const delegation = createDelegation({ + to: delegate, + from: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +// The authority is the (typed data) hash of the delegation from which the authority is derived. +const parentDelegationHash = getDelegationHashOffchain(delegation); + +const leafDelegation = createOpenDelegation({ + from: delegate, + // You can also choose to pass the parent delegation object, and let the function + // handle the rest. + parentDelegation: parentDelegationHash, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); +``` + + + + +## Sign a delegation + +A delegation must be signed by the delegator to be valid for redemption. The `MetaMaskSmartAccount` supports signing the delegation using [EIP-712 Typed Data](https://eips.ethereum.org/EIPS/eip-712) via the `signDelegation` method. +Sign a delegation as follows: + + + + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; +import { delegatorSmartAccount } from "./config.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: delegatorSmartAccount.address, + caveats: [] // Empty caveats array - we recommend adding appropriate restrictions. +}); + +const signature = await delegatorSmartAccount.signDelegation({ delegation }); + +const signedDelegation = { + ...delegation, + signature +}; +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const publicClient = createPublicClient({ + chain, + transport: http() +}); + +const privateKey = generatePrivateKey(); +const account = privateKeyToAccount(privateKey); + +export const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: "0x", + signatory: { account }, +}); + +``` + + + \ No newline at end of file diff --git a/gator_versioned_docs/version-0.9.0/how-to/create-delegation/restrict-delegation.md b/gator_versioned_docs/version-0.9.0/how-to/create-delegation/restrict-delegation.md new file mode 100644 index 00000000000..810eb27e064 --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/how-to/create-delegation/restrict-delegation.md @@ -0,0 +1,807 @@ +--- +description: Learn how to restrict a delegation using caveat enforcers, and the available caveat types. +sidebar_position: 1 +toc_max_heading_level: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Restrict a delegation + +Use [caveat enforcers](../../concepts/caveat-enforcers.md) to apply specific rules and restrictions +to a delegation, ensuring that delegated executions are only performed under predefined circumstances. + +A delegation has a `caveats` property, which is an array of `Caveat` objects. +Each caveat is specified as follows: + +```typescript +export type Caveat = { + enforcer: Hex; // The address of the caveat enforcer contract. + terms: Hex; // Data passed to the caveat enforcer, describing how the redemption should be validated. + args: Hex; // Data that may be specified by the redeemer when redeeming the delegation (only used in limited cases). +}; +``` + +The MetaMask Delegation Toolkit provides a `CaveatBuilder` interface, which offers an intuitive way to define the `caveats` array. +Use the `CaveatBuilder` to easily ensure that your delegations grant only the necessary authority. + +## Create the caveat builder + +To create the caveat builder, call the `createCaveatBuilder()` function, passing an instance of `DeleGatorEnvironment`. +The environment can be accessed from the `MetaMaskSmartAccount`, as in this example: + +```typescript +const environment = delegatorSmartAccount.environment; + +const caveatBuilder = createCaveatBuilder(environment); +``` + +:::note +By default, the `CaveatBuilder` does not allow empty caveats. To allow the `CaveatBuilder` to build an empty caveats array, provide the following configuration: + +```typescript +const caveatBuilder = createCaveatBuilder(environment, { allowEmptyCaveats: true }); +``` +::: + +## Add caveats to the builder + +Add caveats to the builder using the `addCaveat` method, specifying the [caveat type](#caveat-types) and its parameters. You can chain multiple calls to `addCaveat` as in the following example: + +```typescript +const caveats = caveatBuilder + // allowedTargets accepts an array of addresses. + // This caveat restricts the caller to only use the delegation to interact with the specified address. + .addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92"]) + // allowedMethods accepts an array of methods. + // This caveat restricts the caller to only use the delegation to invoke the specified methods. + .addCaveat("allowedMethods", [ + "approve(address,uint256)", + "transfer(address,uint256)" + ]) + // limitedCalls accepts a number. + // This caveat restricts the caller to only use the delegation one time. + .addCaveat("limitedCalls", 1) + .build(); +``` + +
+Important considerations when using caveat enforcers +

+ +- Delegations without caveats are entirely permissive. + It is crucial to add appropriate caveats to restrict the delegated authority sufficiently. + Failing to do so could result in unintended access or actions. +- Caveat enforcers safeguard the execution process but do not guarantee a final state post-redemption. + Always combine caveat enforcers thoughtfully to create comprehensive protection. +- When using multiple caveat enforcers that modify external contract states, the order matters. + For example, if you include both [`NativeBalanceGteEnforcer`](#nativebalancegte) to ensure a balance has increased and + [`NativeTokenPaymentEnforcer`](#nativetokenpayment) to deduct from that balance, + executing `NativeTokenPaymentEnforcer` first might cause `NativeBalanceGteEnforcer` to fail validation. + Consider the sequence of enforcers carefully when creating delegations with interdependent caveats. + +

+
+ +For convenience, you can also pass the `CaveatBuilder` directly to the various helper methods for creating a delegation. For example: + +```typescript +const caveats = caveatBuilder + // allowedTargets accepts an array of addresses. + .addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92"]) + // allowedMethods accepts an array of methods. + .addCaveat("allowedMethods", [ + "approve(address,uint256)", + "transfer(address,uint256)" + ]) + // limitedCalls accepts a number. + .addCaveat("limitedCalls", 1); + +const delegation = createDelegation({ + to: delegate, + from: delegator, + caveats +}); +``` + +## Caveat types + +The `CaveatBuilder` supports various caveat types, each serving a specific purpose. +These caveat types correspond to the out-of-the-box caveat enforcers +that the MetaMask Delegation Toolkit provides. + +Fore more granular or custom control, you can also [create custom caveat enforcers](create-custom-caveat-enforcer.md) +and add them to the caveat builder. + +### `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 + +1. Index in the calldata byte array (including the 4-byte method selector) where the expected calldata starts +2. Expected calldata as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("allowedCalldata", + 4, + encodeAbiParameters([ + { type: "string" }, + { type: "uint256" } + ], [ + "Hello Gator", + 12345n + ]) +); +``` + +:::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 + +1. An array of methods as 4-byte hex strings, ABI function signatures, or `ABIFunction` objects + +#### Example + +```typescript +caveatBuilder.addCaveat("allowedMethods", [ + "0xa9059cbb", + "transfer(address,uint256)", + { + 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 + +1. An array of addresses as hex strings + +#### Example + +```typescript +caveatBuilder.addCaveat("allowedTargets", [ + "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 + +1. The expected `args` as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("argsEqualityCheck", + "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 + +1. After threshold block number as a `bigint` +2. Before threshold block number as a `bigint` + +You can specify `0n` to indicate that there is no limitation on a threshold. + +#### Example + +```typescript +caveatBuilder.addCaveat("blocknumber", + 19426587n, + 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 + +1. A contract address as a hex string +2. The salt to use with the contract, as a hex string +3. The bytecode of the contract as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("deployed", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x0e3e8e2381fde0e8515ed47ec9caec8ba2bc12603bc2b36133fa3e3fa4d88587", + "0x..." // The deploy bytecode for the contract at 0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92 +); +``` + +### `erc1155BalanceGte` + +Ensures the ERC-1155 balance of a specified address has increased by at least a specified amount after the execution has been performed, regardless of what the execution is. + +**Caveat enforcer contract:** [`ERC1155BalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC1155BalanceGteEnforcer.sol) + +#### Parameters + +1. An ERC-1155 contract address as a hex string +2. The recipient's address as a hex string +3. The ID of the ERC-1155 token as a bigint +4. The amount by which the balance must have increased as a bigint + +#### Example + +```typescript +caveatBuilder.addCaveat("erc1155BalanceGte", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1n, + 1_000_000n +); +``` + +### `erc20BalanceGte` + +Ensures the delegator's ERC-20 balance increases by at least the specified amount after execution, regardless of the execution. + +**Caveat enforcer contract:** [`ERC20BalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC20BalanceGteEnforcer.sol) + +#### Parameters + +1. An ERC-20 contract address as a hex string +2. Amount as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20BalanceGte", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n +); +``` + +### `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 + +1. The address of the ERC-20 token contract. +2. The maximum amount of tokens that can be transferred per period, in wei. +3. The duration of each period in seconds. +4. The timestamp when the first period begins. + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20PeriodTransfer", + "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", // Address of the ERC-20 token + 1000000000000000000n, // 1 ERC-20 token - 18 decimals, in wei + 86400, // 1 day in seconds + 1743763600, // April 4th, 2025, at 00:00:00 UTC +); +``` + +### `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 + +1. An ERC-20 contract address as a hex string +2. Initial amount available at start time as a `bigint` +3. Maximum total amount that can be unlocked as a `bigint` +4. Rate at which tokens accrue per second as a `bigint` +5. Start timestamp as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20Streaming", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n, + 10_000_000n, + 100n, + 1703980800 +); +``` + +### `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 + +1. An ERC-20 contract address as a hex string +2. Amount as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc20TransferAmount", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + 1_000_000n +); +``` + +### `erc721BalanceGte` + +Ensures the ERC-721 balance of the specified recipient address increases by at least the specified amount after execution, regardless of execution type. + +**Caveat enforcer contract:** [`ERC721BalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC721BalanceGteEnforcer.sol) + +#### Parameters + +1. An ERC-721 contract address as a hex string +2. The recipient's address as a hex string +3. The amount by which the balance must have increased as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc721BalanceGte", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n +); +``` + +### `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 + +1. The permitted ERC-721 contract address as a hex string +2. The permitted ID of the ERC-721 token as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("erc721Transfer", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 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 + +1. A hex value for calldata. + +#### Example + +```typescript +caveatBuilder.addCaveat("exactCalldata", + "0x1234567890abcdef" // Calldata to be matched +); +``` + +### `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 + +1. Array of expected `ExecutionStruct`, each containing target address, value, and calldata. + +#### Example + +```typescript +const executions = [ + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + callData: "0x", + }, + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 0n, + callData: "0x", + }, +]; + +caveatBuilder.addCaveat("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 + +1. `ExecutionStruct` to be expected. + +#### Example + +```typescript +caveatBuilder.addCaveat("exactExecution", { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + 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 + +1. Array of expected `ExecutionStruct`, each containing target address, value, and calldata. + +#### Example + +```typescript +const executions = [ + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + callData: "0x", + }, + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 0n, + callData: "0x", + }, +]; + +caveatBuilder.addCaveat("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 + +1. An ID as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("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 + +1. A count as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("limitedCalls", + 1 +); +``` + +### `nativeBalanceGte` + +Ensures that the recipient's native token balance has increased by at least the specified amount. + +**Caveat enforcer contract:** [`NativeBalanceGteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NativeBalanceGteEnforcer.sol) + +#### Parameters + +1. The recipient's address as a hex string +2. The amount by which the balance must have increased as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeBalanceGte", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n +); +``` + +### `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 + +1. The recipient's address as a hex string +2. The amount that must be paid as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenPayment", + "0x3fF528De37cd95b67845C1c55303e7685c72F319", + 1_000_000n +); +``` + +### `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 + +1. The maximum amount of tokens that can be transferred per period, in wei. +2. The duration of each period in seconds. +3. The timestamp when the first period begins. + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenPeriodTransfer", + 1000000000000000000n, // 1 ETH in wei + 86400, // 1 day in seconds + 1743763600, // April 4th, 2025, at 00:00:00 UTC +) +``` + +### `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 + +1. Initial amount available at start time as a `bigint` +2. Maximum total amount that can be unlocked as a `bigint` +3. Rate at which tokens accrue per second as a `bigint` +4. Start timestamp as a number + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenStreaming", + 1_000_000n, + 10_000_000n, + 100n, + 1703980800 +); +``` + +### `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 + +1. The allowance as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("nativeTokenTransferAmount", + 1_000_000n +); +``` + +### `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 + +1. A nonce as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("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 + +1. The target contract address as a hex string + +#### Example + +```typescript +caveatBuilder.addCaveat("ownershipTransfer", + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92" +); +``` + +### `redeemer` + +Limits the addresses that can redeem the delegation. +This caveat is designed for restricting 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 + +1. An array of addresses as hex strings + +#### Example + +```typescript +caveatBuilder.addCaveat("redeemer", + [ + "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 + +1. The address of the ERC-20 token contract. +2. The address that will receive the tokens. +3. The amount of tokens to transfer, in wei. +4. The target address for the first transaction. +5. The calldata for the first transaction. + +#### Example + +```typescript +caveatBuilder.addCaveat("specificActionERC20TransferBatch", + "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da" // Address of ERC-20 token contract + "0x027aeAFF3E5C33c4018FDD302c20a1B83aDCD96C", // Address that will receive the tokens + 1000000000000000000n, // 1 ERC-20 token - 18 decimals, in wei + "0xb49830091403f1Aa990859832767B39c25a8006B", // Target address for first transaction + "0x1234567890abcdef" // Calldata to be matched for first transaction +) +``` + +### `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 + +1. After threshold timestamp as a number +2. Before threshold timestamp as a number + +You can specify `0` to indicate that there is no limitation on a threshold. + +#### Example + +```typescript +caveatBuilder.addCaveat("timestamp", + 499165200, + 1445412480 +); +``` + +### `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 + +1. A value as a `bigint` + +#### Example + +```typescript +caveatBuilder.addCaveat("valueLte", + 1_000_000_000_000_000_000n // 1 ETH in wei +); +``` diff --git a/gator_versioned_docs/version-0.9.0/how-to/create-delegator-account.md b/gator_versioned_docs/version-0.9.0/how-to/create-delegator-account.md new file mode 100644 index 00000000000..73e132ee5a1 --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/how-to/create-delegator-account.md @@ -0,0 +1,80 @@ +--- +description: Learn how to create a delegator account using Viem. +sidebar_position: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Create a delegator account + +The MetaMask Delegation Toolkit is embedded, meaning that the end user can instantly interact with a dapp without wallet authorization, confirmations, or corporate logos. Enable users to create a [delegator account](../concepts/delegator-accounts.md) directly in your dapp. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) + +## Create a `MetaMaskSmartAccount` + +The following is an example of creating a delegator account using Viem Core SDK. +Viem Core SDK provides low-level interfaces to offer flexibility and control over the delegator +account creation lifecycle. + +In the example, the Viem [`privateKeyToAccount`](https://viem.sh/docs/accounts/privateKey.html) +function creates an externally owned account as the owner of the delegator account. + + + + +```typescript +import { publicClient, owner } from "./config.ts"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +const deploySalt = "0x"; + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner.address, [], [], []], + deploySalt, + signatory: { account: owner }, +}); +``` + + + + + +```typescript +import { http, createPublicClient } from "viem"; +import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; + +const transport = http(); +export const publicClient = createPublicClient({ + transport, + chain, +}); + +const privateKey = generatePrivateKey(); +export const owner = privateKeyToAccount(privateKey); +``` + + + + + +This example creates the `MetaMaskSmartAccount`, which can perform several functions: + +- In conjunction with [Viem Account Abstraction clients](configure.md), deploy the smart contract account, + and [send user operations](send-user-operation.md). +- [Sign delegations](create-delegation/index.md) that can be used to grant specific rights and permissions to other accounts. + +:::note +The example above uses the Hybrid Delegator smart contract account, which is configurable to have an EOA "owner" and any number of P256 (passkey) signers. +You can also [configure other delegator account types](configure-delegator-accounts-signers.md). +::: diff --git a/gator_versioned_docs/version-0.9.0/how-to/redeem-delegation.md b/gator_versioned_docs/version-0.9.0/how-to/redeem-delegation.md new file mode 100644 index 00000000000..23be468113e --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/how-to/redeem-delegation.md @@ -0,0 +1,347 @@ +--- +description: Learn how to redeem a delegation with a smart contract account (SCA) or an externally owned account (EOA). +sidebar_position: 7 +toc_max_heading_level: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Redeem a delegation + +A delegate can redeem a delegation by submitting either a user operation or a regular transaction, +depending on whether the delegate is a smart contract account (SCA) or externally owned account (EOA). + +The redeem transaction is sent to the `DelegationManager` contract, which validates the delegation and executes actions on the delegator's behalf. To prepare the call data for the redeem transaction, use the `redeemDelegation` utility function. The function supports batch redemption, allowing multiple delegations to be processed within a single transaction. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator account.](create-delegator-account.md) +- [Create a delegation.](create-delegation/index.md) + +## Redeem a delegation + +Redeem a delegation with a [smart contract account (SCA)](#redeem-with-an-sca) or an [externally owned account (EOA)](#redeem-with-an-eoa). + +### Redeem with an SCA + +The following example demonstrates how to submit a user operation to redeem a delegation. +It assumes you have a delegation signed by the delegator, and that the delegate is an SCA. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { bundlerClient, pimlicoClient } from "./client.ts"; +import { delegateSmartAccount } from "./account.ts"; + +const delegations: Delegation[] = [ signedDelegation ]; + +// SINGLE_DEFAULT_MODE is the default execution mode. +const mode: ExecutionMode = SINGLE_DEFAULT_MODE; + +// For SINGLE execution modes, the executions array must be length 1. +const executions: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const { fast: fee } = await pimlicoClient.getUserOperationGasPrice(); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: "", + data: redeemDelegationCalldata + } + ], + ...fee +}); +``` + + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +improt { publicClient } from "./client.ts" + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { createPimlicoClient } from "permissionless/clients/pimlico"; + +// You can get the API Key from the Pimlico dashboard. +const pimlicoUrl = "https://api.pimlico.io/v2/59141/rpc"; + +export const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http(pimlicoUrl), +}); + +export const pimlicoClient = createPimlicoClient({ + transport: http(pimlicoUrl), +}); +``` + + + + +### Redeem with an EOA + +The following example demonstrates how to submit a transaction to redeem a delegation. It assumes you have a delegation signed by the delegator, and that the delegate is an EOA. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { lineaSepolia as chain } from "viem/chains"; +import { delegateWalletClient } from "./account.ts"; + +const delegations: Delegation[] = [ signedDelegation ]; + +// SINGLE_DEFAULT_MODE is the default execution mode. +const mode: ExecutionMode = SINGLE_DEFAULT_MODE; + +// For SINGLE execution modes, the executions array must be length 1. +// Modify the executions to fit your use case. +const executions: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const transactionHash = await walletClient.sendTransaction({ + to: getDeleGatorEnvironment(chain.id).DelegationManager, + data: redeemDelegationCalldata, + chain, +}); +``` + + + + + +```typescript +import { privateKeyToAccount } from "viem/accounts"; +import { lineaSepolia as chain } from "viem/chains"; +import { createWalletClient, http } from "viem"; + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateWalletClient = createWalletClient({ + account: delegateAccount, + chain, + transport: http(), +}) +``` + + + + +## Redeem multiple delegations + +You can redeem multiple delegations in a single user operation, each delegation independent of the others. +Each element in the `delegationsArray` must have a corresponding element in the `executionsArray` and `modes`. + +The following example assumes you already have multiple signed delegations and that the delegate is an SCA. +The preparation of the call data is the same when [using an EOA as the delegate](#redeem-with-an-eoa); +the primary difference is that an EOA submits a regular transaction instead of a user operation. + + + + +```typescript +import { + DelegationFramework, + SINGLE_DEFAULT_MODE, + ExecutionStruct +} from "@metamask/delegation-toolkit"; +import { bundlerClient, pimlicoClient } from "./client.ts"; +import { delegateSmartAccount } from "./account.ts"; + +const delegationsArray: Delegation[][] = [ + [ signedDelegation1 ] + [ signedDelegation2 ] + [ signedDelegation3 ] +]; + +const modes: ExecutionMode = [ + SINGLE_DEFAULT_MODE, + SINGLE_DEFAULT_MODE, + SINGLE_DEFAULT_MODE +]; + +const execution: ExecutionStruct[] = [{ + target: zeroAddress, + value: 0n, + callData: "0x" +}]; + +// Modify the executions to fit your use case. For simplicity, we've +// included a basic example. The execution array can contain +// multiple different executions. +const executionsArray: ExecutionStruct:[][] = [ + execution, + execution, + execution +]; + +const redeemDelegationCalldata = DelegationFramework.encode.redeemDelegations({ + delegations: [ delegations ], + modes: [ mode ], + executions: [ executions ] +}); + +const { fast: fee } = await pimlicoClient.getUserOperationGasPrice(); + +const userOperationHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [ + { + to: "", + data: redeemDelegationCalldata + } + ], + ...fee +}); +``` + + + + + +```typescript +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; +import { privateKeyToAccount } from "viem/accounts"; +import { publicClient } from "./client.ts"; + +const delegateAccount = privateKeyToAccount("0x..."); + +export const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateAccount.address, [], [], []], + deploySalt: "0x", + signatory: { account: delegateAccount }, +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { lineaSepolia as chain } from "viem/chains"; +import { createBundlerClient } from "viem/account-abstraction"; +import { createPimlicoClient } from "permissionless/clients/pimlico"; + +// You can get the API Key from the Pimlico dashboard. +const pimlicoUrl = "https://api.pimlico.io/v2/59141/rpc"; + +export const publicClient = createPublicClient({ + chain, + transport: http(), +}); + +export const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http(pimlicoUrl), +}); + +export const pimlicoClient = createPimlicoClient({ + transport: http(pimlicoUrl), +}); +``` + + + + +## Execution modes + +The Delegation Toolkit supports several execution modes based on [ERC-7579](https://erc7579.com/). +See the [ERC implementation](https://github.com/erc7579/erc7579-implementation/blob/main/src/lib/ModeLib.sol) for more details about the execution modes. + +The supported execution modes are `SINGLE_DEFAULT_MODE`, `SINGLE_TRY_MODE`, `BATCH_DEFAULT_MODE`, and `BATCH_TRY_MODE`. + +### `SINGLE` execution modes + +In `SINGLE` execution modes, only a single delegation chain and a single execution can be provided. This mode processes delegations sequentially: + +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. + +### `BATCH` execution modes + +In `BATCH` execution modes, multiple delegation chains and multiple executions can be provided. This mode executes delegations in an interleaved way: + +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. + +### `DEFAULT` modes + +In `DEFAULT` modes, if a revert occurs during redemption, the entire user operation reverts at that point. + +### `TRY` modes + +In `TRY` modes, if a revert occurs during redemption, execution of the user operation continues. diff --git a/gator_versioned_docs/version-0.9.0/how-to/send-user-operation.md b/gator_versioned_docs/version-0.9.0/how-to/send-user-operation.md new file mode 100644 index 00000000000..428d27bfe88 --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/how-to/send-user-operation.md @@ -0,0 +1,176 @@ +--- +description: Learn how to send an ERC-4337 user operation using Viem. +sidebar_position: 4 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# 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 Delegation Toolkit 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 smart contract account that has not been deployed, the toolkit configures the user operation to automatically deploy the account. +::: + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../get-started/install.md) +- [Configure the Delegation Toolkit.](configure.md) +- [Create a delegator account.](create-delegator-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 +import { bundlerClient, smartAccount } from "./config.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, + maxPriortyFeePerGas +}); +``` + + + + + +```typescript +import { createPublicClient, http } from "viem"; +import { createBundlerClient } from "viem/account-abstraction"; +import { lineaSepolia as chain } from "viem/chains"; +import { + Implementation, + toMetaMaskSmartAccount, +} from "@metamask/delegation-toolkit"; + +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", + signatory: { account }, +}); + +export const bundlerClient = createBundlerClient({ + publicClient, + transport: http("https://public.pimlico.io/v2/1/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). + +```typescript title="example.ts" +// add-next-line ++ import { createPimlicoClient } from "permissionless/clients/pimlico"; +import { bundlerClient, smartAccount } from "./config.ts" // The config.ts is the same as in the previous example. + +// remove-start +- const maxFeePerGas = 1n; +- const maxPriorityFeePerGas = 1n; +// remove-end + +// add-start ++ const pimlicoClient = createPimlicoClient({ ++ transport: http("https://api.pimlico.io/v2/59141/rpc"), // 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, +- maxPriortyFeePerGas + // 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" +import { createPimlicoClient } from "permissionless/clients/pimlico"; +import { bundlerClient, smartAccount } from "./config.ts" // The config.ts is the same as in the previous example. + +const pimlicoClient = createPimlicoClient({ + transport: http("https://api.pimlico.io/v2/59141/rpc"), // 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 +``` diff --git a/gator_versioned_docs/version-0.9.0/index.md b/gator_versioned_docs/version-0.9.0/index.md new file mode 100644 index 00000000000..3cdab43dc69 --- /dev/null +++ b/gator_versioned_docs/version-0.9.0/index.md @@ -0,0 +1,63 @@ +--- +title: Introduction to the MetaMask Delegation Toolkit +sidebar_label: Introduction +description: High-level overview of the Delegation Toolkit, its benefits, and where to start in the documentation. +sidebar_position: 1 +--- + +import CardList from "@site/src/components/CardList" + +# MetaMask Delegation Toolkit documentation + +## Why use the toolkit? + +The MetaMask Delegation Toolkit enables developers to create frictionless new experiences based +on granular permission sharing and trust. +The toolkit offers a suite of contracts, libraries, and services designed for maximum composability, +allowing developers to build and extend their dapps with ease. +The toolkit enables: + +- **Instant user onboarding.** Provide frictionless onboarding with no browser extension, mobile + app, or seed phrase required. + +- **New web3 experiences.** Unlock new experiences such as peer-to-peer social + coordination using incentive trees, or recurring subscription payments that don't require users + to connect to the dapp. + +- **Uninterrupted user experiences.** Keep users immersed in the dapp by embedding the wallet + experience and reassigning gas costs to where they make sense. + +The toolkit includes the [Delegation Framework](concepts/delegation.md#delegation-framework) – a +pioneering set of open-source, customizable smart contracts, allowing dapps and protocols to +implement custom permission control. +Developers can use the Delegation Framework to prepare their dapps for +[delegations](concepts/delegation.md) created from +[delegator accounts](concepts/delegator-accounts.md). + +## Where do I start? + +Check out the following sections to get started with the MetaMask Delegation Toolkit: + + + +## Questions? + +If you have questions, email hellogators@consensys.net. diff --git a/gator_versioned_sidebars/version-0.10.0-sidebars.json b/gator_versioned_sidebars/version-0.10.0-sidebars.json new file mode 100644 index 00000000000..e53f21f7e79 --- /dev/null +++ b/gator_versioned_sidebars/version-0.10.0-sidebars.json @@ -0,0 +1,8 @@ +{ + "docSidebar": [ + { + "type": "autogenerated", + "dirName": "." + } + ] +} diff --git a/gator_versioned_sidebars/version-0.10.1-sidebars.json b/gator_versioned_sidebars/version-0.10.1-sidebars.json new file mode 100644 index 00000000000..e53f21f7e79 --- /dev/null +++ b/gator_versioned_sidebars/version-0.10.1-sidebars.json @@ -0,0 +1,8 @@ +{ + "docSidebar": [ + { + "type": "autogenerated", + "dirName": "." + } + ] +} diff --git a/gator_versioned_sidebars/version-0.10.2-sidebars.json b/gator_versioned_sidebars/version-0.10.2-sidebars.json new file mode 100644 index 00000000000..e53f21f7e79 --- /dev/null +++ b/gator_versioned_sidebars/version-0.10.2-sidebars.json @@ -0,0 +1,8 @@ +{ + "docSidebar": [ + { + "type": "autogenerated", + "dirName": "." + } + ] +} diff --git a/gator_versioned_sidebars/version-0.11.0-sidebars.json b/gator_versioned_sidebars/version-0.11.0-sidebars.json new file mode 100644 index 00000000000..e8bd6fa30ee --- /dev/null +++ b/gator_versioned_sidebars/version-0.11.0-sidebars.json @@ -0,0 +1,9 @@ +{ + "docSidebar": [ + { + "type": "autogenerated", + "dirName": "." + } + ] +} + \ No newline at end of file diff --git a/gator_versioned_sidebars/version-0.9.0-sidebars.json b/gator_versioned_sidebars/version-0.9.0-sidebars.json new file mode 100644 index 00000000000..e8bd6fa30ee --- /dev/null +++ b/gator_versioned_sidebars/version-0.9.0-sidebars.json @@ -0,0 +1,9 @@ +{ + "docSidebar": [ + { + "type": "autogenerated", + "dirName": "." + } + ] +} + \ No newline at end of file diff --git a/gator_versions.json b/gator_versions.json new file mode 100644 index 00000000000..76c33f95d8e --- /dev/null +++ b/gator_versions.json @@ -0,0 +1,7 @@ +[ + "0.11.0", + "0.10.2", + "0.10.1", + "0.10.0", + "0.9.0" +] diff --git a/snaps/how-to/debug-a-snap/common-issues.md b/snaps/how-to/debug-a-snap/common-issues.md index 5480ea25d25..c84ee40b7cf 100644 --- a/snaps/how-to/debug-a-snap/common-issues.md +++ b/snaps/how-to/debug-a-snap/common-issues.md @@ -43,7 +43,7 @@ simply prepending `var variableName;` to your Snap bundle may solve the problem. `yarn mm-snap build` automatically handles that one.) :::caution -Run [`yarn mm-snap manifest --fix`](../../reference/cli/subcommands.md#m-manifest) if you modified +Run [`yarn mm-snap manifest --fix`](../../reference/cli.md#m-manifest) if you modified your Snap bundle after building. Otherwise, your manifest `shasum` value won't be correct, and attempting to install your Snap fails. ::: diff --git a/src/components/SidebarVersionDropdown/SidebarVersionDropdown.module.css b/src/components/SidebarVersionDropdown/SidebarVersionDropdown.module.css new file mode 100644 index 00000000000..04ec3acb23e --- /dev/null +++ b/src/components/SidebarVersionDropdown/SidebarVersionDropdown.module.css @@ -0,0 +1,93 @@ +.dropdown { + position: relative; + font-family: var(--ifm-font-family-base); + border-bottom: 1px solid var(--ifm-toc-border-color); + padding: 0.4rem 0; +} + +.versionItem { + display: flex; + align-items: center; + font-size: 1.7rem; + font-weight: 500; + cursor: pointer; + color: var(--color-text); + user-select: none; + width: 100%; + padding: 0.6rem 2.4rem; +} + +.versionItem:hover { + color: var(--ifm-color-primary); +} + +.chevron { + font-size: 2.2rem; + line-height: 1; + padding: 1rem; +} + +.label { + flex: 1; + line-height: 1.5; +} + +.menu { + position: absolute; + top: 100%; + left: 0; + background: var(--general-white); + border: 1px solid var(--general-gray-light); + border-radius: 4px; + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1); + margin-top: 0.3rem; + padding-bottom: 1rem; + z-index: 100; + width: 100%; + min-width: 180px; +} + +.menuItem { + padding: 0.6rem 2.4rem; + font-size: 1.5rem; + font-weight: 400; + cursor: pointer; + color: var(--color-text); + background: var(--general-white); +} + +.menuItem:hover { + background: var(--ifm-hover-overlay); +} + +.menuItem.hovered { + background: var(--ifm-hover-overlay); +} + +.menuItem.selected { + color: var(--ifm-color-primary) !important; + background: var(--general-gray-light); +} + +[data-theme='dark'] .menu { + background: var(--general-black-light) !important; + border-color: var(--general-white) !important; +} + +[data-theme='dark'] .menuItem { + color: var(--general-white) !important; + background: var(--general-black-light) !important; +} + +[data-theme='dark'] .menuItem:hover { + background: var(--general-black-mid) !important; +} + +[data-theme='dark'] .menuItem.selected { + color: var(--ifm-color-primary) !important; + background: var(--general-black-mid) !important; +} + +[data-theme='dark'] .dropdown { + border-bottom: 1px solid var(--general-white); +} \ No newline at end of file diff --git a/src/components/SidebarVersionDropdown/SidebarVersionDropdown.tsx b/src/components/SidebarVersionDropdown/SidebarVersionDropdown.tsx new file mode 100644 index 00000000000..d40bc938f3d --- /dev/null +++ b/src/components/SidebarVersionDropdown/SidebarVersionDropdown.tsx @@ -0,0 +1,94 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { useHistory, useLocation } from '@docusaurus/router'; +import versions from '@site/gator_versions.json'; +import clsx from 'clsx'; +import styles from './SidebarVersionDropdown.module.css'; + +export default function SidebarVersionDropdown({ path = 'delegation-toolkit' }: { path?: string }) { + const history = useHistory(); + const location = useLocation(); + const dropdownRef = useRef(null); + const [open, setOpen] = useState(false); + const [hoveredVersion, setHoveredVersion] = useState(null); // State to track hovered item + + const latestVersion = versions[0]; + const allVersions = ['current', latestVersion, ...versions.filter(v => v !== latestVersion)]; + + const getVersionLabel = (version: string) => { + if (version === 'current') return 'development'; + if (version === latestVersion) return `latest (${version})`; + return version; + }; + + const getActiveVersion = () => { + const segments = location.pathname.split('/'); + const versionSegment = segments[2]; + + if (versionSegment === 'development') return 'current'; + if (!versionSegment || versionSegment === '' || versionSegment === latestVersion) return latestVersion; + if (versions.includes(versionSegment)) return versionSegment; + return latestVersion; + }; + + const currentVersion = getActiveVersion(); + + const handleSelect = (version: string) => { + let versionPath = ''; + if (version === 'current') { + versionPath = `/${path}/development/`; + } else if (version === latestVersion) { + versionPath = `/${path}/`; + } else { + versionPath = `/${path}/${version}/`; + } + + history.push(versionPath); + setOpen(false); + }; + + const toggleDropdown = () => setOpen((prev) => !prev); + + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { + setOpen(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); + }, []); + + return ( +
+
e.key === 'Enter' && toggleDropdown()} + > + Version: {getVersionLabel(currentVersion)} + {/* Custom class applied here */} +
+ {open && ( +
    + {allVersions.map((version) => ( +
  • handleSelect(version)} + role="menuitem" + onMouseEnter={() => setHoveredVersion(version)} // Set hovered version + onMouseLeave={() => setHoveredVersion(null)} // Reset hover on mouse leave + > + {getVersionLabel(version)} +
  • + ))} +
+ )} +
+ ); +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index eae83266abf..547504ccc0a 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -34,12 +34,17 @@ export default function Home(): JSX.Element { href: '/wallet', theme: '', }, - { + { + title: 'Delegation Toolkit', + description: + 'Integrate MetaMask smart accounts into your dapp. Create embedded wallets that support delegated permissions, gas abstraction, and secure execution.', + href: '/delegation-toolkit', + }, + { title: 'Embedded Wallets SDK', description: 'Use the Embedded Wallets SDK (Web3Auth) to onboard power users and first-time users in seconds via social logins, passkeys, or by integrating your own authentication providers.', href: 'https://web3auth.io/docs', - theme: '', }, { title: 'Snaps', diff --git a/src/scss/custom.scss b/src/scss/custom.scss index f7531a7d577..e86412f316c 100644 --- a/src/scss/custom.scss +++ b/src/scss/custom.scss @@ -392,12 +392,10 @@ ol { border-radius: 0.5rem; } -/** - * Temporary style - to be deleted - */ +/* Doc version banner */ -.getfeedback-container { - margin-top: 5rem; +.theme-doc-version-banner { + font-size: 1.6rem; } /* Header */ diff --git a/src/theme/DocSidebar/Desktop/index.tsx b/src/theme/DocSidebar/Desktop/index.tsx new file mode 100644 index 00000000000..657c42c3503 --- /dev/null +++ b/src/theme/DocSidebar/Desktop/index.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import clsx from 'clsx'; +import { useThemeConfig } from '@docusaurus/theme-common'; +import { useLocation } from '@docusaurus/router'; +import Logo from '@theme/Logo'; +import CollapseButton from '@theme-original/DocSidebar/Desktop/CollapseButton'; +import Content from '@theme-original/DocSidebar/Desktop/Content'; +import SidebarVersionDropdown from '@site/src/components/SidebarVersionDropdown/SidebarVersionDropdown'; +import type { PropSidebar } from '@docusaurus/plugin-content-docs'; +import styles from './styles.module.css'; + +type Props = { + path: string; + sidebar: PropSidebar; + onCollapse: () => void; + isHidden: boolean; +}; + +function DocSidebarDesktop({ path, sidebar, onCollapse, isHidden }: Props) { + const { + navbar: { hideOnScroll }, + docs: { + sidebar: { hideable }, + }, + } = useThemeConfig(); + + const location = useLocation(); + const isGatorDocs = location.pathname.startsWith('/delegation-toolkit'); + + let versionDropdown = null; + try { + if (isGatorDocs) { + versionDropdown = ( +
+ +
+ ); + } + } catch (e) { + console.error('Failed to render version dropdown:', e); + } + + return ( +
+ {hideOnScroll && } + {versionDropdown} + + {hideable && } +
+ ); +} + +export default DocSidebarDesktop; diff --git a/src/theme/DocSidebar/Desktop/styles.module.css b/src/theme/DocSidebar/Desktop/styles.module.css new file mode 100644 index 00000000000..c5d5e50e16f --- /dev/null +++ b/src/theme/DocSidebar/Desktop/styles.module.css @@ -0,0 +1,37 @@ +@media (min-width: 997px) { + .sidebar { + display: flex; + flex-direction: column; + height: 100%; + padding-top: var(--ifm-navbar-height); + width: var(--doc-sidebar-width); + } + + .sidebarWithHideableNavbar { + padding-top: 0; + } + + .sidebarHidden { + opacity: 0; + visibility: hidden; + } + + .sidebarLogo { + display: flex !important; + align-items: center; + margin: 0 var(--ifm-navbar-padding-horizontal); + min-height: var(--ifm-navbar-height); + max-height: var(--ifm-navbar-height); + color: inherit !important; + text-decoration: none !important; + } + + .sidebarLogo img { + margin-right: 0.5rem; + height: 2rem; + } +} + +.sidebarLogo { + display: none; +} diff --git a/src/theme/DocSidebar/Mobile/index.tsx b/src/theme/DocSidebar/Mobile/index.tsx new file mode 100644 index 00000000000..d36be9852a1 --- /dev/null +++ b/src/theme/DocSidebar/Mobile/index.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import clsx from 'clsx'; +import { + NavbarSecondaryMenuFiller, + type NavbarSecondaryMenuComponent, + ThemeClassNames, +} from '@docusaurus/theme-common'; +import { useNavbarMobileSidebar } from '@docusaurus/theme-common/internal'; +import { useLocation } from '@docusaurus/router'; +import DocSidebarItems from '@theme/DocSidebarItems'; +import SidebarVersionDropdown from '@site/src/components/SidebarVersionDropdown/SidebarVersionDropdown'; +import type { Props } from '@theme/DocSidebar/Mobile'; +import styles from './styles.module.css'; + +// eslint-disable-next-line react/function-component-definition +const DocSidebarMobileSecondaryMenu: NavbarSecondaryMenuComponent = ({ + sidebar, + path, +}) => { + const mobileSidebar = useNavbarMobileSidebar(); + const location = useLocation(); + const isGatorDocs = location.pathname.startsWith('/delegation-toolkit'); + + return ( +
    + {isGatorDocs && ( +
  • + +
  • + )} + { + if ((item.type === 'category' && item.href) || item.type === 'link') { + mobileSidebar.toggle(); + } + }} + level={1} + /> +
+ ); +}; + +function DocSidebarMobile(props: Props) { + return ( + + ); +} + +export default React.memo(DocSidebarMobile); diff --git a/src/theme/DocSidebar/Mobile/styles.module.css b/src/theme/DocSidebar/Mobile/styles.module.css new file mode 100644 index 00000000000..0b0645f8b4a --- /dev/null +++ b/src/theme/DocSidebar/Mobile/styles.module.css @@ -0,0 +1,5 @@ +.versionDropdownContainer { + padding: 1rem; + border-bottom: 1px solid var(--ifm-toc-border-color); +} + diff --git a/src/theme/DocVersionBanner/index.jsx b/src/theme/DocVersionBanner/index.jsx new file mode 100644 index 00000000000..6e085cfd3fc --- /dev/null +++ b/src/theme/DocVersionBanner/index.jsx @@ -0,0 +1,119 @@ +import React from "react"; +import clsx from "clsx"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import Link from "@docusaurus/Link"; +import Translate from "@docusaurus/Translate"; +import { + useActivePlugin, + useDocVersionSuggestions, +} from "@docusaurus/plugin-content-docs/client"; +import { ThemeClassNames } from "@docusaurus/theme-common"; +import { + useDocsPreferredVersion, + useDocsVersion, +} from "@docusaurus/plugin-content-docs/client"; +function UnreleasedVersionLabel({ siteTitle, versionMetadata }) { + return ( + {versionMetadata.label}, + }}> + { + "This is the development version of the documentation and some features may not yet be available in the latest release." + } + + ); +} +function UnmaintainedVersionLabel({ siteTitle, versionMetadata }) { + return ( + {versionMetadata.label}, + }}> + { + "This is documentation for the MetaMask Delegation Toolkit version {versionLabel}, which is no longer actively maintained." + } + + ); +} +const BannerLabelComponents = { + unreleased: UnreleasedVersionLabel, + unmaintained: UnmaintainedVersionLabel, +}; +function BannerLabel(props) { + const BannerLabelComponent = + BannerLabelComponents[props.versionMetadata.banner]; + return ; +} +function LatestVersionSuggestionLabel({ versionLabel, to, onClick }) { + return ( + + + latest version + + + ), + }}> + {" You can switch to the {latestVersionLink} ({versionLabel})."} + + ); +} +function DocVersionBannerEnabled({ className, versionMetadata }) { + const { + siteConfig: { title: siteTitle }, + } = useDocusaurusContext(); + const { pluginId } = useActivePlugin({ failfast: true }); + const getVersionMainDoc = (version) => + version.docs.find((doc) => doc.id === version.mainDocId); + const { savePreferredVersionName } = useDocsPreferredVersion(pluginId); + const { latestDocSuggestion, latestVersionSuggestion } = + useDocVersionSuggestions(pluginId); + // Try to link to same doc in latest version (not always possible), falling + // back to main doc of latest version + const latestVersionSuggestedDoc = + latestDocSuggestion ?? getVersionMainDoc(latestVersionSuggestion); + return ( +
+
+ + savePreferredVersionName(latestVersionSuggestion.name)} + /> +
+
+ ); +} +export default function DocVersionBanner({ className }) { + const versionMetadata = useDocsVersion(); + if (versionMetadata.banner) { + return ( + + ); + } + return null; +} diff --git a/wallet/how-to/send-transactions/send-batch-transactions.md b/wallet/how-to/send-transactions/send-batch-transactions.md index b165b27b213..048fb34f999 100644 --- a/wallet/how-to/send-transactions/send-batch-transactions.md +++ b/wallet/how-to/send-transactions/send-batch-transactions.md @@ -14,13 +14,13 @@ You can send and manage batch transactions in MetaMask, using the methods specif ## About atomic batch transactions An atomic batch transaction is a group of transactions that are executed together as a single unit. -When a dapp requests to submit a batch of transactions atomically, MetaMask may prompt users to upgrade their externally owned account (EOA) to a [MetaMask delegator account](https://docs.gator.metamask.io/concepts/delegator-accounts) (or "smart account"). +When a dapp requests to submit a batch of transactions atomically, MetaMask may prompt users to upgrade their externally owned account (EOA) to a [MetaMask smart account](/delegation-toolkit/development/concepts/smart-accounts). If the user accepts, MetaMask proceeds to upgrade the account and process the request as a single atomic transaction as specified by [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702). -:::note Delegator accounts -MetaMask delegator accounts are [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) smart contract accounts (SCAs) that support programmable account behavior and advanced features such as multi-signature approvals, transaction batching, and custom security policies. +:::note 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, transaction batching, and custom security policies. -See the [MetaMask Delegation Toolkit documentation](https://docs.gator.metamask.io/) for more information about delegator accounts and their capabilities. +See the [Delegation Toolkit documentation](/delegation-toolkit) for more information about smart accounts and their capabilities. ::: The key benefits of atomic batch transactions include: @@ -219,7 +219,7 @@ In these cases, `atomic` is `true` but multiple receipts are returned. ## Resources - See the [MetaMask 7702/5792 Readiness dapp](https://7702playground.metamask.io/) to quickly test sending batch transactions. -- See the [MetaMask Delegation Toolkit documentation](https://docs.gator.metamask.io/) for more information about delegator accounts and their capabilities. +- See the [MetaMask Delegation Toolkit documentation](/delegation-toolkit) for more information about smart accounts and their capabilities. - See the following topics in the MetaMask end user documentation: - [What is a smart account?](https://support.metamask.io/configure/accounts/what-is-a-smart-account/) - [How to switch to or revert from a smart account](https://support.metamask.io/configure/accounts/switch-to-or-revert-from-a-smart-account/)