diff --git a/delegation-toolkit/concepts/delegation/caveat-enforcers.md b/delegation-toolkit/concepts/delegation/caveat-enforcers.md index c46efc0ab03..4a551111ef6 100644 --- a/delegation-toolkit/concepts/delegation/caveat-enforcers.md +++ b/delegation-toolkit/concepts/delegation/caveat-enforcers.md @@ -118,7 +118,7 @@ This "all-or-nothing" approach ensures that delegations only execute exactly as ## Caveat builder -While caveat enforcers operate at the smart contract level, most developers interact with them through the [`CaveatBuilder`](../../guides/delegation/restrict-delegation.md) interface in the MetaMask Delegation Toolkit. +While caveat enforcers operate at the smart contract level, most developers interact with them through the `CaveatBuilder` interface in the MetaMask Delegation Toolkit. The `CaveatBuilder` provides a developer-friendly TypeScript API that: @@ -126,7 +126,7 @@ The `CaveatBuilder` provides a developer-friendly TypeScript API that: - 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` +Each [caveat type](../../reference/delegation/caveats.md) in the `CaveatBuilder` corresponds to a specific caveat enforcer contract. For example, when you use: ```typescript @@ -134,7 +134,7 @@ caveatBuilder.addCaveat("allowedTargets", ["0xc11F3a8E5C7D16b75c9E2F60d26f5321C6 ``` The builder is creating a caveat that references the -[`AllowedTargetsEnforcer`](../../reference/caveats.md#allowedtargets) contract address and +[`AllowedTargetsEnforcer`](../../reference/delegation/caveats.md#allowedtargets) contract address and properly encodes the provided addresses as terms for that enforcer. ## Caveat enforcer best practices @@ -144,14 +144,14 @@ 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. + For example, using [`NativeTokenPaymentEnforcer`](../../reference/delegation/caveats.md#nativetokenpayment) before + [`NativeBalanceChangeEnforcer`](../../reference/delegation/caveats.md#nativebalancechange) might cause validation failures. - **Be careful with unbounded delegations** - Always include appropriate caveat enforcers to limit what a delegate can do. ## Available caveat enforcers -The Delegation Toolkit provides [out-of-the-box caveat enforcers](../../reference/caveats.md) +The Delegation Toolkit provides [out-of-the-box caveat enforcers](../../reference/delegation/caveats.md) for common restriction patterns, including: - Limiting target addresses and methods. diff --git a/delegation-toolkit/concepts/delegation/index.md b/delegation-toolkit/concepts/delegation/index.md index 4c47510f2fb..992fb3937a5 100644 --- a/delegation-toolkit/concepts/delegation/index.md +++ b/delegation-toolkit/concepts/delegation/index.md @@ -39,20 +39,20 @@ You can create the following delegation types: In a chain of delegations, the first delegation is the root delegation. For example, Alice delegates the ability to spend her USDC to Bob, limiting the amount to 100 USDC. - Use [`createDelegation`](../../reference/api/delegation.md#createdelegation) to create a root delegation. + Use [`createDelegation`](../../reference/delegation/index.md#createdelegation) to create a root delegation. - **Open root delegation** - An open root delegation is a root delegation that doesn't specify a delegate. This means that any account can redeem the delegation. For example, Alice delegates the ability to spend 100 of her USDC to anyone. You must create open root delegations carefully, to ensure that they are not misused. - Use [`createOpenDelegation`](../../reference/api/delegation.md#createopendelegation) to create an open root delegation. + Use [`createOpenDelegation`](../../reference/delegation/index.md#createopendelegation) to create an open root delegation. - **Redelegation** - A delegate can redelegate permissions that have been granted to them, creating a chain of delegations across trusted parties. For example, Alice delegates the ability to spend 100 of her USDC to Bob. Bob redelegates the ability to spend 50 of Alice's 100 USDC to Carol. - Use [`createDelegation`](../../reference/api/delegation.md#createdelegation) to create a redelegation. + Use [`createDelegation`](../../reference/delegation/index.md#createdelegation) to create a redelegation. - **Open redelegation** - An open redelegation is a redelegation that doesn't specify a delegate. This means that any account can redeem the redelegation. @@ -60,7 +60,7 @@ You can create the following delegation types: Bob redelegates the ability to spend 50 of Alice's 100 USDC to anyone. As with open root delegations, you must create open redelegations carefully, to ensure that they are not misused. - Use [`createOpenDelegation`](../../reference/api/delegation.md#createopendelegation) to create an open redelegation. + Use [`createOpenDelegation`](../../reference/delegation/index.md#createopendelegation) to create an open redelegation. ## Delegation Framework @@ -79,7 +79,7 @@ It consists of the following components: - **Delegation Manager** - The Delegation Manager validates delegations and triggers executions on behalf of the delegator, ensuring tasks are executed accurately and securely. - When you redeem a delegation using [`redeemDelegations`](../../reference/api/delegation.md#redeemdelegations), the Delegation Manager performs the following steps. + When you redeem a delegation using [`redeemDelegations`](../../reference/delegation/index.md#redeemdelegations), the Delegation Manager performs the following steps. It processes a single step for all redemptions before proceeding to the next one: 1. Validates the input data by ensuring the lengths of `delegations`, `modes`, and @@ -150,19 +150,19 @@ sequenceDiagram ## Execution modes -When redeeming a delegation using [`redeemDelegations`](../../reference/api/delegation.md#redeemdelegations), you must pass an execution mode for each delegation chain you pass to the method. +When redeeming a delegation using [`redeemDelegations`](../../reference/delegation/index.md#redeemdelegations), you must pass an execution mode for each delegation chain you pass to the method. The Delegation Toolkit supports the following execution modes, based on [ERC-7579](https://erc7579.com/): | Execution mode | Number of delegation chains passed to `redeemDelegations` | Processing method | Does user operation continue execution if redemption reverts? | |--|--|--|--| -| `SINGLE_DEFAULT_MODE` | One | Sequential | No | -| `SINGLE_TRY_MODE` | One | Sequential | Yes | -| `BATCH_DEFAULT_MODE` | Multiple | Interleaved | No | -| `BATCH_TRY_MODE` | Multiple | Interleaved | Yes | +| `SingleDefault` | One | Sequential | No | +| `SingleTry` | One | Sequential | Yes | +| `BatchDefault` | Multiple | Interleaved | No | +| `BatchTry` | Multiple | Interleaved | Yes | ### Sequential processing -In `SINGLE` modes, processing is sequential: +In `Single` modes, processing is sequential: 1. For each delegation in the chain, all caveats' `before` hooks are called. 2. The single redeemed action is executed. @@ -170,10 +170,10 @@ In `SINGLE` modes, processing is sequential: ### Interleaved processing -In `BATCH` modes, processing is interleaved: +In `Batch` modes, processing is interleaved: 1. For each chain in the batch, and each delegation in the chain, all caveats' `before` hooks are called. 2. Each redeemed action is executed. 3. For each chain in the batch, and each delegation in the chain, all caveats' `after` hooks are called. -`BATCH` mode allows for powerful use cases, but the Delegation Framework currently does not include any `BATCH` compatible caveat enforcers. +`Batch` mode allows for powerful use cases, but the Delegation Framework currently does not include any `Batch` compatible caveat enforcers. diff --git a/delegation-toolkit/concepts/smart-accounts.md b/delegation-toolkit/concepts/smart-accounts.md index d40cbfc7bd5..489b382ad06 100644 --- a/delegation-toolkit/concepts/smart-accounts.md +++ b/delegation-toolkit/concepts/smart-accounts.md @@ -43,7 +43,7 @@ See [Create a smart account](../guides/smart-accounts/create-smart-account.md) t ### Hybrid smart account The Hybrid smart account is a flexible implementation that supports both an externally owned account (EOA) owner and any number of passkey (WebAuthn) signers. -You can configure any of these signers as the signatory, and use them to sign any data, including user operations, on behalf of the smart account. +You can configure any of these signers, and use them to sign any data, including user operations, on behalf of the smart account. This type is referenced in the toolkit as `Implementation.Hybrid`. @@ -79,7 +79,7 @@ The MetaMask Smart Accounts flow is as follows: 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. + necessary details and signed by the configured signers. 3. **Bundlers and mempool** - The signed user operation is submitted to a special mempool, where bundlers collect and package multiple user operations into a single transaction to save on gas costs. diff --git a/delegation-toolkit/experimental/erc-7710-redeem-delegations.md b/delegation-toolkit/experimental/erc-7710-redeem-delegations.md index 36d7b6ded0a..7cfe5fb23f5 100644 --- a/delegation-toolkit/experimental/erc-7710-redeem-delegations.md +++ b/delegation-toolkit/experimental/erc-7710-redeem-delegations.md @@ -65,7 +65,7 @@ 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`](../reference/api/experimental-actions/bundler-client.md#senduseroperationwithdelegation)). +These calls must be executed before redeeming the permission (this is handled for you in [`sendUserOperationWithDelegation`](../reference/erc7715/bundler-client.md#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: @@ -86,7 +86,7 @@ Redeem a delegation with a [MetaMask smart account](#redeem-with-a-metamask-smar To redeem a delegation with a MetaMask smart account, [create a smart account](../guides/smart-accounts/create-smart-account.md) 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`](../reference/api/experimental-actions/bundler-client.md#senduseroperationwithdelegation) to redeem the permission. +After setting up your Bundler Client, you can extend its functionality with `erc7710BundlerActions` actions to support ERC-7710. Once extended, use [`sendUserOperationWithDelegation`](../reference/erc7715/bundler-client.md#senduseroperationwithdelegation) to redeem the permission. @@ -146,7 +146,7 @@ export const sessionAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", - signatory: { account }, + signer: { account }, }); export const bundlerClient = createBundlerClient({ @@ -168,7 +168,7 @@ export const bundlerClient = createBundlerClient({ 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`](../reference/api/experimental-actions/wallet-client.md#sendtransactionwithdelegation) to redeem the permission. +After creating your Wallet Client, you can extend its functionality with `erc7710WalletActions` actions to support ERC-7710. Once extended, use [`sendTransactionWithDelegation`](../reference/erc7715/wallet-client.md#sendtransactionwithdelegation) to redeem the permission. diff --git a/delegation-toolkit/experimental/erc-7715-request-permissions.md b/delegation-toolkit/experimental/erc-7715-request-permissions.md index 4b47d770c8d..d540dc377f7 100644 --- a/delegation-toolkit/experimental/erc-7715-request-permissions.md +++ b/delegation-toolkit/experimental/erc-7715-request-permissions.md @@ -80,7 +80,7 @@ export const sessionAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", - signatory: { account }, + signer: { account }, }); export const walletClient = createWalletClient({ @@ -165,7 +165,7 @@ You should always verify the granted permissions and adjust your dapp's behavior ## 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`](../reference/api/experimental-actions/bundler-client.md#senduseroperationwithdelegation)). +These calls must be executed before redeeming the permission (this is handled for you in [`sendUserOperationWithDelegation`](../reference/erc7715/bundler-client.md#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: diff --git a/delegation-toolkit/get-started/erc7715-quickstart.md b/delegation-toolkit/get-started/erc7715-quickstart.md index 7ee8c0bbb9a..dc0476a5c0b 100644 --- a/delegation-toolkit/get-started/erc7715-quickstart.md +++ b/delegation-toolkit/get-started/erc7715-quickstart.md @@ -80,7 +80,7 @@ const sessionAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", - signatory: { account }, + signer: { account }, }); ``` diff --git a/delegation-toolkit/get-started/smart-account-quickstart/eip7702.md b/delegation-toolkit/get-started/smart-account-quickstart/eip7702.md index b4a02a521aa..c2e7258eb2d 100644 --- a/delegation-toolkit/get-started/smart-account-quickstart/eip7702.md +++ b/delegation-toolkit/get-started/smart-account-quickstart/eip7702.md @@ -135,7 +135,7 @@ const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Stateless7702, address, - signatory: { walletClient }, + signer: { walletClient }, }); ``` diff --git a/delegation-toolkit/get-started/smart-account-quickstart/index.md b/delegation-toolkit/get-started/smart-account-quickstart/index.md index 5a72c500f2f..8056ae82d7a 100644 --- a/delegation-toolkit/get-started/smart-account-quickstart/index.md +++ b/delegation-toolkit/get-started/smart-account-quickstart/index.md @@ -68,7 +68,7 @@ const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", - signatory: { account }, + signer: { account }, }); ``` diff --git a/delegation-toolkit/guides/configure-toolkit.md b/delegation-toolkit/guides/configure-toolkit.md index 0d9d3c00c84..d2c81f94590 100644 --- a/delegation-toolkit/guides/configure-toolkit.md +++ b/delegation-toolkit/guides/configure-toolkit.md @@ -103,7 +103,7 @@ const delegatorSmartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [delegatorAccount.address, [], [], []], deploySalt: "0x", - signatory: { account: delegatorAccount }, + signer: { account: delegatorAccount }, }); export delegatorSmartAccount; @@ -116,7 +116,7 @@ export delegatorSmartAccount; See the changelog of the toolkit version you are using (in the left sidebar) for supported chains. ::: -Alternatively, you can use the [`getDelegatorEnvironment`](../reference/api/delegation.md#getdelegatorenvironment) function to resolve the environment. +Alternatively, you can use the [`getDelegatorEnvironment`](../reference/delegation/index.md#getdelegatorenvironment) function to resolve the environment. This function is especially useful if your delegator is not a smart account when creating a [redelegation](../concepts/delegation/index.md#delegation-types). @@ -132,7 +132,7 @@ const environment: DeleGatorEnvironment = getDelegatorEnvironment(11155111); ### Deploy a custom environment -You can deploy the contracts using any method, but the toolkit provides a convenient [`deployDelegatorEnvironment`](../reference/api/delegation.md#deploydelegatorenvironment) function. This function simplifies deploying the Delegation Framework contracts to your desired EVM chain. +You can deploy the contracts using any method, but the toolkit provides a convenient [`deployDelegatorEnvironment`](../reference/delegation/index.md#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`. diff --git a/delegation-toolkit/guides/delegation/check-delegation-state.md b/delegation-toolkit/guides/delegation/check-delegation-state.md new file mode 100644 index 00000000000..b356a96b717 --- /dev/null +++ b/delegation-toolkit/guides/delegation/check-delegation-state.md @@ -0,0 +1,104 @@ +--- +description: Learn how to check the delegation state. +sidebar_label: Check the delegation state +toc_max_heading_level: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Check the delegation state + +When using [spending limit delegation scopes](use-delegation-scopes/spending-limit.md) or relevant [caveat enforcers](../../reference/delegation/caveats.md), +you might need to check the remaining transferrable amount in a delegation. +For example, if a delegation allows a user to spend 10 USDC per week and they have already spent 10 - n USDC in the current period, +you can determine how much of the allowance is still available for transfer. + +Use the `CaveatEnforcerClient` to check the available balances for specific scopes or caveats. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../get-started/install.md) +- [Create a delegator account.](execute-on-smart-accounts-behalf.md#3-create-a-delegator-account) +- [Create a delegate account.](execute-on-smart-accounts-behalf.md#4-create-a-delegate-account) +- [Create a delegation with an ERC-20 periodic scope.](use-delegation-scopes/spending-limit.md#erc-20-periodic-scope) + +## Create a `CaveatEnforcerClient` + +To check the delegation state, create a [`CaveatEnforcerClient`](../../reference/delegation/caveat-enforcer-client.md). +This client allows you to interact with the caveat enforcers of the delegation, and read the required state. + + + + +```typescript +import { environment, publicClient as client } from './config.ts' +import { createCaveatEnforcerClient } from '@metamask/delegation-toolkit' + +const caveatEnforcerClient = createCaveatEnforcerClient({ + environment, + client, +}) +``` + + + + +```typescript +import { sepolia as chain } from 'viem/chains' +import { createPublicClient, http } from 'viem' +import { getDeleGatorEnvironment } from '@metamask/delegation-toolkit' + +export const environment = getDeleGatorEnvironment(chain.id) + +export const publicClient = createPublicClient({ + chain, + transport: http(), +}) +``` + + + + +## Read the caveat enforcer state + +This example uses the [`getErc20PeriodTransferEnforcerAvailableAmount`](../../reference/delegation/caveat-enforcer-client.md#geterc20periodtransferenforceravailableamount) method to read the state and retrieve the remaining amount for the current transfer period. + + + + +```typescript +import { delegation } './config.ts' + +// Returns the available amount for current period. +const { availableAmount } = await caveatEnforcerClient.getErc20PeriodTransferEnforcerAvailableAmount({ + delegation, +}) +``` + + + + +```typescript +import { createDelegation } from '@metamask/delegation-toolkit' + +export const delegation = createDelegation({ + scope: { + type: 'erc20PeriodTransfer', + tokenAddress: '0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da', + periodAmount: 1000000000000000000n, + periodDuration: 86400, + startDate: 1743763600, + }, + to: delegateAccount, + from: delegatorAccount, + environment: delegatorAccount.environment, +}) +``` + + + + +## Next steps + +See the [Caveat Enforcer Client reference](../../reference/delegation/caveat-enforcer-client.md) for the full list of available methods. diff --git a/delegation-toolkit/guides/delegation/execute-on-smart-accounts-behalf.md b/delegation-toolkit/guides/delegation/execute-on-smart-accounts-behalf.md index 2e067e886d3..4725e343241 100644 --- a/delegation-toolkit/guides/delegation/execute-on-smart-accounts-behalf.md +++ b/delegation-toolkit/guides/delegation/execute-on-smart-accounts-behalf.md @@ -51,10 +51,10 @@ const bundlerClient = createBundlerClient({ ### 3. Create a delegator account Create an account to represent Alice, the delegator who will create a delegation. -The delegator must be a MetaMask smart account; use the toolkit's [`toMetaMaskSmartAccount`](../../reference/api/smart-account.md#tometamasksmartaccount) method to create the delegator account. +The delegator must be a MetaMask smart account; use the toolkit's [`toMetaMaskSmartAccount`](../../reference/smart-account.md#tometamasksmartaccount) method to create the delegator account. A Hybrid smart account is a flexible smart account implementation that supports both an externally owned account (EOA) owner and any number of P256 (passkey) signers. -This examples configures a [Hybrid smart account with an Account signatory](../smart-accounts/create-smart-account.md#create-a-hybrid-smart-account-with-an-account-signatory): +This examples configures a [Hybrid smart account with an Account signer](../smart-accounts/create-smart-account.md#create-a-hybrid-smart-account-with-an-account-signer): ```typescript import { Implementation, toMetaMaskSmartAccount } from "@metamask/delegation-toolkit" @@ -67,7 +67,7 @@ const delegatorSmartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [delegatorAccount.address, [], [], []], deploySalt: "0x", - signatory: { account: delegatorAccount }, + signer: { account: delegatorAccount }, }) ``` @@ -93,7 +93,7 @@ const delegateSmartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, // Hybrid smart account deployParams: [delegateAccount.address, [], [], []], deploySalt: "0x", - signatory: { account: delegateAccount }, + signer: { account: delegateAccount }, }) ``` @@ -122,9 +122,11 @@ export const delegateWalletClient = createWalletClient({ Create a [root delegation](../../concepts/delegation/index.md#delegation-types) from Alice to Bob. With a root delegation, Alice is delegating her own authority away, as opposed to *redelegating* permissions she received from a previous delegation. -Use the toolkit's [`createDelegation`](../../reference/api/delegation.md#createdelegation) method to create a root delegation. -This example passes an empty `caveats` array, which means Bob can perform any action on Alice's behalf. We recommend [restricting the delegation](restrict-delegation.md) by adding caveat enforcers. -For example, Alice can delegate the ability to spend her USDC to Bob, limiting the amount to 100 USDC. +Use the toolkit's [`createDelegation`](../../reference/delegation/index.md#createdelegation) method to create a root delegation. When creating +delegation, you need to configure the scope of the delegation to define the initial authority. + +This example uses the [`erc20TransferAmount`](use-delegation-scopes/spending-limit.md#erc-20-transfer-scope) scope, allowing Alice to delegate to Bob the ability to spend her USDC, with a +specified limit on the total amount. :::warning Important @@ -135,16 +137,24 @@ Before creating a delegation, ensure that the delegator account (in this example ```typescript import { createDelegation } from "@metamask/delegation-toolkit" +// USDC address on Ethereum Sepolia. +const tokenAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; + const delegation = createDelegation({ to: delegateSmartAccount.address, // This example uses a delegate smart account from: delegatorSmartAccount.address, - caveats: [], // Empty caveats array - we recommend adding appropriate restrictions. + environment: delegatorSmartAccount.environment + scope: { + type: "erc20TransferAmount", + tokenAddress, + maxAmount: 10000000n, + }, }) ``` ### 6. Sign the delegation -Sign the delegation with Alice's account, using the [`signDelegation`](../../reference/api/smart-account.md#signdelegation) method from `MetaMaskSmartAccount`. Alternatively, you can use the toolkit's [`signDelegation`](../../reference/api/delegation.md#signdelegation) utility method. Bob will later use the signed delegation to perform actions on Alice's behalf. +Sign the delegation with Alice's account, using the [`signDelegation`](../../reference/smart-account.md#signdelegation) method from `MetaMaskSmartAccount`. Alternatively, you can use the toolkit's [`signDelegation`](../../reference/delegation/index.md#signdelegation) utility method. Bob will later use the signed delegation to perform actions on Alice's behalf. ```typescript const signature = await delegatorSmartAccount.signDelegation({ @@ -161,8 +171,8 @@ const signedDelegation = { Bob can now redeem the delegation. The redeem transaction is sent to the `DelegationManager` contract, which validates the delegation and executes actions on Alice's behalf. -To prepare the calldata for the redeem transaction, use the [`redeemDelegations`](../../reference/api/delegation.md#redeemdelegations) method from `DelegationManager`. -Since Bob is redeeming a single delegation chain, use the [`SINGLE_DEFAULT_MODE`](../../concepts/delegation/index.md#execution-modes) execution mode. +To prepare the calldata for the redeem transaction, use the [`redeemDelegations`](../../reference/delegation/index.md#redeemdelegations) method from `DelegationManager`. +Since Bob is redeeming a single delegation chain, use the [`SingleDefault`](../../concepts/delegation/index.md#execution-modes) execution mode. Bob can redeem the delegation by submitting a user operation if his account is a smart account, or a regular transaction if his account is an EOA: @@ -170,9 +180,8 @@ Bob can redeem the delegation by submitting a user operation if his account is a ```typescript -import { createExecution } from "@metamask/delegation-toolkit" +import { createExecution, ExecutionMode } from "@metamask/delegation-toolkit" import { DelegationManager } from "@metamask/delegation-toolkit/contracts" -import { SINGLE_DEFAULT_MODE } from "@metamask/delegation-toolkit/utils" import { zeroAddress } from "viem" const delegations = [signedDelegation] @@ -181,7 +190,7 @@ const executions = createExecution({ target: zeroAddress }) const redeemDelegationCalldata = DelegationManager.encode.redeemDelegations({ delegations: [delegations], - modes: [SINGLE_DEFAULT_MODE], + modes: [ExecutionMode.SingleDefault], executions: [executions], }) @@ -202,9 +211,8 @@ const userOperationHash = await bundlerClient.sendUserOperation({ ```typescript -import { createExecution, getDeleGatorEnvironment } from "@metamask/delegation-toolkit" +import { createExecution, getDeleGatorEnvironment, ExecutionMode } from "@metamask/delegation-toolkit" import { DelegationManager } from "@metamask/delegation-toolkit/contracts" -import { SINGLE_DEFAULT_MODE } from "@metamask/delegation-toolkit/utils" import { zeroAddress } from "viem" const delegations = [signedDelegation] @@ -213,7 +221,7 @@ const executions = createExecution({ target: zeroAddress }) const redeemDelegationCalldata = DelegationManager.encode.redeemDelegations({ delegations: [delegations], - modes: [SINGLE_DEFAULT_MODE], + modes: [ExecutionMode.SingleDefault], executions: [executions] }); @@ -226,3 +234,8 @@ const transactionHash = await delegateWalletClient.sendTransaction({ + +## Next steps + +- See [how to configure different scopes](use-delegation-scopes/index.md) to define the initial authority of a delegation. +- See [how to further refine the authority of a delegation](use-delegation-scopes/constrain-scope.md) using caveat enforcers. \ No newline at end of file diff --git a/delegation-toolkit/guides/delegation/restrict-delegation.md b/delegation-toolkit/guides/delegation/restrict-delegation.md deleted file mode 100644 index d78aa669c67..00000000000 --- a/delegation-toolkit/guides/delegation/restrict-delegation.md +++ /dev/null @@ -1,108 +0,0 @@ ---- -description: Learn how to restrict a delegation using caveat enforcers, and the available caveat types. -toc_max_heading_level: 3 ---- - -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Restrict a delegation - -Use [caveat enforcers](../../concepts/delegation/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`](../../reference/api/delegation.md#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 once. - .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 specific or custom control, you can also [create custom caveat enforcers](/tutorials/create-custom-caveat-enforcer) -and add them to the caveat builder. diff --git a/delegation-toolkit/guides/delegation/use-delegation-scopes/constrain-scope.md b/delegation-toolkit/guides/delegation/use-delegation-scopes/constrain-scope.md new file mode 100644 index 00000000000..db2fd94e6b2 --- /dev/null +++ b/delegation-toolkit/guides/delegation/use-delegation-scopes/constrain-scope.md @@ -0,0 +1,63 @@ +--- +description: Learn how to constrain a delegation scope using caveat enforcers. +sidebar_label: Constrain a scope +toc_max_heading_level: 3 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Constrain a delegation scope + +[Delegation scopes](index.md) define the delegation's initial authority and help prevent delegation misuse. +You can further constrain these scopes and limit the delegation's authority by applying [caveat enforcers](../../../concepts/delegation/caveat-enforcers.md). + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../../get-started/install.md) +- [Create a delegator account.](../execute-on-smart-accounts-behalf.md#3-create-a-delegator-account) +- [Create a delegate account.](../execute-on-smart-accounts-behalf.md#4-create-a-delegate-account) +- [Configure a delegation scope.](index.md) + +## Apply a caveat enforcer + +For example, Alice creates a delegation with an [ERC-20 transfer scope](spending-limit.md#erc-20-transfer-scope) that allows Bob to spend up to 10 USDC. +If Alice wants to further restrict the scope to limit Bob's delegation to be valid for only seven days, +she can apply the [`timestamp`](../../../reference/delegation/caveats.md#timestamp) caveat enforcer. + +The following example creates a delegation using [`createDelegation`](../../../reference/delegation/index.md#createdelegation), applies the ERC-20 transfer scope with a spending limit of 10 USDC, and applies the `timestamp` caveat enforcer to restrict the delegation's validity to a seven-day period: + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +// Convert milliseconds to seconds. +const currentTime = Math.floor(Date.now() / 1000); + +// Seven days after current time. +const beforeThreshold = currentTime + 604800; + +const caveats = [{ + type: "timestamp", + afterThreshold: currentTime, + beforeThreshold, +}]; + +const delegation = createDelegation({ + scope: { + type: "erc20TransferAmount", + tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + maxAmount: 10000n, + }, + // Apply caveats to the delegation. + caveats, + to: delegateAccount, + from: delegatorAccount, + environment: delegatorAccount.environment, +}); +``` + +## Next steps + +- See the [caveats reference](../../../reference/delegation/caveats.md) for the full list of caveat types and their parameters. +- For more specific or custom control, you can also [create custom caveat enforcers](/tutorials/create-custom-caveat-enforcer) +and apply them to delegations. diff --git a/delegation-toolkit/guides/delegation/use-delegation-scopes/function-call.md b/delegation-toolkit/guides/delegation/use-delegation-scopes/function-call.md new file mode 100644 index 00000000000..b44a01c6b70 --- /dev/null +++ b/delegation-toolkit/guides/delegation/use-delegation-scopes/function-call.md @@ -0,0 +1,47 @@ +--- +description: Learn how to use the function call scope for a delegation. +--- + +# Use the function call scope + +The function call scope defines the specific methods, contract addresses, and calldata that are allowed for the delegation. +For example, Alice delegates to Bob the ability to call the `approve` function on the USDC contract, with the approval amount set to `0`. + +Internally, this scope uses the [`allowedTargets`](../../../reference/delegation/caveats.md#allowedtargets) and [`allowedMethods`](../../../reference/delegation/caveats.md#allowedmethods) caveat enforcers, and +optionally uses the [`allowedCalldata`](../../../reference/delegation/caveats.md#allowedcalldata) or [`exactCalldata`](../../../reference/delegation/caveats.md#exactcalldata) caveat enforcers when those parameters are specified. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../../get-started/install.md) +- [Configure the Delegation Toolkit.](../../configure.md) +- [Create a delegator account.](../execute-on-smart-accounts-behalf.md#3-create-a-delegator-account) +- [Create a delegate account.](../execute-on-smart-accounts-behalf.md#4-create-a-delegate-account) + +## Function call scope + +This scope requires `targets` and `selectors` as mandatory parameters for the configuration. +You can specify the allowed methods in `selectors` and the permitted contract addresses in `targets`. + +The following example sets the delegation scope to allow the delegate to call the `approve` function on the USDC token contract. + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +// USDC address on Sepolia. +const USDC_ADDRESS = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" + +const delegation = createDelegation({ + scope: { + type: "functionCall", + targets: [USDC_ADDRESS], + selectors: ["approve(address, uint256)"] + }, + to: delegateAccount, + from: delegatorAccount, + environment: delegatorAccount.environment, +}); +``` + +## Next steps + +See [how to further constrain the authority of a delegation](constrain-scope.md) using caveat enforcers. diff --git a/delegation-toolkit/guides/delegation/use-delegation-scopes/index.md b/delegation-toolkit/guides/delegation/use-delegation-scopes/index.md new file mode 100644 index 00000000000..1822d4975b8 --- /dev/null +++ b/delegation-toolkit/guides/delegation/use-delegation-scopes/index.md @@ -0,0 +1,16 @@ +--- +description: Learn how to use the delegation scopes. +--- + +# Use delegation scopes + +When [creating a delegation](../execute-on-smart-accounts-behalf.md), you can configure a scope to define the delegation's initial authority and help prevent delegation misuse. +You can further constrain this initial authority by [adding caveats to a delegation](constrain-scope.md). + +The Delegation Toolkit currently supports three categories of scopes: + +| Scope type | Description | +|------------|-------------| +| [Spending limit scopes](spending-limit.md) | Restricts the spending of native, ERC-20, and ERC-721 tokens based on defined conditions. | +| [Function call scope](function-call.md) | Restricts the delegation to specific contract methods, contract addresses, or calldata. | +| [Ownership transfer scope](ownership-transfer.md) | Restricts the delegation to only allow ownership transfers, specifically the `transferOwnership` function for a specified contract. | diff --git a/delegation-toolkit/guides/delegation/use-delegation-scopes/ownership-transfer.md b/delegation-toolkit/guides/delegation/use-delegation-scopes/ownership-transfer.md new file mode 100644 index 00000000000..d51de5ef9fc --- /dev/null +++ b/delegation-toolkit/guides/delegation/use-delegation-scopes/ownership-transfer.md @@ -0,0 +1,41 @@ +--- +description: Learn how to use the ownership transfer scope for a delegation. +--- + +# Use the ownership transfer scope + +The ownership transfer scope restricts a delegation to ownership transfer calls only. +For example, Alice has deployed a smart contract, and she delegates to Bob the ability to transfer ownership of that contract. + +Internally, this scope uses the [`ownershipTransfer`](../../../reference/delegation/caveats.md#ownershiptransfer) caveat enforcer. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../../get-started/install.md) +- [Configure the Delegation Toolkit.](../../configure.md) +- [Create a delegator account.](../execute-on-smart-accounts-behalf.md#3-create-a-delegator-account) +- [Create a delegate account.](../execute-on-smart-accounts-behalf.md#4-create-a-delegate-account) + +## Ownership transfer scope + +This scope requires a `contractAddress`, which represents the address of the deployed contract. + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +const contractAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" + +const delegation = createDelegation({ + scope: { + type: "ownershipTransfer", + contractAddress, + }, + to: delegateAccount, + from: delegatorAccount, + environment: delegatorAccount.environment, +}); +``` + +## Next steps + +See [how to further constrain the authority of a delegation](constrain-scope.md) using caveat enforcers. diff --git a/delegation-toolkit/guides/delegation/use-delegation-scopes/spending-limit.md b/delegation-toolkit/guides/delegation/use-delegation-scopes/spending-limit.md new file mode 100644 index 00000000000..15bb029f108 --- /dev/null +++ b/delegation-toolkit/guides/delegation/use-delegation-scopes/spending-limit.md @@ -0,0 +1,207 @@ +--- +description: Learn how to use the spending limit scopes for a delegation. +--- + +# Use spending limit scopes + +Spending limit scopes define how much a delegate can spend in native, ERC-20, or ERC-721 tokens. +You can set transfer limits with or without time-based (periodic) or streaming conditions, depending on your use case. + +## Prerequisites + +- [Install and set up the Delegation Toolkit.](../../../get-started/install.md) +- [Configure the Delegation Toolkit.](../../configure.md) +- [Create a delegator account.](../execute-on-smart-accounts-behalf.md#3-create-a-delegator-account) +- [Create a delegate account.](../execute-on-smart-accounts-behalf.md#4-create-a-delegate-account) + +## ERC-20 periodic scope + +This scope ensures a per-period limit for ERC-20 token transfers. +You set the amount and the time window. +At the start of each new period, the allowance resets. +For example, Alice creates a delegation that lets Bob spend up to 10 USDC on her behalf each day. +Bob can transfer a total of 10 USDC per day; the limit resets at the beginning of the next day. + +When this scope is applied, the toolkit automatically disallows native token transfers (sets the native token transfer limit to `0`). + +Internally, this scope uses the [`erc20PeriodTransfer`](../../../reference/delegation/caveats.md#erc20periodtransfer) and [`valueLte`](../../../reference/delegation/caveats.md#valuelte) caveat enforcers. + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +const delegation = createDelegation({ + scope: { + type: "erc20PeriodTransfer", + tokenAddress: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + periodAmount: 1000000000000000000n, + periodDuration: 86400, + startDate: 1743763600, + }, + to: delegateAccount, + from: delegatorAccount, + environment: delegatorAccount.environment, +}); +``` + +## ERC-20 streaming scope + +This scopes ensures a linear streaming transfer limit for ERC-20 tokens. +Token transfers are blocked until the defined start timestamp. +At the start, a specified initial amount is released, after which tokens accrue linearly at the configured rate, up to the maximum allowed amount. +For example, Alice creates a delegation that allows Bob to spend 0.1 USDC per second, starting with an initial amount of 10 USDC, up to a maximum of 100 USDC. + +When this scope is applied, the toolkit automatically disallows native token transfers (sets the native token transfer limit to `0`). + +Internally, this scope uses the [`erc20Streaming`](../../../reference/delegation/caveats.md#erc20streaming) and [`valueLte`](../../../reference/delegation/caveats.md#valuelte) caveat enforcers. + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +const delegation = createDelegation({ + scope: { + type: "erc20Streaming", + tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + amountPerSecond: 100n, + initialAmount: 1000000n, + maxAmount: 10000000n, + startTime: 1703980800, + }, + to: delegateAccount, + from: delegatorAccount, + environment: delegatorAccount.environment, +}); +``` + +## ERC-20 transfer scope + +This scope ensures that ERC-20 token transfers are limited to a predefined maximum amount. +This scope is useful for setting simple, fixed transfer limits without any time based or streaming conditions. +For example, Alice creates a delegation that allows Bob to spend up to 10 USDC without any conditions. +Bob may use the 10 USDC in a single transaction or make multiple transactions, as long as the total does not exceed 10 USDC. + +When this scope is applied, the toolkit automatically disallows native token transfers (sets the native token transfer limit to `0`). + +Internally, this scope uses the [`erc20TransferAmount`](../../../reference/delegation/caveats.md#erc20transferamount) and [`valueLte`](../../../reference/delegation/caveats.md#valuelte) caveat enforcers. + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +const delegation = createDelegation({ + scope: { + type: "erc20TransferAmount", + tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + maxAmount: 10000n, + }, + to: delegateAccount, + from: delegatorAccount, + environment: delegatorAccount.environment, +}); +``` + +## ERC-721 scope + +This scope limits the delegation to ERC-721 token transfers only. +For example, Alice creates a delegation that allows Bob to transfer an NFT she owns on her behalf. + +Internally, this scope uses the [`erc721Transfer`](../../../reference/delegation/caveats.md#erc721transfer) caveat enforcer. + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +const delegation = createDelegation({ + scope: { + type: "erc721Transfer", + tokenAddress: "0x3fF528De37cd95b67845C1c55303e7685c72F319", + tokenId: 1n, + }, + to: delegateAccount, + from: delegatorAccount, + environment: delegatorAccount.environment, +}); +``` + +## Native token periodic scope + +This scope ensures a per-period limit for native token transfers. +You set the amount and the time window. +At the start of each new period, the allowance resets. +For example, Alice creates a delegation that lets Bob spend up to 0.01 ETH on her behalf each day. +Bob can transfer a total of 0.01 ETH per day; the limit resets at the beginning of the next day. + +When this scope is applied, the toolkit disallows ERC-20 and ERC-721 token transfers by default, setting the allowed `calldata` to `0x`. + +Internally, this scope uses the [`exactCalldata`](../../../reference/delegation/caveats.md#exactcalldata) and [`nativeTokenPeriodTransfer`](../../../reference/delegation/caveats.md#nativetokenperiodtransfer) caveat enforcers. + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +const delegation = createDelegation({ + scope: { + type: "nativeTokenPeriodTransfer", + periodAmount: 1000000000000000000n, + periodDuration: 86400, + startDate: 1743763600, + }, + to: delegateAccount, + from: delegatorAccount, + environment: delegatorAccount.environment, +}); +``` + +## Native token streaming scope + +This scopes ensures a linear streaming transfer limit for native tokens. +Token transfers are blocked until the defined start timestamp. +At the start, a specified initial amount is released, after which tokens accrue linearly at the configured rate, up to the maximum allowed amount. +For example, Alice creates delegation that allows Bob to spend 0.001 ETH per second, starting with an initial amount of 0.01 ETH, up to a maximum of 0.1 ETH. + +When this scope is applied, the toolkit disallows ERC-20 and ERC-721 token transfers by default, setting the allowed `calldata` to `0x`. + +Internally, this scope uses the [`exactCalldata`](../../../reference/delegation/caveats.md#exactcalldata) and [`nativeTokenStreaming`](../../../reference/delegation/caveats.md#nativetokenstreaming) caveat enforcers. + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +const delegation = createDelegation({ + scope: { + type: "nativeTokenStreaming", + amountPerSecond: 100n, + initialAmount: 1000000n, + maxAmount: 10000000n, + startTime: 1703980800, + }, + to: delegateAccount, + from: delegatorAccount, + environment: delegatorAccount.environment, +}); +``` + +## Native token transfer scope + +This scope ensures that native token transfers are limited to a predefined maximum amount. +This scope is useful for setting simple, fixed transfer limits without any time based or streaming conditions. +For example, Alice creates a delegation that allows Bob to spend up to 0.1 ETH without any conditions. +Bob may use the 0.1 ETH in a single transaction or make multiple transactions, as long as the total does not exceed 0.1 ETH. + +When this scope is applied, the toolkit disallows ERC-20 and ERC-721 token transfers by default, setting the allowed `calldata` to `0x`. + +Internally, this scope uses the [`exactCalldata`](../../../reference/delegation/caveats.md#exactcalldata) and [`nativeTokenTransferAmount`](../../../reference/delegation/caveats.md#nativetokentransferamount) caveat enforcers. + +```typescript +import { createDelegation } from "@metamask/delegation-toolkit"; + +const delegation = createDelegation({ + scope: { + type: "nativeTokenTransferAmount", + // 0.001 ETH in wei format. + maxAmount: 1000000000000000n, + }, + to: delegateAccount, + from: delegatorAccount, + environment: delegatorAccount.environment, +}); +``` + +## Next steps + +See [how to further constrain the authority of a delegation](constrain-scope.md) using caveat enforcers. diff --git a/delegation-toolkit/guides/smart-accounts/create-smart-account.md b/delegation-toolkit/guides/smart-accounts/create-smart-account.md index 56712123e2a..57463f70632 100644 --- a/delegation-toolkit/guides/smart-accounts/create-smart-account.md +++ b/delegation-toolkit/guides/smart-accounts/create-smart-account.md @@ -8,7 +8,7 @@ import TabItem from "@theme/TabItem"; # Create a smart account You can enable users to create a [MetaMask smart account](../../concepts/smart-accounts.md) directly in your dapp. -This page provides examples of using [`toMetaMaskSmartAccount`](../../reference/api/smart-account.md#tometamasksmartaccount) with Viem Core SDK to create different types of smart accounts with different signature schemes. +This page provides examples of using [`toMetaMaskSmartAccount`](../../reference/smart-account.md#tometamasksmartaccount) with Viem Core SDK to create different types of smart accounts with different signature schemes. An account's supported *signatories* can sign data on behalf of the smart account. ## Prerequisites @@ -18,18 +18,18 @@ An account's supported *signatories* can sign data on behalf of the smart accoun ## Create a Hybrid smart account A Hybrid smart account supports both an externally owned account (EOA) owner and any number of passkey (WebAuthn) signers. -You can create a Hybrid smart account with the following types of signatories. +You can create a Hybrid smart account with the following types of signers. -### Create a Hybrid smart account with an Account signatory +### Create a Hybrid smart account with an Account signer -Use [`toMetaMaskSmartAccount`](../../reference/api/smart-account.md#tometamasksmartaccount), and Viem's [`privateKeyToAccount` and `generatePrivateKey`](https://viem.sh/docs/accounts/local/privateKeyToAccount), to create a Hybrid smart account with a signatory from a randomly generated private key: +Use [`toMetaMaskSmartAccount`](../../reference/smart-account.md#tometamasksmartaccount), and Viem's [`privateKeyToAccount` and `generatePrivateKey`](https://viem.sh/docs/accounts/local/privateKeyToAccount), to create a Hybrid smart account with a signer from a randomly generated private key: ```typescript import { publicClient } from "./client.ts" -import { account } from "./signatory.ts"; +import { account } from "./signer.ts"; import { Implementation, toMetaMaskSmartAccount, @@ -40,7 +40,7 @@ const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", - signatory: { account }, + signer: { account }, }); ``` @@ -61,7 +61,7 @@ export const publicClient = createPublicClient({ - + ```typescript import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; @@ -73,16 +73,16 @@ export const account = privateKeyToAccount(privateKey); -### Create a Hybrid smart account with a Wallet Client signatory +### Create a Hybrid smart account with a Wallet Client signer -Use [`toMetaMaskSmartAccount`](../../reference/api/smart-account.md#tometamasksmartaccount) and Viem's [`createWalletClient`](https://viem.sh/docs/clients/wallet) to create a Hybrid smart account with a Wallet Client signatory: +Use [`toMetaMaskSmartAccount`](../../reference/smart-account.md#tometamasksmartaccount) and Viem's [`createWalletClient`](https://viem.sh/docs/clients/wallet) to create a Hybrid smart account with a Wallet Client signer: ```typescript import { publicClient } from "./client.ts" -import { walletClient } from "./signatory.ts"; +import { walletClient } from "./signer.ts"; import { Implementation, toMetaMaskSmartAccount, @@ -96,7 +96,7 @@ const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [owner, [], [], []], deploySalt: "0x", - signatory: { walletClient }, + signer: { walletClient }, }); ``` @@ -117,7 +117,7 @@ export const publicClient = createPublicClient({ - + ```typescript import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; @@ -137,9 +137,9 @@ export const walletClient = createWalletClient({ -### Create a Hybrid smart account with a passkey signatory +### Create a Hybrid smart account with a passkey signer -Use [`toMetaMaskSmartAccount`](../../reference/api/smart-account.md#tometamasksmartaccount) and Viem's [`toWebAuthnAccount`](https://viem.sh/account-abstraction/accounts/webauthn) to create a Hybrid smart account with a passkey (WebAuthn) signatory: +Use [`toMetaMaskSmartAccount`](../../reference/smart-account.md#tometamasksmartaccount) and Viem's [`toWebAuthnAccount`](https://viem.sh/account-abstraction/accounts/webauthn) to create a Hybrid smart account with a passkey (WebAuthn) signer: :::info Installation required To work with WebAuthn, install the [Ox SDK](https://oxlib.sh/). @@ -150,7 +150,7 @@ To work with WebAuthn, install the [Ox SDK](https://oxlib.sh/). ```typescript import { publicClient } from "./client.ts" -import { webAuthnAccount, credential } from "./signatory.ts"; +import { webAuthnAccount, credential } from "./signer.ts"; import { Implementation, toMetaMaskSmartAccount, @@ -169,7 +169,7 @@ const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [owner, [credential.id], [publicKey.x], [publicKey.y]], deploySalt: "0x", - signatory: { webAuthnAccount, keyId: toHex(credential.id) }, + signer: { webAuthnAccount, keyId: toHex(credential.id) }, }); ``` @@ -190,7 +190,7 @@ export const publicClient = createPublicClient({
- + ```typescript import { @@ -211,7 +211,7 @@ export const webAuthnAccount = toWebAuthnAccount({ credential }); ## Create a Multisig smart account A [Multisig smart account](../../concepts/smart-accounts.md#multisig-smart-account) supports multiple EOA signers with a configurable threshold for execution. -Use [`toMetaMaskSmartAccount`](../../reference/api/smart-account.md#tometamasksmartaccount) to create a Multsig smart account with a combination of account signatories and Wallet Client signatories: +Use [`toMetaMaskSmartAccount`](../../reference/smart-account.md#tometamasksmartaccount) to create a Multsig smart account with a combination of account signers and Wallet Client signers: @@ -224,16 +224,16 @@ import { toMetaMaskSmartAccount, } from "@metamask/delegation-toolkit"; -const signers = [ account.address, walletClient.address ]; -const signatory = [ { account }, { walletClient } ]; +const owners = [ account.address, walletClient.address ]; +const signer = [ { account }, { walletClient } ]; const threshold = 2n const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.MultiSig, - deployParams: [signers, threshold], + deployParams: [owners, threshold], deploySalt: "0x", - signatory, + signer, }); ``` @@ -293,16 +293,16 @@ This implementation does not handle the upgrade process; see the [EIP-7702 quick You can create a Stateless 7702 smart account with the following types of signatories. -### Create a Stateless 7702 smart account with an account signatory +### Create a Stateless 7702 smart account with an account signer -Use [`toMetaMaskSmartAccount`](../../reference/api/smart-account.md#tometamasksmartaccount) and Viem's [`privateKeyToAccount`](https://viem.sh/docs/accounts/local/privateKeyToAccount) to create a Stateless 7702 smart account with a signatory from a private key: +Use [`toMetaMaskSmartAccount`](../../reference/smart-account.md#tometamasksmartaccount) and Viem's [`privateKeyToAccount`](https://viem.sh/docs/accounts/local/privateKeyToAccount) to create a Stateless 7702 smart account with a signer from a private key: ```typescript import { publicClient } from "./client.ts"; -import { account } from "./signatory.ts"; +import { account } from "./signer.ts"; import { Implementation, toMetaMaskSmartAccount, @@ -312,7 +312,7 @@ const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Stateless7702, address: account.address // Address of the upgraded EOA - signatory: { account }, + signer: { account }, }); ``` @@ -333,7 +333,7 @@ export const publicClient = createPublicClient({ - + ```typescript import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; @@ -345,16 +345,16 @@ export const account = privateKeyToAccount(privateKey); -### Create a Stateless 7702 smart account with a Wallet Client signatory +### Create a Stateless 7702 smart account with a Wallet Client signer -Use [`toMetaMaskSmartAccount`](../../reference/api/smart-account.md#tometamasksmartaccount) and Viem's [`createWalletClient`](https://viem.sh/docs/clients/wallet) to create a Stateless 7702 smart account with a Wallet Client signatory: +Use [`toMetaMaskSmartAccount`](../../reference/smart-account.md#tometamasksmartaccount) and Viem's [`createWalletClient`](https://viem.sh/docs/clients/wallet) to create a Stateless 7702 smart account with a Wallet Client signer: ```typescript import { publicClient } from "./client.ts"; -import { walletClient } from "./signatory.ts"; +import { walletClient } from "./signer.ts"; import { Implementation, toMetaMaskSmartAccount, @@ -367,7 +367,7 @@ const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Stateless7702, address, // Address of the upgraded EOA - signatory: { walletClient }, + signer: { walletClient }, }); ``` @@ -388,7 +388,7 @@ export const publicClient = createPublicClient({ - + ```typescript import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; diff --git a/delegation-toolkit/guides/smart-accounts/deploy-smart-account.md b/delegation-toolkit/guides/smart-accounts/deploy-smart-account.md index 135ff40b306..5967a858ac7 100644 --- a/delegation-toolkit/guides/smart-accounts/deploy-smart-account.md +++ b/delegation-toolkit/guides/smart-accounts/deploy-smart-account.md @@ -73,7 +73,7 @@ export const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", - signatory: { account }, + signer: { account }, }); export const bundlerClient = createBundlerClient({ @@ -87,7 +87,7 @@ export const bundlerClient = createBundlerClient({ ## Deploy manually -To deploy a smart account manually, call the [`getFactoryArgs`](../../reference/api/smart-account.md#getfactoryargs) +To deploy a smart account manually, call the [`getFactoryArgs`](../../reference/smart-account.md#getfactoryargs) method from the smart account to retrieve the `factory` and `factoryData`. This allows you to use a relay account to sponsor the deployment without needing a paymaster. The `factory` represents the contract address responsible for deploying the smart account, while `factoryData` contains the @@ -137,7 +137,7 @@ export const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", - signatory: { account }, + signer: { account }, }); const relayAccountPrivateKey = "0x121.."; diff --git a/delegation-toolkit/guides/smart-accounts/generate-multisig-signature.md b/delegation-toolkit/guides/smart-accounts/generate-multisig-signature.md index 36ee418a2a8..846029d7312 100644 --- a/delegation-toolkit/guides/smart-accounts/generate-multisig-signature.md +++ b/delegation-toolkit/guides/smart-accounts/generate-multisig-signature.md @@ -11,7 +11,7 @@ The MetaMask Delegation Toolkit supports [Multisig smart accounts](../../concept allowing you to add multiple externally owned accounts (EOA) signers with a configurable execution threshold. When the threshold is greater than 1, you can collect signatures from the required signers -and use the [`aggregateSignature`](../../reference/api/smart-account.md#aggregatesignature) function to combine them +and use the [`aggregateSignature`](../../reference/smart-account.md#aggregatesignature) function to combine them into a single aggregated signature. ## Prerequisites @@ -98,7 +98,7 @@ export const aliceSmartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.MultiSig, deployParams: [signers, threshold], deploySalt: "0x", - signatory: [ { account: aliceAccount } ], + signer: [ { account: aliceAccount } ], }); export const bobSmartAccount = await toMetaMaskSmartAccount({ @@ -106,7 +106,7 @@ export const bobSmartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.MultiSig, deployParams: [signers, threshold], deploySalt: "0x", - signatory: [ { account: bobAccount } ], + signer: [ { account: bobAccount } ], }); export const bundlerClient = createBundlerClient({ diff --git a/delegation-toolkit/guides/smart-accounts/send-gasless-transaction.md b/delegation-toolkit/guides/smart-accounts/send-gasless-transaction.md index 93fb019f0e9..c5a5bf946b7 100644 --- a/delegation-toolkit/guides/smart-accounts/send-gasless-transaction.md +++ b/delegation-toolkit/guides/smart-accounts/send-gasless-transaction.md @@ -75,7 +75,7 @@ export const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", - signatory: { account }, + signer: { account }, }); export const bundlerClient = createBundlerClient({ diff --git a/delegation-toolkit/guides/smart-accounts/send-user-operation.md b/delegation-toolkit/guides/smart-accounts/send-user-operation.md index 96258d5f347..e8159d9ad27 100644 --- a/delegation-toolkit/guides/smart-accounts/send-user-operation.md +++ b/delegation-toolkit/guides/smart-accounts/send-user-operation.md @@ -87,7 +87,7 @@ export const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", - signatory: { account }, + signer: { account }, }); export const bundlerClient = createBundlerClient({ diff --git a/delegation-toolkit/index.md b/delegation-toolkit/index.md index cbad5cbcd7f..d3f5d16a561 100644 --- a/delegation-toolkit/index.md +++ b/delegation-toolkit/index.md @@ -55,11 +55,6 @@ title: "EIP-7702 quickstart", description: "Upgrade an externally owned account to a smart account.", }, { -href: "get-started/erc7715-quickstart", -title: "ERC-7715 quickstart", -description: "Request permissions from MetaMask and execute transactions on a user's behalf.", -}, -{ href: "get-started/use-the-cli", title: "Use the CLI", description: "Use the Delegation Toolkit CLI to bootstrap a project.", diff --git a/delegation-toolkit/reference/caveats.md b/delegation-toolkit/reference/caveats.md deleted file mode 100644 index d1a2c7b1d45..00000000000 --- a/delegation-toolkit/reference/caveats.md +++ /dev/null @@ -1,755 +0,0 @@ ---- -description: Caveat enforcers reference. -toc_max_heading_level: 2 ---- - -# Caveats - -When [restricting a delegation](../guides/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 to restrict smart contracts or EOAs lacking delegation support, -and can be placed anywhere in the delegation chain to restrict the redeemer. - -:::note -Delegator accounts with delegation functionalities can bypass these restrictions by delegating to -other addresses. -For example, Alice makes Bob the redeemer. -This condition is enforced, but if Bob is a delegator he can create a separate delegation to Carol -that allows her to redeem Alice's delegation through Bob. -::: - -Caveat enforcer contract: [`RedeemerEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/RedeemerEnforcer.sol) - -### Parameters - -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/delegation-toolkit/reference/delegation/caveat-enforcer-client.md b/delegation-toolkit/reference/delegation/caveat-enforcer-client.md new file mode 100644 index 00000000000..844a1a22829 --- /dev/null +++ b/delegation-toolkit/reference/delegation/caveat-enforcer-client.md @@ -0,0 +1,345 @@ +--- +description: Caveat Enforcer Client API reference. +sidebar_label: Caveat Enforcer Client +toc_max_heading_level: 2 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# Caveat Enforcer Client + +The following API methods are related to `CaveatEnforcerClient` used to [check the delegation state](../../guides/delegation/check-delegation-state.md). + +## `createCaveatEnforcerClient` + +Create a Viem Client extended with caveat enforcer actions. This client allows you to interact with the caveat enforcers of the +delegation, and read the required state. + +### Parameters + +| Name | Type | Required | Description | +| ------------- | ---------------------- | -------- | ----------- | +| `client` | `Client` | Yes | The Viem Client to interact with the caveat enforcer contracts and read their state. | +| `environment` | `DeleGatorEnvironment` | Yes | Environment to resolve the smart contracts for the current chain. | + +### Example + + + + +```typescript +import { environment, publicClient as client } from './config.ts' +import { createCaveatEnforcerClient } from '@metamask/delegation-toolkit' + +const caveatEnforcerClient = createCaveatEnforcerClient({ + environment, + client, +}) +``` + + + + +```typescript +import { sepolia as chain } from 'viem/chains' +import { createPublicClient, http } from 'viem' +import { getDeleGatorEnvironment } from '@metamask/delegation-toolkit' + +export const environment = getDeleGatorEnvironment(chain.id) + +export const publicClient = createPublicClient({ + chain, + transport: http(), +}) +``` + + + + +## `getErc20PeriodTransferEnforcerAvailableAmount` + +Returns the available amount from the ERC-20 period transfer enforcer for the current period. + +### Parameters + +| Name | Type | Required | Description | +| ------------- | ---------------------- | -------- | ----------- | +| `delegation` | `Delegation` | Yes | The delegation object for which you want to check the available amount. | + +### Example + + + + +```typescript +import { delegation } './config.ts' + +// Returns the available amount for current period. +const { availableAmount } = await caveatEnforcerClient.getErc20PeriodTransferEnforcerAvailableAmount({ + delegation, +}) +``` + + + + +```typescript +import { createDelegation } from '@metamask/delegation-toolkit' +import { sepolia as chain } from 'viem/chains' +import { getDeleGatorEnvironment } from '@metamask/delegation-toolkit' + +const environment = getDeleGatorEnvironment(chain.id) + +export const delegation = createDelegation({ + scope: { + type: 'erc20PeriodTransfer', + tokenAddress: '0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da', + periodAmount: 1000000000000000000n, + periodDuration: 86400, + startDate: 1743763600, + }, + to: 'DELEGATE_ADDRESS', + from: 'DELEGATOR_ADDRESS', + environment, +}) +``` + + + + +## `getErc20StreamingEnforcerAvailableAmount` + +Returns the available amount from the ERC-20 streaming enforcer. + +### Parameters + +| Name | Type | Required | Description | +| ------------- | ---------------------- | -------- | ----------- | +| `delegation` | `Delegation` | Yes | The delegation object for which you want to check the available amount. | + +### Example + + + + +```typescript +import { delegation } './config.ts' + +// Returns the available amount +const { availableAmount } = await caveatEnforcerClient.getErc20StreamingEnforcerAvailableAmount({ + delegation, +}) +``` + + + + +```typescript +import { createDelegation } from '@metamask/delegation-toolkit' +import { sepolia as chain } from 'viem/chains' +import { getDeleGatorEnvironment } from '@metamask/delegation-toolkit' + +const environment = getDeleGatorEnvironment(chain.id) + +export const delegation = createDelegation({ + scope: { + type: 'erc20Streaming', + tokenAddress: '0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92', + amountPerSecond: 100n, + initialAmount: 1000000n, + maxAmount: 10000000n, + startTime: 1703980800, + }, + to: 'DELEGATE_ADDRESS', + from: 'DELEGATOR_ADDRESS', + environment, +}) +``` + + + + +## `getNativeTokenPeriodTransferEnforcerAvailableAmount` + +Returns the available amount from the native token period enforcer for the current period. + +### Parameters + +| Name | Type | Required | Description | +| ------------- | ---------------------- | -------- | ----------- | +| `delegation` | `Delegation` | Yes | The delegation object for which you want to check the available amount. | + +### Example + + + + +```typescript +import { delegation } './config.ts' + +// Returns the available amount for current period. +const { availableAmount } = await caveatEnforcerClient.getNativeTokenPeriodTransferEnforcerAvailableAmount({ + delegation, +}) +``` + + + + +```typescript +import { createDelegation } from '@metamask/delegation-toolkit' +import { sepolia as chain } from 'viem/chains' +import { getDeleGatorEnvironment } from '@metamask/delegation-toolkit' + +const environment = getDeleGatorEnvironment(chain.id) + +export const delegation = createDelegation({ + scope: { + type: 'nativeTokenPeriodTransfer', + periodAmount: 1000000000000000000n, + periodDuration: 86400, + startDate: 1743763600, + }, + to: 'DELEGATE_ADDRESS', + from: 'DELEGATOR_ADDRESS', + environment, +}) +``` + + + + +## `getNativeTokenStreamingEnforcerAvailableAmount` + +Returns the available amount from the native streaming enforcer. + +### Parameters + +| Name | Type | Required | Description | +| ------------- | ---------------------- | -------- | ----------- | +| `delegation` | `Delegation` | Yes | The delegation object for which you want to check the available amount. | + +### Example + + + + +```typescript +import { delegation } './config.ts' + +// Returns the available amount +const { availableAmount } = await caveatEnforcerClient.getNativeTokenStreamingEnforcerAvailableAmount({ + delegation, +}) +``` + + + + +```typescript +import { createDelegation } from '@metamask/delegation-toolkit' +import { sepolia as chain } from 'viem/chains' +import { getDeleGatorEnvironment } from '@metamask/delegation-toolkit' + +const environment = getDeleGatorEnvironment(chain.id) + +export const delegation = createDelegation({ + scope: { + type: "nativeTokenStreaming", + amountPerSecond: 100n, + initialAmount: 1000000n, + maxAmount: 10000000n, + startTime: 1703980800, + }, + to: 'DELEGATE_ADDRESS', + from: 'DELEGATOR_ADDRESS', + environment, +}) +``` + + + + +## `getMultiTokenPeriodEnforcerAvailableAmount` + +Returns the available amount from the multi token period transfer enforcer for the current period. You'll need to +encode the args for the token index you want to check the available amount. + +### Parameters + +| Name | Type | Required | Description | +| ------------- | ---------------------- | -------- | ----------- | +| `delegation` | `Delegation` | Yes | The delegation object with token index for which you want to check the available amount. | + +### Example + + + + +```typescript +import { delegation } './config.ts' + +// Encode the args for the multiTokenPeriod enforcer. +const args = encodePacked(['uint256'], [BigInt(0)]); + +// Ensure the index is correct when working with multiple enforcers. +delegation.caveats[0].args = args + +// Returns the available amount for the first token in the list. +const { availableAmount } = await caveatEnforcerClient.getMultiTokenPeriodEnforcerAvailableAmount({ + delegation, +}) +``` + + + + +```typescript +import { createDelegation, getDeleGatorEnvironment, ROOT_AUTHORITY } from '@metamask/delegation-toolkit' +import { createCaveatBuilder } from '@metamask/delegation-toolkit/utils' +import { sepolia as chain } from 'viem/chains' + +const environment = getDeleGatorEnvironment(chain.id) +const caveatBuilder = createCaveatBuilder(environment) + +// Current time as start date. +// Since startDate is in seconds, we need to convert milliseconds to seconds. +const startDate = Math.floor(Date.now() / 1000); + +const tokenConfigs = [ + { + token: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + // 1 token with 18 decimals. + periodAmount: 1000000000000000000n, + // 1 day in seconds. + periodDuration: 86400, + startDate + }, + { + // For native token use zeroAddress + token: zeroAddress, + // 0.01 ETH in wei. + periodAmount: 10000000000000000n, + // 1 hour in seconds. + periodDuration: 3600, + startDate + } +] + +const caveats = caveatBuilder.addCaveat('nativeTokenTransferAmount', 1000000n).addCaveat({ + 'multiTokenPeriod', + tokenConfigs +}) + +export const delegation: Delegation = { + delegate: 'DELEGATE_ADDRESS', + delegator: 'DELEGATOR_ADDRESS', + authority: ROOT_AUTHORITY, + caveats: caveats.build(), + salt: '0x', +}; +``` + + + diff --git a/delegation-toolkit/reference/delegation/caveats.md b/delegation-toolkit/reference/delegation/caveats.md new file mode 100644 index 00000000000..6bb871a7fbc --- /dev/null +++ b/delegation-toolkit/reference/delegation/caveats.md @@ -0,0 +1,904 @@ +--- +description: Caveat enforcers reference. +toc_max_heading_level: 2 +--- + +# Caveats + +When [constraining a delegation scope](../../guides/delegation/use-delegation-scopes/constrain-scope.md), you can specify the following caveat types. + +## `allowedCalldata` + +Limits the calldata that is executed. + +You can use this caveat to enforce function parameters. +We strongly recommend using this caveat to validate static types and not dynamic types. +You can validate dynamic types through a series of `allowedCalldata` terms, but this is tedious and error-prone. + +Caveat enforcer contract: [`AllowedCalldataEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/AllowedCalldataEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ------------ | -------- | -------- | -------------------------------------------------------------------------------------------------------------------- | +| `startIndex` | `number` | Yes | The index in the `calldata` byte array (including the 4-byte method selector) where the expected `calldata` starts. | +| `value` | `Hex` | Yes | The expected `calldata` that must match at the specified index. | + +### Example + +```typescript +const value = encodeAbiParameters( + [ + { type: "string" }, + { type: "uint256" } + ], + [ + "Hello Gator", + 12345n + ] +); + +const caveats = [{ + type: "allowedCalldata", + startIndex: 4, + value, +}]; +``` + +:::note +This example uses Viem's [`encodeAbiParameters`](https://viem.sh/docs/abi/encodeAbiParameters) utility to encode the parameters as ABI-encoded hex strings. +::: + +## `allowedMethods` + +Limits what methods the delegate can call. + +Caveat enforcer contract: [`AllowedMethodsEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/AllowedMethodsEnforcer.sol) + +### Parameters + +| Name | Type | Required |Description | +| ----------- | ------------------ | -------- | ---------- | +| `selectors` | `MethodSelector[]` | Yes | The list of method selectors that the delegate is allowed to call. The selector value can be 4-byte hex string, ABI function signature, or ABI function object. | + +### Example + +```typescript +const caveats = [{ + type: "allowedMethods", + selectors: [ + // 4-byte Hex string. + "0xa9059cbb", + // ABI function signature. + "transfer(address,uint256)", + // ABI function object. + { + name: 'transfer', + type: 'function', + inputs: [ + { name: 'recipient', type: 'address' }, + { name: 'amount', type: 'uint256' } + ], + outputs: [], + stateMutability: 'nonpayable', + }, + ] +}]; +``` + +:::note +This example adds the `transfer` function to the allowed methods in three different ways - as the 4-byte function selector, the ABI function signature, and the `ABIFunction` object. +::: + +## `allowedTargets` + +Limits what addresses the delegate can call. + +Caveat enforcer contract: [`AllowedTargetsEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/AllowedTargetsEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| --------- | ----------- | -------- | ----------------------------------------------------------- | +| `targets` | `Address[]` | Yes | The list of addresses that the delegate is allowed to call. | + +### Example + +```typescript +const caveats = [{ + type: "allowedTargets", + targets: [ + "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + "0xB2880E3862f1024cAC05E66095148C0a9251718b", + ] +}]; +``` + +## `argsEqualityCheck` + +Ensures that the `args` provided when redeeming the delegation are equal to the terms specified on the caveat. + +Caveat enforcer contract: [`ArgsEqualityCheckEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ArgsEqualityCheckEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ------ | ----- | -------- | ------------------------------------------------------------------------ | +| `args` | `Hex` | Yes | The expected args that must match exactly when redeeming the delegation. | + +### Example + +```typescript +const caveats = [{ + type: "argsEqualityCheck", + args: "0xf2bef872456302645b7c0bb59dcd96ffe6d4a844f311ebf95e7cf439c9393de2", +}]; +``` + +## `blockNumber` + +Specifies a range of blocks through which the delegation will be valid. + +Caveat enforcer contract: [`BlockNumberEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/BlockNumberEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ----------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------- | +| `afterThreshold` | `bigint` | Yes | The block number after which the delegation is valid. Set the value to `0n` to disable this threshold. | +| `beforeThreshold` | `bigint` | Yes | The block number before which the delegation is valid. Set the value to `0n` to disable this threshold. | + +### Example + +```typescript +const caveats = [{ + type: "blockNumber", + afterThreshold: 19426587n, + beforeThreshold: 0n, +}]; +``` + +## `deployed` + +Ensures a contract is deployed, and if not, deploys the contract. + +Caveat enforcer contract: [`DeployedEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/DeployedEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ----------------- | --------- | -------- | ------------------------------------ | +| `contractAddress` | `Address` | Yes | The contract address. | +| `salt` | `Hex` | Yes | The salt to use with the deployment. | +| `bytecode` | `Hex` | Yes | The bytecode of the contract. | + +### Example + +```typescript +const caveats = [{ + type: "deployed", + contractAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + salt: "0x0e3e8e2381fde0e8515ed47ec9caec8ba2bc12603bc2b36133fa3e3fa4d88587", + bytecode: "0x..." // The deploy bytecode for the contract at 0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92 +}]; +``` + +## `erc1155BalanceChange` + +Ensures that the recipient's ERC-1155 token balance has changed within the allowed bounds — either increased by a minimum or decreased by a maximum specified amount. + +Caveat enforcer contract: [`ERC1155BalanceBalanceEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC1155BalanceBalanceEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ------------- | +| `tokenAddress` | `Address` | Yes | The ERC-1155 token contract address. | +| `recipient` | `Address` | Yes | The address on which the checks will be applied. | +| `tokenId` | `bigint` | Yes | The ID of the ERC-1155 token. | +| `balance` | `bigint` | Yes | The amount by which the balance must be changed. | +| `changeType` | `BalanceChangeType` | Yes | The balance change type for the ERC-1155 token. Specifies whether the balance should have increased or decreased. Valid parameters are `BalanceChangeType.Increase` and `BalanceChangeType.Decrease`. | + +### Example + +```typescript +const caveats = [{ + type: "erc1155BalanceChange", + tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + recipient: "0x3fF528De37cd95b67845C1c55303e7685c72F319", + tokenId: 1n, + balance: 1000000n, + changeType: BalanceChangeType.Increase, +}]; +``` + +## `erc20BalanceChange` + +Ensures that the recipient's ERC-20 token balance has changed within the allowed bounds — either increased by a minimum or decreased by a maximum specified amount. + +Caveat enforcer contract: [`ERC20BalanceChangeEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC20BalanceChangeEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ------------- | +| `tokenAddress` | `Address` | Yes | The ERC-20 token contract addres. | +| `recipient` | `Address` | Yes | The address on which the checks will be applied. | +| `balance` | `bigint` | Yes | The amount by which the balance must be changed. | +| `changeType` | `BalanceChangeType` | Yes | The balance change type for the ERC-20 token. Specifies whether the balance should have increased or decreased. Valid parameters are `BalanceChangeType.Increase` and `BalanceChangeType.Decrease`. | + +### Example + +```typescript +const caveats = [{ + type: "erc20BalanceChange", + tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + recipient: "0x3fF528De37cd95b67845C1c55303e7685c72F319", + balance: 1000000n, + changeType: BalanceChangeType.Increase, +}]; +``` + +## `erc20PeriodTransfer` + +Ensures that ERC-20 token transfers remain within a predefined limit during a +specified time window. At the start of each new period, the allowed transfer +amount resets. Any unused transfer allowance from the previous period does not +carry over and is forfeited. + +Caveat enforcer contract: [`ERC20PeriodTransferEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC20PeriodTransferEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ---------------- | --------- | -------- | ---------------------------------------------------------------- | +| `tokenAddress` | `Address` | Yes | The ERC-20 token contract address as a hex string. | +| `periodAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred per period. | +| `periodDuration` | `number` | Yes | The duration of each period in seconds. | +| `startDate` | `number` | Yes | The timestamp when the first period begins in seconds. | + +### Example + +```typescript +// Current time as start date. +// Since startDate is in seconds, we need to convert milliseconds to seconds. +const startDate = Math.floor(Date.now() / 1000); + +const caveats = [{ + type: "erc20PeriodTransfer", + // Address of the ERC-20 token. + tokenAddress: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + // 1 ERC-20 token - 18 decimals, in wei. + periodAmount: 1000000000000000000n, + // 1 day in seconds. + periodDuration: 86400, + startDate, +}]; +``` + +## `erc20Streaming` + +Enforces a linear streaming transfer limit for ERC-20 tokens. Block token access until the specified start timestamp. At the start timestamp, immediately release the specified initial amount. Afterward, accrue tokens linearly at the specified rate, up to the specified maximum. + +Caveat enforcer contract: [`ERC20StreamingEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC20StreamingEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ----------------- | --------- | -------- | --------------------------------------------------------- | +| `tokenAddress` | `Address` | Yes | The ERC-20 token contract address. | +| `initialAmount` | `bigint` | Yes | The initial amount that can be transferred at start time. | +| `maxAmount` | `bigint` | Yes | The maximum total amount that can be unlocked. | +| `amountPerSecond` | `bigint` | Yes | The rate at which tokens accrue per second. | +| `startTime` | `number` | Yes | The start timestamp in seconds. | + +### Example + +```typescript +// Current time as start date. +// Since startDate is in seconds, we need to convert milliseconds to seconds. +const startDate = Math.floor(Date.now() / 1000); + +const caveats = [{ + type: "erc20Streaming", + // Address of the ERC-20 token. + tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + // 1 ERC-20 token - 18 decimals, in wei. + initialAmount: 1000000000000000000n, + // 10 ERC-20 token - 18 decimals, in wei. + maxAmount: 10000000000000000000n + // 0.00001 ERC-20 token - 18 decimals, in wei. + amountPerSecond: 10000000000000n, + startDate, +}]; +``` + +## `erc20TransferAmount` + +Limits the transfer of ERC-20 tokens. + +Caveat enforcer contract: [`ERC20TransferAmountEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC20TransferAmountEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| -------------- | --------- | -------- | ----------------------------------------------------------------- | +| `tokenAddress` | `Address` | Yes | The ERC-20 token contract address. | +| `maxAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred by delegate. | + +### Example + +```typescript +const caveats = [{ + type: "erc20TransferAmount", + tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + // 1 ERC-20 token - 18 decimals, in wei. + maxAmount: 1000000000000000000n +}]; +``` + +## `erc721BalanceChange` + +Ensures that the recipient's ERC-721 token balance has changed within the allowed bounds — either increased by a minimum or decreased by a maximum specified amount. + +Caveat enforcer contract: [`ERC721BalanceChangeEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC721BalanceChangeEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ------------- | +| `tokenAddress` | `Address` | Yes | The ERC-721 token contract addres. | +| `recipient` | `Address` | Yes | The address on which the checks will be applied. | +| `balance` | `bigint` | Yes | The amount by which the balance must be changed. | +| `changeType` | `BalanceChangeType` | Yes | The balance change type for the ERC-721 token. Specifies whether the balance should have increased or decreased. Valid parameters are `BalanceChangeType.Increase` and `BalanceChangeType.Decrease`. | + +### Example + +```typescript +const caveats = [{ + type: "erc721BalanceChange", + tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + recipient: "0x3fF528De37cd95b67845C1c55303e7685c72F319", + balance: 1000000n, + changeType: BalanceChangeType.Increase, +}]; +``` + +## `erc721Transfer` + +Restricts the execution to only allow ERC-721 token transfers, specifically the `transferFrom(from, to, tokenId)` function, for a specified token ID and contract. + +Caveat enforcer contract: [`ERC721TransferEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ERC721TransferEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| -------------- | --------- | -------- | ---------------------------------------------------------------------------- | +| `tokenAddress` | `Address` | Yes | The ERC-721 token contract address. | +| `tokenId` | `bigint` | Yes | The ID of the ERC-721 token that can be transferred by delegate. | + +### Example + +```typescript +const caveats = [{ + type: "erc721Transfer", + tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + tokenId: 1n +}]; +``` + +## `exactCalldata` + +Verifies that the transaction calldata matches the expected calldata. For batch transactions, +see [`exactCalldataBatch`](#exactcalldatabatch). + +Caveat enforcer contract: [`ExactCalldataEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ExactCalldataEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ----------------- | -------------------------------- | -------- | ----------------------------------------------------- | +| `exactCalldata` | `calldata` | Yes | The `calldata` that the delegate is allowed to call. | + +### Example + +```typescript +const caveats = [{ + type: "exactCalldata", + exactCalldata: "0x1234567890abcdef", +}]; +``` + +## `exactCalldataBatch` + +Verifies that the provided batch execution calldata matches +the expected calldata for each individual execution in the batch. + +Caveat enforcer contract: [`ExactCalldataBatchEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ExactCalldataBatchEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ----------------- | ------------------------| -------- | ----------------------------------------------------- | +| `executions` | `ExecutionStruct[]` | Yes | The list of executions that must be matched exactly in the batch. Each execution specifies a target address, value, and calldata. | + +### Example + +```typescript +const executions = [ + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + callData: "0x", + }, + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 0n, + callData: "0x", + }, +]; + +const caveats = [{ + type: "exactCalldataBatch", + executions, +}]; +``` + +## `exactExecution` + +Verifies that the provided execution matches the expected execution. For batch transactions, +see [`exactExecutionBatch`](#exactexecutionbatch). + +Caveat enforcer contract: [`ExactExecutionEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ExactExecutionEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ----------------- | ------------------| -------- | ----------------------------------------------------- | +| `execution` | `ExecutionStruct` | Yes | The execution that must be matched exactly. Specifies the target address, value, and calldata. | + +### Example + +```typescript +const caveats = [{ + type: "exactExecution", + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, + callData: "0x", +}]; +``` + +## `exactExecutionBatch` + +Verifies that each execution in the batch matches the expected +execution parameters - including target, value, and calldata. + +Caveat enforcer contract: [`ExactExecutionBatchEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ExactExecutionBatchEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ----------------- | ------------------------| -------- | ----------------------------------------------------- | +| `executions` | `ExecutionStruct[]` | Yes | The list of executions that must be matched exactly in the batch. Each execution specifies a target address, value, and calldata. | + +### Example + +```typescript +const executions = [ + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 1000000000000000000n, // 1 ETH + callData: "0x", + }, + { + target: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + value: 0n, + callData: "0x", + }, +]; + +const caveats = [{ + type: "exactExecutionBatch", + executions, +}]; +``` + +## `id` + +Specifies an ID for multiple delegations. Once one of them is redeemed, the other delegations with the same ID are revoked. + +Caveat enforcer contract: [`IdEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/IdEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ----------| ------------------------| -------- | -------------------------------------------------------------------------------- | +| `id` | `bigint` | `number` | Yes | An ID for the delegation. Only one delegation may be redeemed with any given ID. | + +### Example + +```typescript +const caveats = [{ + type: "id", + id: 123456, +}]; +``` + +## `limitedCalls` + +Limits the number of times the delegate can perform executions on the delegator's behalf. + +Caveat enforcer contract: [`LimitedCallsEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/LimitedCallsEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ----------| ------------------------| -------- | ------------------------------------------------------------ | +| `limit` | `number` | Yes | The maximum number of times this delegation may be redeemed. | + +### Example + +```typescript +const caveats = [{ + type: "limitedCalls", + limit: 1, +}]; +``` + +## `multiTokenPeriod` + +Ensures that token transfers for multiple tokens stay within the specified limits for the defined periods. +At the start of each new period, the allowed transfer amount for each token resets. Any unused transfer allowance from the previous period expires and does not carry over. + +When redeeming the delegation, the index of the relevant token configuration must be specified +as the `args` of this caveat (encoded as `uint256` hex value). + +Caveat enforcer contract: [`MultiTokenPeriodEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/MultiTokenPeriodEnforcer.sol) + +### Parameters + +The list of `TokenPeriodConfig` objects, where each object contains: + +| Name | Type | Required | Description | +| ---------------- | --------- | -------- | ---------------------------------------------------------------- | +| `token` | `Address` | Yes | The ERC-20 token contract address as a hex string. | +| `periodAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred per period. | +| `periodDuration` | `number` | Yes | The duration of each period in seconds. | +| `startDate` | `number` | Yes | The timestamp when the first period begins in seconds. | + +### Example + +```typescript +import { zeroAddress } from 'viem'; + +// Current time as start date. +// Since startDate is in seconds, we need to convert milliseconds to seconds. +const startDate = Math.floor(Date.now() / 1000); + +const tokenPeriodConfigs = [ + { + token: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + // 1 token with 18 decimals. + periodAmount: 1000000000000000000n, + // 1 day in seconds. + periodDuration: 86400, + startDate + }, + { + // For native token use zeroAddress + token: zeroAddress, + // 0.01 ETH in wei. + periodAmount: 10000000000000000n, + // 1 hour in seconds. + periodDuration: 3600, + startDate + } +] + +const caveats = [{ + type: "multiTokenPeriod", + tokenPeriodConfigs, +}]; +``` + +## `nativeBalanceChange` + +Ensures that the recipient's native token balance has changed within the allowed bounds — either increased by a minimum or decreased by a maximum specified amount. + +Caveat enforcer contract: [`NativeBalanceChangeEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NativeBalanceChangeEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ------------- | +| `recipient` | `Address` | Yes | The address on which the checks will be applied. | +| `balance` | `bigint` | Yes | The amount by which the balance must be changed. | +| `changeType` | `BalanceChangeType` | Yes | The balance change type for the native token. Specifies whether the balance should have increased or decreased. Valid parameters are `BalanceChangeType.Increase` and `BalanceChangeType.Decrease`. | + +### Example + +```typescript +const caveats = [{ + type: "nativeBalanceChange", + recipient: "0x3fF528De37cd95b67845C1c55303e7685c72F319", + balance: 1000000n, + changeType: BalanceChangeType.Increase, +}]; +``` + +## `nativeTokenPayment` + +Enforces payment in native token (for example, ETH) for the right to use the delegation. +A permissions context allowing payment must be provided as the `args` when +redeeming the delegation. + +Caveat enforcer contract: [`NativeTokenPaymentEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NativeTokenPaymentEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ---- | ---- | -------- | ------------- | +| `recipient` | `Address` | Yes | The recipient address who receives the payment. | +| `amount` | `bigint` | Yes | The amount that must be paid. | + +### Example + +```typescript +const caveats = [{ + type: "nativeTokenPayment", + recipient: "0x3fF528De37cd95b67845C1c55303e7685c72F319", + amount: 1000000n, +}]; +``` + +## `nativeTokenPeriodTransfer` + +Ensures that native token transfers remain within a predefined limit during a +specified time window. At the start of each new period, the allowed transfer +amount resets. Any unused transfer allowance from the previous period does not +carry over and is forfeited. + +Caveat enforcer contract: [`NativeTokenPeriodTransferEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NativeTokenPeriodTransferEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ---------------- | --------- | -------- | ---------------------------------------------------------------- | +| `periodAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred per period. | +| `periodDuration` | `number` | Yes | The duration of each period in seconds. | +| `startDate` | `number` | Yes | The timestamp when the first period begins in seconds. | + +### Example + +```typescript +// Current time as start date. +// Since startDate is in seconds, we need to convert milliseconds to seconds. +const startDate = Math.floor(Date.now() / 1000); + +const caveats = [{ + type: "nativeTokenPeriodTransfer", + // 1 ETH in wei. + periodAmount: 1000000000000000000n, + // 1 day in seconds. + periodDuration: 86400, + startDate, +}]; +``` + +## `nativeTokenStreaming` + +Enforces a linear streaming limit for native tokens (for example, ETH). Nothing is available before the specified start timestamp. At the start timestamp, the specified initial amount becomes immediately available. After that, tokens accrue linearly at the specified rate, capped by the specified maximum. + +Caveat enforcer contract: [`NativeTokenStreamingEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NativeTokenStreamingEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ----------------- | --------- | -------- | --------------------------------------------------------- | +| `initialAmount` | `bigint` | Yes | The initial amount that can be transferred at start time. | +| `maxAmount` | `bigint` | Yes | The maximum total amount that can be unlocked. | +| `amountPerSecond` | `bigint` | Yes | The rate at which tokens accrue per second. | +| `startTime` | `number` | Yes | The start timestamp in seconds. | + +### Example + +```typescript +// Current time as start date. +// Since startDate is in seconds, we need to convert milliseconds to seconds. +const startDate = Math.floor(Date.now() / 1000); + +const caveats = [{ + type: "nativeTokenStreaming", + // 0.01 ETH in wei. + initialAmount: 10000000000000000, + // 0.5 ETH in wei. + maxAmount: 500000000000000000n + // 0.00001 ETH in wei. + amountPerSecond: 10000000000000n, + startDate, +}]; +``` + +## `nativeTokenTransferAmount` + +Enforces an allowance of native currency (for example, ETH). + +Caveat enforcer contract: [`NativeTokenTransferAmountEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NativeTokenTransferAmountEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| -------------- | --------- | -------- | ----------------------------------------------------------------- | +| `maxAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred by delegate. | + +### Example + +```typescript +const caveats = [{ + type: "nativeTokenTransferAmount", + // 0.00001 ETH in wei. + maxAmount: 10000000000000000n +}]; +``` + +## `nonce` + +Adds a nonce to a delegation, and revokes previous delegations by incrementing the current nonce by calling `incrementNonce(address _delegationManager)`. + +Caveat enforcer contract: [`NonceEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/NonceEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| -------------- | --------- | -------- | ----------------------------------------------------------------- | +| `nonce` | `Hex` | Yes | The nonce to allow bulk revocation of delegations. | + + +### Example + +```typescript +const caveats = [{ + type: "nonce", + nonce: "0x1" +}]; +``` + +## `ownershipTransfer` + +Restricts the execution to only allow ownership transfers, specifically the `transferOwnership(address _newOwner)` function, for a specified contract. + +Caveat enforcer contract: [`OwnershipTransferEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/OwnershipTransferEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ----------------- | --------- | -------- | -----------------------------------------------------------------------| +| `contractAddress` | `Address` | Yes | The target contract address for which ownership transfers are allowed. | + +### Example + +```typescript +const caveats = [{ + type: "ownershipTransfer", + contractAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92" +}]; +``` + +## `redeemer` + +Limits the addresses that can redeem the delegation. +This caveat is designed to restrict smart contracts or EOAs lacking delegation support, +and can be placed anywhere in the delegation chain to restrict the redeemer. + +:::note +Delegator accounts with delegation functionalities can bypass these restrictions by delegating to +other addresses. +For example, Alice makes Bob the redeemer. +This condition is enforced, but if Bob is a delegator he can create a separate delegation to Carol +that allows her to redeem Alice's delegation through Bob. +::: + +Caveat enforcer contract: [`RedeemerEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/RedeemerEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ----------------- | ----------- | -------- | -----------------------------------------------------------------------| +| `redeemers` | `Address[]` | Yes | The list of addresses that are allowed to redeem the delegation. | + +### Example + +```typescript +const caveats = [{ + type: "redeemer", + redeemers: [ + "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + "0x6be97c23596ECed7170fdFb28e8dA1Ca5cdc54C5" + ], +}]; +``` + +## `specificActionERC20TransferBatch` + +Ensures validation of a batch consisting of exactly two transactions: +1. The first transaction must call a specific target contract with predefined calldata. +2. The second transaction must be an ERC-20 token transfer that matches specified + parameters—including the ERC-20 token contract address, amount, and recipient. + +Caveat enforcer contract: [`SpecificActionERC20TransferBatchEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/SpecificActionERC20TransferBatchEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| -------------- | --------- | -------- | --------------------------------------------- | +| `tokenAddress` | `Address` | Yes | The ERC-20 token contract address. | +| `recipient` | `Address` | Yes | The address that will receive the tokens. | +| `amount` | `bigint` | Yes | The amount of tokens to transfer. | +| `target` | `Address` | Yes | The target address for the first transaction. | +| `calldata` | `Hex` | Yes | The `calldata` for the first transaction. | + +### Example + +```typescript +const caveats = [{ + type: "specificActionERC20TransferBatch", + tokenAddress: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + recipient: "0x027aeAFF3E5C33c4018FDD302c20a1B83aDCD96C", + // 1 ERC-20 token - 18 decimals, in wei + amount: 1000000000000000000n, + target: "0xb49830091403f1Aa990859832767B39c25a8006B", + calldata: "0x1234567890abcdef" +}]; +``` + +## `timestamp` + +Specifies a range of timestamps through which the delegation will be valid. + +Caveat enforcer contract: [`TimestampEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/TimestampEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ----------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------- | +| `afterThreshold` | `number` | Yes | The timestamp after which the delegation is valid in seconds. Set the value to `0` to disable this threshold. | +| `beforeThreshold` | `number` | Yes | The timestamp before which the delegation is valid in seconds. Set the value to `0` to disable this threshold. | + +### Example + +```typescript +// We need to convert milliseconds to seconds. +const currentTime = Math.floor(Date.now() / 1000); +// 1 hour after current time. +const afterThreshold = currentTime + 3600; +// 1 day after afterThreshold +const beforeThreshold = afterThreshold + 86400; + +const caveats = [{ + type: "timestamp", + afterThreshold, + beforeThreshold, +}]; +``` + +## `valueLte` + +Limits the value of native tokens that the delegate can spend. + +Caveat enforcer contract: [`ValueLteEnforcer.sol`](https://github.com/MetaMask/delegation-framework/blob/main/src/enforcers/ValueLteEnforcer.sol) + +### Parameters + +| Name | Type | Required | Description | +| ----------------- | ----------- | -------- | -----------------------------------------------------------------------| +| `maxValue` | `bigint` | Yes | The maximum value that may be specified when redeeming this delegation.| + +### Example + +```typescript +const caveats = [{ + type: "valueLte", + // 0.01 ETH in wei. + maxValue: 10000000000000000n +}]; +``` diff --git a/delegation-toolkit/reference/delegation/delegation-scopes.md b/delegation-toolkit/reference/delegation/delegation-scopes.md new file mode 100644 index 00000000000..95bc9ce9eb0 --- /dev/null +++ b/delegation-toolkit/reference/delegation/delegation-scopes.md @@ -0,0 +1,332 @@ +--- +description: Delegation scopes reference. +--- + +# Delegation scopes + +When [creating a delegation](../../guides/delegation/execute-on-smart-accounts-behalf.md), you can configure the following scopes to define the delegation's initial authority. +Learn [how to use delegation scopes](../../guides/delegation/use-delegation-scopes/index.md). + +## Spending limit scopes + +### ERC-20 periodic scope + +Ensures a per-period limit for ERC-20 token transfers. +At the start of each new period, the allowance resets. + + +#### Parameters + +| Name | Type | Required | Description | +| ---------------- | --------- | -------- | ---------------------------------------------------------------- | +| `tokenAddress` | `Address` | Yes | The ERC-20 token contract address as a hex string. | +| `periodAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred per period. | +| `periodDuration` | `number` | Yes | The duration of each period in seconds. | +| `startDate` | `number` | Yes | The timestamp when the first period begins in seconds. | + +#### Example + +```typescript +import { createDelegation, getDelegatorEnvironment } from "@metamask/delegation-toolkit"; +import { sepolia } from "viem/chains"; + +const delegation = createDelegation({ + scope: { + type: "erc20PeriodTransfer", + tokenAddress: "0xb4aE654Aca577781Ca1c5DE8FbE60c2F423f37da", + periodAmount: 1000000000000000000n, + periodDuration: 86400, + startDate: 1743763600, + }, + // Address that is granting the delegation + from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", + // Address to which the delegation is being granted + to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", + // Alternatively you can use environment property of MetaMask smart account. + environment: getDelegatorEnvironment(sepolia.id); +}); +``` + +### ERC-20 streaming scope + +Ensures a linear streaming transfer limit for ERC-20 tokens. +Token transfers are blocked until the defined start timestamp. +At the start, a specified initial amount is released, after which tokens accrue linearly at the configured rate, up to the maximum allowed amount. + +#### Parameters + +| Name | Type | Required | Description | +| ----------------- | --------- | -------- | --------------------------------------------------------- | +| `tokenAddress` | `Address` | Yes | The ERC-20 token contract address. | +| `initialAmount` | `bigint` | Yes | The initial amount that can be transferred at start time. | +| `maxAmount` | `bigint` | Yes | The maximum total amount that can be unlocked. | +| `amountPerSecond` | `bigint` | Yes | The rate at which tokens accrue per second. | +| `startTime` | `number` | Yes | The start timestamp in seconds. | + +#### Example + +```typescript +import { createDelegation, getDelegatorEnvironment } from "@metamask/delegation-toolkit"; +import { sepolia } from "viem/chains"; + +const delegation = createDelegation({ + scope: { + type: "erc20Streaming", + tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + amountPerSecond: 100n, + initialAmount: 1000000n, + maxAmount: 10000000n, + startTime: 1703980800, + }, + // Address that is granting the delegation + from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", + // Address to which the delegation is being granted + to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", + // Alternatively you can use environment property of MetaMask smart account. + environment: getDelegatorEnvironment(sepolia.id); +}); +``` + +### ERC-20 transfer scope + +Ensures that ERC-20 token transfers are limited to a predefined maximum amount. +This scope is useful for setting simple, fixed transfer limits without any time-based or streaming conditions. + +#### Parameters + +| Name | Type | Required | Description | +| -------------- | --------- | -------- | ----------------------------------------------------------------- | +| `tokenAddress` | `Address` | Yes | The ERC-20 token contract address. | +| `maxAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred by delegate. | + +#### Example + +```typescript +import { createDelegation, getDelegatorEnvironment } from "@metamask/delegation-toolkit"; +import { sepolia } from "viem/chains"; + +const delegation = createDelegation({ + scope: { + type: "erc20TransferAmount", + tokenAddress: "0xc11F3a8E5C7D16b75c9E2F60d26f5321C6Af5E92", + maxAmount: 10000n, + }, + // Address that is granting the delegation + from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", + // Address to which the delegation is being granted + to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", + // Alternatively you can use environment property of MetaMask smart account. + environment: getDelegatorEnvironment(sepolia.id); +}); +``` + +### ERC-721 scope + +Limits the delegation to ERC-721 token (NFT) transfers only. + +#### Parameters + +| Name | Type | Required | Description | +| -------------- | --------- | -------- | ---------------------------------------------------------------------------- | +| `tokenAddress` | `Address` | Yes | The ERC-721 token contract address. | +| `tokenId` | `bigint` | Yes | The ID of the ERC-721 token that can be transferred by delegate. | + +#### Example + +```typescript +import { createDelegation, getDelegatorEnvironment } from "@metamask/delegation-toolkit"; +import { sepolia } from "viem/chains"; + +const delegation = createDelegation({ + scope: { + type: "erc721Transfer", + tokenAddress: "0x3fF528De37cd95b67845C1c55303e7685c72F319", + tokenId: 1n, + }, + // Address that is granting the delegation + from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", + // Address to which the delegation is being granted + to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", + // Alternatively you can use environment property of MetaMask smart account. + environment: getDelegatorEnvironment(sepolia.id); +}); +``` + +### Native token periodic scope + +Ensures a per-period limit for native token transfers. +At the start of each new period, the allowance resets. + +#### Parameters + +| Name | Type | Required | Description | +| ---------------- | --------- | -------- | ---------------------------------------------------------------- | +| `periodAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred per period. | +| `periodDuration` | `number` | Yes | The duration of each period in seconds. | +| `startDate` | `number` | Yes | The timestamp when the first period begins in seconds. | +| `allowedCalldata` | `AllowedCalldataBuilderConfig[]` | No | The list of `calldata` that the delegate is allowed to call. Cannot be used together with `exactCalldata`. | +| `exactCalldata` | `ExactCalldataBuilderConfig` | No | The `calldata` that the delegate is allowed to call. By default, the value is set to `0x`. Cannot be used together with `allowedCalldata`. | + +#### Example + +```typescript +import { createDelegation, getDelegatorEnvironment } from "@metamask/delegation-toolkit"; +import { sepolia } from "viem/chains"; + +const delegation = createDelegation({ + scope: { + type: "nativeTokenPeriodTransfer", + periodAmount: 1000000000000000000n, + periodDuration: 86400, + startDate: 1743763600, + }, + // Address that is granting the delegation + from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", + // Address to which the delegation is being granted + to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", + // Alternatively you can use environment property of MetaMask smart account. + environment: getDelegatorEnvironment(sepolia.id); +}); +``` + +### Native token streaming scope + +Ensures a linear streaming transfer limit for native tokens. +Token transfers are blocked until the defined start timestamp. +At the start, a specified initial amount is released, after which tokens accrue linearly at the configured rate, up to the maximum allowed amount. + +#### Parameters + +| Name | Type | Required | Description | +| ----------------- | --------- | -------- | --------------------------------------------------------- | +| `initialAmount` | `bigint` | Yes | The initial amount that can be transferred at start time. | +| `maxAmount` | `bigint` | Yes | The maximum total amount that can be unlocked. | +| `amountPerSecond` | `bigint` | Yes | The rate at which tokens accrue per second. | +| `startTime` | `number` | Yes | The start timestamp in seconds. | +| `allowedCalldata` | `AllowedCalldataBuilderConfig[]` | No | The list of `calldata` that the delegate is allowed to call. Cannot be used together with `exactCalldata`. | +| `exactCalldata` | `ExactCalldataBuilderConfig` | No | The `calldata` that the delegate is allowed to call. By default, the value is set to `0x`. Cannot be used together with `allowedCalldata`. | + +#### Example + +```typescript +import { createDelegation, getDelegatorEnvironment } from "@metamask/delegation-toolkit"; +import { sepolia } from "viem/chains"; + +const delegation = createDelegation({ + scope: { + type: "nativeTokenStreaming", + amountPerSecond: 100n, + initialAmount: 1000000n, + maxAmount: 10000000n, + startTime: 1703980800, + }, + // Address that is granting the delegation + from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", + // Address to which the delegation is being granted + to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", + // Alternatively you can use environment property of MetaMask smart account. + environment: getDelegatorEnvironment(sepolia.id); +}); +``` + +### Native token transfer scope + +Ensures that native token transfers are limited to a predefined maximum amount. +This scope is useful for setting simple, fixed transfer limits without any time based or streaming conditions. + +#### Parameters + +| Name | Type | Required | Description | +| ----------------- | --------- | -------- | ----------------------------------------------------------------- | +| `maxAmount` | `bigint` | Yes | The maximum amount of tokens that can be transferred by delegate. | +| `allowedCalldata` | `AllowedCalldataBuilderConfig[]` | No | The list of `calldata` that the delegate is allowed to call. Cannot be used together with `exactCalldata`. | +| `exactCalldata` | `ExactCalldataBuilderConfig` | No | The `calldata` that the delegate is allowed to call. By default, the value is set to `0x`. Cannot be used together with `allowedCalldata`. | + +#### Example + +```typescript +import { createDelegation, getDelegatorEnvironment } from "@metamask/delegation-toolkit"; +import { sepolia } from "viem/chains"; + +const delegation = createDelegation({ + scope: { + type: "nativeTokenTransferAmount", + // 0.001 ETH in wei format. + maxAmount: 1000000000000000n, + }, + // Address that is granting the delegation + from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", + // Address to which the delegation is being granted + to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", + // Alternatively you can use environment property of MetaMask smart account. + environment: getDelegatorEnvironment(sepolia.id); +}); +``` + +## Function call scope + +Defines the specific methods, contract addresses, and calldata that are allowed for the delegation. + +#### Parameters + +| Name | Type | Required | Description | +| ----------------- | -------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `targets` | `Address[]` | Yes | The list of addresses that the delegate is allowed to call. | +| `selectors` | `MethodSelector[]` | Yes | The list of method selectors that the delegate is allowed to call. The selector value can be 4-byte hex string, ABI function signature, or ABI function object. | +| `allowedCalldata` | `AllowedCalldataBuilderConfig[]` | No | The list of `calldata` that the delegate is allowed to call. Cannot be used together with `exactCalldata`. | +| `exactCalldata` | `ExactCalldataBuilderConfig` | No | The `calldata` that the delegate is allowed to call. Cannot be used together with `allowedCalldata`. | + +#### Example + +This example sets the delegation scope to allow the delegate to call the `approve` function on the USDC token contract. + +```typescript +import { createDelegation, getDelegatorEnvironment } from "@metamask/delegation-toolkit"; +import { sepolia } from "viem/chains"; + +const delegation = createDelegation({ + scope: { + type: "functionCall", + targets: ["0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"], // USDC address on Sepolia. + selectors: ["approve(address, uint256)"] + }, + // Address that is granting the delegation + from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", + // Address to which the delegation is being granted + to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", + // Alternatively you can use environment property of MetaMask smart account. + environment: getDelegatorEnvironment(sepolia.id); +}); +``` + +## Ownership transfer scope + +Restricts a delegation to ownership transfer calls only. + +#### Parameters + +| Name | Type | Required | Description | +| ----------------- | --------- | -------- | -----------------------------------------------------------------------| +| `contractAddress` | `Address` | Yes | The target contract address for which ownership transfers are allowed. | + +#### Example + +```typescript +import { createDelegation, getDelegatorEnvironment } from "@metamask/delegation-toolkit"; +import { sepolia } from "viem/chains"; + +const contractAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" + +const delegation = createDelegation({ + scope: { + type: "ownershipTransfer", + contractAddress, + }, + // Address that is granting the delegation + from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", + // Address to which the delegation is being granted + to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", + // Alternatively you can use environment property of MetaMask smart account. + environment: getDelegatorEnvironment(sepolia.id); +}); +``` diff --git a/delegation-toolkit/reference/api/delegation.md b/delegation-toolkit/reference/delegation/index.md similarity index 85% rename from delegation-toolkit/reference/api/delegation.md rename to delegation-toolkit/reference/delegation/index.md index 254eb890a60..c03c291c7e6 100644 --- a/delegation-toolkit/reference/api/delegation.md +++ b/delegation-toolkit/reference/delegation/index.md @@ -1,6 +1,5 @@ --- description: Delegation-related API methods reference. -sidebar_label: Delegation toc_max_heading_level: 2 --- @@ -58,7 +57,7 @@ export const delegatorSmartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [delegatorAccount.address, [], [], []], deploySalt: "0x", - signatory: { account: delegatorAccount }, + signer: { account: delegatorAccount }, }); ``` @@ -90,22 +89,30 @@ Creates a delegation with a specific delegate. | ---- | ---- | -------- | ----------- | | `from` | `Hex` | Yes | The address that is granting the delegation. | | `to` | `Hex` | Yes | The address to which the delegation is being granted. | -| `caveats` | `Caveats` | Yes | Caveats to restrict the authority being granted. | +| `scope` | `ScopeConfig` | Yes | The scope of the delegation that defines the initial authority. | +| `environment` | `DeleGatorEnvironment` | Yes | The environment used by the toolkit to define contract addresses for interacting with the Delegation Framework contracts. | +| `caveats` | `Caveats` | No | Caveats that further refine the authority granted by the `scope`. | | `parentDelegation` | `Delegation \| Hex` | No | The parent delegation or its corresponding hex to create a delegation chain. | | `salt` | `Hex` | No | The salt for generating the delegation hash. This helps prevent hash collisions when creating identical delegations. | ### Example ```typescript -import { createDelegation } from "@metamask/delegation-toolkit"; +import { createDelegation, getDelegatorEnvironment } from "@metamask/delegation-toolkit"; +import { sepolia } from "viem/chains"; const delegation = createDelegation({ // Address that is granting the delegation from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", // Address to which the delegation is being granted to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", - // Empty caveats array - we recommend adding appropriate restrictions - caveats: [], + // Alternatively you can use environment property of MetaMask smart account. + environment: getDelegatorEnvironment(sepolia.id); + scope: { + type: "nativeTokenTransferAmount", + // 0.001 ETH in wei format. + maxAmount: 1000000000000000n, + }, }); ``` @@ -118,7 +125,9 @@ Creates an open delegation that can be redeemed by any delegate. | Name | Type | Required | Description | | ---- | ---- | -------- | ----------- | | `from` | `Hex` | Yes | The address that is granting the delegation. | -| `caveats` | `Caveats` | Yes | Caveats to restrict the authority being granted. | +| `scope` | `ScopeConfig` | Yes | The scope of the delegation that defines the initial authority. | +| `environment` | `DeleGatorEnvironment` | Yes | The environment used by the toolkit to define contract addresses for interacting with the Delegation Framework contracts. | +| `caveats` | `Caveats` | No | Caveats that further refine the authority granted by the `scope`. | | `parentDelegation` | `Delegation \| Hex` | No | The parent delegation or its corresponding hex to create a delegation chain. | | `salt` | `Hex` | No | The salt for generating the delegation hash. This helps prevent hash collisions when creating identical delegations. | @@ -126,13 +135,19 @@ Creates an open delegation that can be redeemed by any delegate. ### Example ```typescript -import { createOpenDelegation } from "@metamask/delegation-toolkit"; +import { createOpenDelegation, getDelegatorEnvironment } from "@metamask/delegation-toolkit"; +import { sepolia } from "viem/chains"; const delegation = createOpenDelegation({ // Address that is granting the delegation from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", - // Empty caveats array - we recommend adding appropriate restrictions - caveats: [], + // Alternatively you can use environment property of MetaMask smart account. + environment: getDelegatorEnvironment(sepolia.id); + scope: { + type: "nativeTokenTransferAmount", + // 0.001 ETH in wei format. + maxAmount: 1000000000000000n, + }, }); ``` @@ -278,12 +293,17 @@ const disableDelegationData = DelegationManager.encode.disableDelegation({ ```ts import { createDelegation } from "@metamask/delegation-toolkit"; +import { sepolia } from "viem/chains"; export const delegation = createDelegation({ from: "0x7E48cA6b7fe6F3d57fdd0448B03b839958416fC1", to: "0x2B2dBd1D5fbeB77C4613B66e9F35dBfE12cB0488", - // Empty caveats array - we recommend adding appropriate restrictions - caveats: [], + environment: getDelegatorEnvironment(sepolia.id); + scope: { + type: "nativeTokenTransferAmount", + // 0.001 ETH in wei format. + maxAmount: 1000000000000000n, + }, }); ``` @@ -367,7 +387,7 @@ This method supports batch redemption, allowing multiple delegations to be proce | Name | Type | Required | Description | | --- | --- | --- | --- | | `delegations` | `Delegation[][]` | Yes | A nested collection representing chains of delegations. Each inner collection contains a chain of delegations to be redeemed. | -| `modes` | `ExecutionMode[]` | Yes | A collection specifying the [execution mode](../../concepts/delegation/index.md#execution-modes) for each corresponding delegation chain. Supported execution modes are `SINGLE_DEFAULT_MODE`, `SINGLE_TRY_MODE`, `BATCH_DEFAULT_MODE`, and `BATCH_TRY_MODE`. | +| `modes` | `ExecutionMode[]` | Yes | A collection specifying the [execution mode](../../concepts/delegation/index.md#execution-modes) for each corresponding delegation chain. Supported execution modes are `SingleDefault`, `SingleTry`, `BatchDefault`, and `BatchTry`. | | `executions` | `ExecutionStruct[][]` | Yes | A nested collection where each inner collection contains a list of `ExecutionStruct` objects associated with a specific delegation chain. | ### Example @@ -375,14 +395,13 @@ This method supports batch redemption, allowing multiple delegations to be proce This example assumes you have a delegation signed by the delegator. ```ts -import { createExecution } from "@metamask/delegation-toolkit"; +import { createExecution, ExecutionMode } from "@metamask/delegation-toolkit"; import { DelegationManager } from "@metamask/delegation-toolkit/contracts"; -import { SINGLE_DEFAULT_MODE } from "@metamask/delegation-toolkit/utils"; import { zeroAddress } from "viem"; const data = DelegationManager.encode.redeemDelegations({ delegations: [[ signedDelegation ]], - modes: [ SINGLE_DEFAULT_MODE ], + modes: [ ExecutionMode.SingleDefault ], executions: [[ execution ]], }); ``` @@ -432,9 +451,8 @@ import { createWalletClient } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { sepolia } from "viem/chains"; -export const delegationManager = getDeleGatorEnvironment( - sepolia.id -).DelegationManager; +const environment = getDelegatorEnvironment(sepolia.id); +export const delegationManager = environment.DelegationManager; const account = privateKeyToAccount(delegateWallet as `0x${string}`); @@ -451,8 +469,12 @@ const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; export const delegation = createDelegation({ to: delegate, from: account.address, - // Empty caveats array - we recommend adding appropriate restrictions. - caveats: [], + environment, + scope: { + type: "nativeTokenTransferAmount", + // 0.001 ETH in wei format. + maxAmount: 1000000000000000n, + }, }); ``` diff --git a/delegation-toolkit/reference/api/experimental-actions/bundler-client.md b/delegation-toolkit/reference/erc7715/bundler-client.md similarity index 96% rename from delegation-toolkit/reference/api/experimental-actions/bundler-client.md rename to delegation-toolkit/reference/erc7715/bundler-client.md index 196e3a732ee..7ef51bc55e2 100644 --- a/delegation-toolkit/reference/api/experimental-actions/bundler-client.md +++ b/delegation-toolkit/reference/erc7715/bundler-client.md @@ -30,7 +30,7 @@ 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 [`redeemDelegations`](../delegation.md#redeemdelegations) utility function to generate the calldata manually. | +| `permissionsContext` | `0x${string}` | Yes | Encoded calldata for redeeming delegations. If you're not using ERC-7715, you can use the [`redeemDelegations`](../delegation/index.md#redeemdelegations) utility function to generate the calldata manually. | ### Example @@ -91,7 +91,7 @@ export const sessionAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", - signatory: { account }, + signer: { account }, }); export const bundlerClient = createBundlerClient({ diff --git a/delegation-toolkit/reference/api/experimental-actions/wallet-client.md b/delegation-toolkit/reference/erc7715/wallet-client.md similarity index 97% rename from delegation-toolkit/reference/api/experimental-actions/wallet-client.md rename to delegation-toolkit/reference/erc7715/wallet-client.md index ccbaeb20fa8..6aa3e66c8df 100644 --- a/delegation-toolkit/reference/api/experimental-actions/wallet-client.md +++ b/delegation-toolkit/reference/erc7715/wallet-client.md @@ -100,7 +100,7 @@ This function has the same parameters, and it also requires the following parame | 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 [`redeemDelegations`](../delegation.md#redeemdelegations) utility function to generate the calldata manually. | +| `permissionsContext` | `0x${string}` | Yes | Encoded calldata for redeeming delegations. If you're not using ERC-7715, you can use the [`redeemDelegations`](../delegation/index.md#redeemdelegations) utility function to generate the calldata manually. | ### Example diff --git a/delegation-toolkit/reference/api/smart-account.md b/delegation-toolkit/reference/smart-account.md similarity index 92% rename from delegation-toolkit/reference/api/smart-account.md rename to delegation-toolkit/reference/smart-account.md index 7ef02dc7041..01c8110b408 100644 --- a/delegation-toolkit/reference/api/smart-account.md +++ b/delegation-toolkit/reference/smart-account.md @@ -9,7 +9,7 @@ import TabItem from "@theme/TabItem"; # MetaMask Smart Accounts API reference -The following API methods are related to creating, managing, and signing with [MetaMask Smart Accounts](../../concepts/smart-accounts.md). +The following API methods are related to creating, managing, and signing with [MetaMask Smart Accounts](../concepts/smart-accounts.md). ## `aggregateSignature` @@ -95,7 +95,7 @@ export const aliceSmartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.MultiSig, deployParams: [signers, threshold], deploySalt: "0x", - signatory: [ { account: aliceAccount } ], + signer: [ { account: aliceAccount } ], }); export const bobSmartAccount = await toMetaMaskSmartAccount({ @@ -103,7 +103,7 @@ export const bobSmartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.MultiSig, deployParams: [signers, threshold], deploySalt: "0x", - signatory: [ { account: bobAccount } ], + signer: [ { account: bobAccount } ], }); export const bundlerClient = createBundlerClient({ @@ -119,7 +119,7 @@ export const bundlerClient = createBundlerClient({ Encodes calls for execution by a MetaMask smart account. If there's a single call directly to the smart account, it returns the call data directly. For multiple calls or calls to other addresses, it creates executions and encodes them for the smart account's `execute` function. -The execution mode is set to `SINGLE_DEFAULT_MODE` for a single call to other address, or `BATCH_DEFAULT_MODE` for multiple calls. +The execution mode is set to `SingleDefault` for a single call to other address, or `BatchDefault` for multiple calls. ### Parameters @@ -169,7 +169,7 @@ export const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [delegatorAccount.address, [], [], []], deploySalt: "0x", - signatory: { account: delegatorAccount }, + signer: { account: delegatorAccount }, }); ``` @@ -216,7 +216,7 @@ export const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [delegatorAccount.address, [], [], []], deploySalt: "0x", - signatory: { account: delegatorAccount }, + signer: { account: delegatorAccount }, }); ``` @@ -264,7 +264,7 @@ export const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [delegatorAccount.address, [], [], []], deploySalt: "0x", - signatory: { account: delegatorAccount }, + signer: { account: delegatorAccount }, }); ``` @@ -288,7 +288,7 @@ Signs the delegation and returns the delegation signature. ```ts -import { createDelegation } from "@metamask/delegation-toolkit"; +import { createDelegation, getDelegatorEnvironment } from "@metamask/delegation-toolkit"; import { delegatorSmartAccount } from "./config.ts"; // The address to which the delegation is granted. It can be an EOA address, or @@ -298,8 +298,12 @@ const delegate = "0x2FcB88EC2359fA635566E66415D31dD381CF5585"; const delegation = createDelegation({ to: delegate, from: account.address, - // Empty caveats array - we recommend adding appropriate restrictions. - caveats: [], + environment: delegatorSmartAccount.environment, + scope: { + type: "nativeTokenTransferAmount", + // 0.001 ETH in wei format. + maxAmount: 1000000000000000n, + }, }); const signature = delegatorSmartAccount.signDelegation({ delegation }); @@ -329,7 +333,7 @@ export const delegatorSmartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [delegatorAccount.address, [], [], []], deploySalt: "0x", - signatory: { account: delegatorAccount }, + signer: { account: delegatorAccount }, }); ``` @@ -339,7 +343,7 @@ export const delegatorSmartAccount = await toMetaMaskSmartAccount({ ## `signMessage` Generates the [EIP-191](https://eips.ethereum.org/EIPS/eip-191) signature -using the `MetaMaskSmartAccount` signatory. The Delegation Toolkit +using the `MetaMaskSmartAccount` signer. The Delegation Toolkit uses Viem under the hood to provide this functionality. ### Parameters @@ -383,7 +387,7 @@ export const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", - signatory: { account }, + signer: { account }, }); ``` @@ -393,7 +397,7 @@ export const smartAccount = await toMetaMaskSmartAccount({ ## `signTypedData` Generates the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) signature -using the `MetaMaskSmartAccount` signatory. The Delegation Toolkit +using the `MetaMaskSmartAccount` signer. The Delegation Toolkit uses Viem under the hood to provide this functionality. ### Parameters @@ -450,7 +454,7 @@ export const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", - signatory: { account }, + signer: { account }, }); ``` @@ -459,7 +463,7 @@ export const smartAccount = await toMetaMaskSmartAccount({ ## `signUserOperation` -Signs a user operation with the `MetaMaskSmartAccount` signatory. The Delegation +Signs a user operation with the `MetaMaskSmartAccount` signer. The Delegation Toolkit uses Viem under the hood to provide this functionality. ### Parameters @@ -511,7 +515,7 @@ export const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", - signatory: { account }, + signer: { account }, }); ``` @@ -528,7 +532,7 @@ Creates a `MetaMaskSmartAccount` instance. | ---- |-----------------------------------------------------|--------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `client` | `Client` | Yes | Viem Client to retrieve smart account data. | | `implementation` | `TImplementation` | Yes | Implementation type for the smart account. Can be Hybrid, Multisig, or Stateless7702. | -| `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. | +| `signer` | `SignerConfigByImplementation ` | Yes | Signers for the smart account. Can be a Viem Account, Viem Wallet Client, or a WebAuthnAccount. Web3AuthnAccounts are only supported for Hybrid implementations. | | `environment` | `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. | @@ -564,7 +568,7 @@ const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", - signatory: { account: account }, + signer: { account: account }, }); ``` @@ -621,7 +625,7 @@ const aliceSmartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.MultiSig, deployParams: [signers, threshold], deploySalt: "0x", - signatory: [ { account: aliceAccount } ], + signer: [ { account: aliceAccount } ], }); ``` @@ -664,7 +668,7 @@ const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Stateless7702, address: account.address, - signatory: { account }, + signer: { account }, }); ``` diff --git a/gator-sidebar.js b/gator-sidebar.js index 70cbe0797f4..fd3e183d0d1 100644 --- a/gator-sidebar.js +++ b/gator-sidebar.js @@ -1,6 +1,7 @@ // @ts-check /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ + const sidebar = { gatorSidebar: [ { @@ -23,7 +24,7 @@ const sidebar = { 'get-started/smart-account-quickstart/eip7702', ], }, - 'get-started/erc7715-quickstart', + // 'get-started/erc7715-quickstart', 'get-started/use-the-cli', 'get-started/supported-networks', ], @@ -69,20 +70,32 @@ const sidebar = { collapsed: false, items: [ 'guides/delegation/execute-on-smart-accounts-behalf', - 'guides/delegation/restrict-delegation', + { + type: 'category', + label: 'Use delegation scopes', + collapsed: false, + link: { type: "doc", id: "guides/delegation/use-delegation-scopes/index" }, + items: [ + 'guides/delegation/use-delegation-scopes/spending-limit', + 'guides/delegation/use-delegation-scopes/function-call', + 'guides/delegation/use-delegation-scopes/ownership-transfer', + 'guides/delegation/use-delegation-scopes/constrain-scope', + ], + }, + 'guides/delegation/check-delegation-state', ], }, ], }, - { - type: 'category', - label: 'Experimental', - collapsed: false, - items: [ - 'experimental/erc-7715-request-permissions', - 'experimental/erc-7710-redeem-delegations', - ], - }, + // { + // type: 'category', + // label: 'Experimental', + // collapsed: false, + // items: [ + // 'experimental/erc-7715-request-permissions', + // 'experimental/erc-7710-redeem-delegations', + // ], + // }, { type: 'category', label: 'Tutorials', @@ -103,6 +116,11 @@ const sidebar = { label: "Create a custom caveat enforcer", href: "/tutorials/create-custom-caveat-enforcer" }, + { + type: "link", + label: "Create a social invite link", + href: "/tutorials/create-invite-link" + }, ], }, { @@ -110,25 +128,27 @@ const sidebar = { label: 'Reference', collapsed: false, items: [ - 'reference/caveats', + 'reference/smart-account', { type: 'category', - label: 'Delegation Toolkit API', + label: 'Delegation', collapsed: false, + link: { type: "doc", id: "reference/delegation/index" }, items: [ - 'reference/api/delegation', - 'reference/api/smart-account', - { - type: 'category', - label: 'Experimental actions', - collapsed: false, - items: [ - 'reference/api/experimental-actions/bundler-client', - 'reference/api/experimental-actions/wallet-client', - ], - }, + 'reference/delegation/delegation-scopes', + 'reference/delegation/caveats', + 'reference/delegation/caveat-enforcer-client', ], }, + // { + // type: 'category', + // label: 'ERC-7715', + // collapsed: false, + // items: [ + // 'reference/erc7715/wallet-client', + // 'reference/erc7715/bundler-client', + // ], + // }, ], }, ], diff --git a/src/pages/tutorials/create-custom-caveat-enforcer.md b/src/pages/tutorials/create-custom-caveat-enforcer.md index c952fef8bbe..275503978cd 100644 --- a/src/pages/tutorials/create-custom-caveat-enforcer.md +++ b/src/pages/tutorials/create-custom-caveat-enforcer.md @@ -12,7 +12,7 @@ import TabItem from "@theme/TabItem"; This tutorial walks you through creating a custom [caveat enforcer](/delegation-toolkit/development/concepts/delegation/caveat-enforcers) and applying it to a [delegation](/delegation-toolkit/concepts/delegation/). -The MetaMask Delegation Toolkit includes [out-of-the-box caveat enforcers](/delegation-toolkit/reference/caveats) that define rules and restrictions for common use cases. +The MetaMask Delegation Toolkit includes [out-of-the-box caveat enforcers](/delegation-toolkit/reference/delegation/caveats) that define rules and restrictions for common use cases. For more specific control or other use cases, you can create custom caveat enforcers. In this tutorial, you'll create and apply a caveat enforcer that only allows a delegation to be redeemed after a specific timestamp. @@ -100,8 +100,7 @@ The Forge CLI will display the address of the deployed caveat enforcer. ### 4. Apply the caveat enforcer -Specify the address of the deployed `AfterTimestampEnforcer.sol` contract, add it to the caveat builder, and create a delegation. -Learn more about [applying caveats to a delegation](/delegation-toolkit/development/guides/delegation/restrict-delegation). +Specify the address of the deployed `AfterTimestampEnforcer.sol` contract, add it to the [caveat builder](/delegation-toolkit/reference/delegation/delegation-api.md#createcaveatbuilder), and create a delegation. The following code snippet uses the custom caveat enforcer to create a delegation granting a 1,000,000 wei allowance that becomes spendable one hour after it is created: @@ -110,7 +109,8 @@ a 1,000,000 wei allowance that becomes spendable one hour after it is created: ```typescript -import { createCaveatBuilder, createDelegation } from '@metamask/delegation-toolkit' +import { createDelegation, ROOT_AUTHORITY } from '@metamask/delegation-toolkit' +import { createCaveatBuilder } from '@metamask/delegation-toolkit/utils' import { toHex } from 'viem' import { delegatorSmartAccount } from './config.ts' @@ -128,11 +128,13 @@ const caveats = caveatBuilder.addCaveat('nativeTokenTransferAmount', 1000000n).a terms: toHex(tenAM), }) -const delegation = createDelegation({ - to: "DELEGATE_ADDRESS", - from: delegatorSmartAccount.address, - caveats, -}) +const delegation: Delegation = { + delegate: "DELEGATE_ADDRESS", + delegator: delegatorSmartAccount.address, + authority: ROOT_AUTHORITY, + caveats: caveats.build(), + salt: '0x', +}; ``` @@ -157,7 +159,7 @@ export const delegatorSmartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: '0x', - signatory: { account }, + signer: { account }, }) ``` diff --git a/src/pages/tutorials/create-invite-link.md b/src/pages/tutorials/create-invite-link.md new file mode 100644 index 00000000000..f1efae319b3 --- /dev/null +++ b/src/pages/tutorials/create-invite-link.md @@ -0,0 +1,303 @@ +--- +title: Create a social invite link +image: 'img/tutorials/tutorials-banners/create-invite-link.png' +description: Allow users to refer their friends to your dapp using an invite link. +tags: [delegation toolkit, embedded wallets, social, invite, referral, link] +date: Sep 8, 2025 +author: MetaMask Developer Relations +toc_max_heading_level: 4 +--- + +This tutorial walks you through creating an invite link so users can refer their friends to your dapp with minimal friction. + +For example, Alice (the inviter) wants Bob (the invitee) to try out your dapp. +She sends him a link that allows him to claim 0.001 ETH from her wallet within a time limit. +Bob can start using your dapp right away, without installing a wallet or paying gas fees. + +You'll enable this by: + +- Adding an [embedded wallet](/embedded-wallets) for instant onboarding. +- Creating a [MetaMask smart account](/delegation-toolkit/concepts/smart-accounts) to create and redeem an invitation. +- Creating an [open delegation](/delegation-toolkit/concepts/delegation) to represent an invitation. + +## 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. +- Get a [Client ID](/embedded-wallets/dashboard) from the Embedded Wallets (Web3Auth) dashboard. +- [Create a Pimlico API key](https://docs.pimlico.io/guides/create-api-key#create-api-key). + :::note + This tutorial uses Pimlico's bundler and paymaster, but you can use any bundler and paymaster of your choice. + ::: + +## Steps + +### 1. Set up the project + +#### 1.1. Install dependencies + +Install the [MetaMask Delegation Toolkit](https://www.npmjs.com/package/@metamask/delegation-toolkit) and other dependencies in your project: + +```bash npm2yarn +npm install @metamask/delegation-toolkit @web3auth/modal wagmi @tanstack/react-query +``` + +#### 1.2. Set up Embedded Wallets (Web3Auth) + +Configure [MetaMask Embedded Wallets (Web3Auth)](/embedded-wallets/) to enable users to instantly connect to your dapp using familiar login methods, like social accounts or email. + +1. Add a `WEB3AUTH_CLIENT_ID` environment variable, replacing `` with your Web3Auth Client ID: + + ```txt title='.env.local' + WEB3AUTH_CLIENT_ID= + ``` + +2. Configure Web3Auth options: + + ```tsx title='providers/AppProvider.tsx' + import { WEB3AUTH_NETWORK, Web3AuthOptions } from '@web3auth/modal'; + + const web3AuthOptions: Web3AuthOptions = { + clientId: process.env.WEB3AUTH_CLIENT_ID as string, + web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_MAINNET, + }; + + const web3authConfig = { + web3AuthOptions, + }; + ``` + +3. Create a connect button: + + ```tsx title='components/ConnectButton.tsx' + import { useWeb3AuthConnect } from '@web3auth/modal/react'; + import Button from '@/components/Button'; // You can add your own Button component + + export default function ConnectButton() { + const { connect } = useWeb3AuthConnect(); + + return ( +
+ +
+ ); + } + ``` + +#### 1.3. Set up Wagmi + +Wrap your dapp with the Wagmi, Web3Auth, and React Query providers. +Add Wagmi using the Web3Auth Wagmi adapter so wallet connections from Web3Auth are available to Wagmi hooks. + +```tsx title='providers/AppProvider.tsx' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { Web3AuthProvider } from '@web3auth/modal/react'; +import { WagmiProvider } from '@web3auth/modal/react/wagmi'; + +const queryClient = new QueryClient(); + +export function AppProvider({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + ); +} +``` + +### 2. Create a Bundler Client + +Create a [Viem Bundler Client](https://viem.sh/account-abstraction/clients/bundler) using Viem's `createBundlerClient` function. +You can use the bundler service to estimate gas for user operations and submit transactions to the network. + +Set `paymaster` to `true` to use the Pimlico paymaster with the Bundler Client, and replace `` with your Pimlico API key: + +```tsx +import { createBundlerClient } from 'viem/account-abstraction'; +import { usePublicClient } from "wagmi"; + +const publicClient = usePublicClient(); + +const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http('https://api.pimlico.io/v2/11155111/rpc?apikey='), + paymaster: true, // The same Pimlico URL will be used for bundler and paymaster. +}); +``` + +### 3. Create a smart account + +Create an account to create and redeem an invitation. +This account will create a delegation, and must be a [MetaMask smart account](/delegation-toolkit/concepts/smart-accounts). +This example uses a [Hybrid smart account](/delegation-toolkit/development/guides/smart-accounts/create-smart-account/#create-a-hybrid-smart-account), which is a flexible smart account implementation that supports both an externally owned account (EOA) owner and any number of passkey (WebAuthn) signers: + +```tsx +import { Implementation, toMetaMaskSmartAccount } from '@metamask/delegation-toolkit'; +import { useAccount, usePublicClient, useWalletClient } from "wagmi"; + +const { address } = useAccount(); +const publicClient = usePublicClient(); +const { data: walletClient } = useWalletClient(); + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [address, [], [], []], + deploySalt: '0x', + signer: { walletClient }, +}); +``` + +### 4. Create an invitation + +#### 4.1. Deploy the account + +To create an invitation, first deploy the smart account by sending a user operation: + +```ts +import { zeroAddress } 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: zeroAddress }], + maxFeePerGas, + maxPriorityFeePerGas, +}); +``` + +#### 4.2. Fund the account + +Fund the deployed smart account with some Sepolia ETH to enable the invitee to spend funds when they redeem the invitation. + +:::note +You can use the [MetaMask faucet](/developer-tools/faucet) to get Sepolia ETH. +::: + +#### 4.3. Create an open root delegation + +Create an [open root delegation](/delegation-toolkit/concepts/delegation) to represent an invitation. +A root delegation is the first delegation in a chain of delegations, and an open root delegation grants permission to any account. +In this example, the inviter creates an invitation that can be redeemed by any invitee, allowing the invitee to spend up to 0.001 ETH. + +```ts +import { createOpenDelegation } from '@metamask/delegation-toolkit'; + +const delegation = createOpenDelegation({ + from: smartAccount.address, + environment: smartAccount.environment; + scope: { + type: 'nativeTokenTransferAmount', + // 0.001 ETH in wei format. + maxAmount: 1000000000000000n, + }, +}); +``` + +#### 4.4. Sign the delegation + +Sign the delegation to enable the invitee to redeem the invitation in the future: + +```tsx +const signature = await smartAccount.signDelegation({ + delegation, +}) + +const signedDelegation = { + ...delegation, + signature, +} +``` + +#### 4.5. Share the invitation + +Encode the delegation into a shareable invite link: + +```tsx +import { Delegation } from '@metamask/delegation-toolkit'; + +export function encodeDelegation(delegation: Delegation): string { + const delegationJson = JSON.stringify(delegation); + return Buffer.from(delegationJson, 'utf-8').toString('base64'); +} + +const encoded = encodeDelegation(signedDelegation); + +const url = new URL(window.location.href); +url.searchParams.set("delegation", encoded); +const shareableUrl = url.toString(); +``` + +The inviter can now share the link with anyone. + +### 5. Redeem the invitation + +#### 5.1. Decode the shared invitation + +When the invitee opens the shared invite link, decode the delegation: + +```tsx +import { Delegation } from '@metamask/delegation-toolkit'; + +const urlParams = new URLSearchParams(window.location.search); +const encodedDelegation = urlParams.get('delegation'); + +export function decodeDelegation(encodedDelegation: string): Delegation { + const decodedDelegationJson = Buffer.from(encodedDelegation, 'base64').toString('utf-8'); + return JSON.parse(decodedDelegationJson) as Delegation; +} + +const decodedDelegation = decodeDelegation(encodedDelegation); +``` + +#### 5.2. Redeem the delegation + +[Redeem the delegation](/delegation-toolkit/development/guides/delegation/execute-on-smart-accounts-behalf/#7-redeem-the-delegation) by submitting a user operation from the smart account to the `DelegationManager` contract. +Submitting the user operation deploys the account for first-time users. + +The delegation manager validates the delegation and executes delegated actions. +In this case, the invitee can spend up to 0.001 ETH when using your dapp. + +```ts +import { createExecution, getDeleGatorEnvironment, ExecutionMode } from '@metamask/delegation-toolkit'; +import { DelegationManager } from '@metamask/delegation-toolkit/contracts'; + +const delegations = [decodedDelegation]; + +const executions = createExecution(smartAccount.address, 1000000000000000n); + +const redeemDelegationCalldata = DelegationManager.encode.redeemDelegations({ + delegations: [delegations], + modes: [ExecutionMode.SingleDefault], + executions: [executions] +}); + +// 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: smartAccount.address, + data: redeemDelegationCalldata, + }, + ], + maxFeePerGas, + maxPriorityFeePerGas, +}); +``` + +## Next steps + +- See [`invitation-link-example`](https://github.com/MetaMask/gator-examples/tree/feat/invitation-link-example/examples/invitation-link-example) on GitHub for a complete example dapp. +- When creating an invitation, you can add more rules and restrictions using [delegation scopes](/delegation-toolkit/development/guides/delegation/use-delegation-scopes) and [caveat enforcers](/delegation-toolkit/development/guides/delegation/use-delegation-scopes/refine-scope). +- Learn more about [smart account implementations](/delegation-toolkit/development/guides/smart-accounts/create-smart-account). +- Learn more about [delegation types](/delegation-toolkit/development/concepts/delegation/#delegation-types). diff --git a/src/pages/tutorials/use-erc20-paymaster.md b/src/pages/tutorials/use-erc20-paymaster.md index bc7ea7bf1fd..b68c4955c82 100644 --- a/src/pages/tutorials/use-erc20-paymaster.md +++ b/src/pages/tutorials/use-erc20-paymaster.md @@ -102,7 +102,7 @@ const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: "0x", - signatory: { account }, + signer: { account }, }); ``` diff --git a/src/pages/tutorials/use-passkey-as-backup-signer.md b/src/pages/tutorials/use-passkey-as-backup-signer.md index 5d1e7669976..2594512d292 100644 --- a/src/pages/tutorials/use-passkey-as-backup-signer.md +++ b/src/pages/tutorials/use-passkey-as-backup-signer.md @@ -84,7 +84,7 @@ const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: '0x', - signatory: { account }, + signer: { account }, }) // Deploy the smart account by sending a user operation. @@ -178,7 +178,7 @@ const smartAccount = await toMetaMaskSmartAccount({ implementation: Implementation.Hybrid, deployParams: [owner, [credential.id], [publicKey.x], [publicKey.y]], deploySalt: '0x', - signatory: { webAuthnAccount, keyId: toHex(credential.id) }, + signer: { webAuthnAccount, keyId: toHex(credential.id) }, }) ``` diff --git a/src/scss/theme/_toc.scss b/src/scss/theme/_toc.scss index a9cc52f1afa..8796b7eeeb9 100644 --- a/src/scss/theme/_toc.scss +++ b/src/scss/theme/_toc.scss @@ -48,6 +48,7 @@ border-radius: 0.4rem; background: var(--ifm-code-background); padding: 0.2rem 0.5rem; + hyphens: auto; } } li { diff --git a/src/theme/MDXPage/index.tsx b/src/theme/MDXPage/index.tsx index bb1c12cc856..ac5c7c652b4 100644 --- a/src/theme/MDXPage/index.tsx +++ b/src/theme/MDXPage/index.tsx @@ -167,7 +167,11 @@ export default function MDXPage(props: ComponentProps) { {MDXPageContent.toc && (
- +
)} diff --git a/static/img/tutorials/tutorials-banners/create-invite-link.png b/static/img/tutorials/tutorials-banners/create-invite-link.png new file mode 100644 index 00000000000..1edfd47bdc7 Binary files /dev/null and b/static/img/tutorials/tutorials-banners/create-invite-link.png differ diff --git a/vercel.json b/vercel.json index 125b355eed8..90cb06fce19 100644 --- a/vercel.json +++ b/vercel.json @@ -740,7 +740,7 @@ }, { "source": "/delegation-toolkit/development/how-to/create-delegation/restrict-delegation/", - "destination": "/delegation-toolkit/development/guides/delegation/restrict-delegation/" + "destination": "/delegation-toolkit/development/guides/delegation/use-delegation-scopes/constrain-scope/" }, { "source": "/delegation-toolkit/development/how-to/redeem-delegation/", @@ -766,6 +766,26 @@ "source": "/delegation-toolkit/development/concepts/caveat-enforcers/", "destination": "/delegation-toolkit/development/concepts/delegation/caveat-enforcers/" }, + { + "source": "/delegation-toolkit/development/reference/caveats/", + "destination": "/delegation-toolkit/development/reference/delegation/caveats/" + }, + { + "source": "/delegation-toolkit/development/reference/api/delegation/", + "destination": "/delegation-toolkit/development/reference/delegation/" + }, + { + "source": "/delegation-toolkit/development/reference/api/smart-account/", + "destination": "/delegation-toolkit/development/reference/smart-account/" + }, + { + "source": "/delegation-toolkit/development/reference/api/experimental-actions/bundler-client", + "destination": "/delegation-toolkit/development/reference/erc7715/bundler-client/" + }, + { + "source": "/delegation-toolkit/development/reference/api/experimental-actions/wallet-client", + "destination": "/delegation-toolkit/development/reference/erc7715/wallet-client/" + }, { "source": "/delegation-toolkit/development/concepts/environment/", "destination": "/delegation-toolkit/development/guides/configure-toolkit/"