diff --git a/README.md b/README.md index fbe94122..4f0b94d8 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ This monorepo consists of a suite of tools to enable developers to build using t - `/smart-accounts-kit` has utilities for creating a [DeleGator SCA](https://github.com/MetaMask/delegation-framework/blob/main/documents/DeleGatorCore.md#metamasks-delegatorcore) know as MetaMask smart account, setting up delegations, and redeeming them. - `/delegator-e2e` has end-to-end tests for the Smart Accounts Kit. - `./shared` contains basic shared resources for configuring and testing the packages. +- `./skills` contains agent skills that help AI coding assistants use the Smart Accounts Kit correctly. + - [`/smart-accounts-kit`](./skills/smart-accounts-kit/SKILL.md) — build dApps with MetaMask Smart Accounts, ERC-7710 delegations, and ERC-7715 Advanced Permissions. + - [`/x402-payments`](./skills/x402-payments/SKILL.md) — build x402 (HTTP 402) machine-to-machine payment flows using ERC-7710 delegations and ERC-7715 Advanced Permissions. ## Getting Started diff --git a/skills/smart-accounts-kit/SKILL.md b/skills/smart-accounts-kit/SKILL.md new file mode 100644 index 00000000..723b7b9c --- /dev/null +++ b/skills/smart-accounts-kit/SKILL.md @@ -0,0 +1,85 @@ +--- +name: smart-accounts-kit +version: 1 +description: Build dApps with MetaMask Smart Accounts Kit — ERC-4337 smart accounts, delegations, and Advanced Permissions (ERC-7715) +--- + +# MetaMask Smart Accounts Kit + +## When to use + +- You want to create ERC-4337 smart accounts (Hybrid, MultiSig, or Stateless7702) +- You want to send user operations or batch transactions via bundlers +- You want to configure signers (EOA, passkey/WebAuthn, multisig, wallet client) +- You want to implement gas abstraction with paymasters +- You want to create, sign, or redeem delegations (ERC-7710) +- You want to request Advanced Permissions via MetaMask extension (ERC-7715) +- You want to build automated backend services (DCA bots, keeper services) using delegations +- You want to implement session accounts for AI agents or automated trading +- You want to set up parallel user operations with nonce keys + +## Installation + +```bash +npm install @metamask/smart-accounts-kit permissionless +``` + +## Which smart account type to use + +| Name | Usage | +|------|-------| +| Hybrid | A flexible account with EOA, wallet client, and passkey (WebAuthn) signers. The most flexible option for standard dApp users. | +| MultiSig | Requires multiple signers to meet a threshold before transactions execute. Best for treasury, DAO, or shared custody use cases. | +| Stateless7702 | Upgrades an existing EOA to a smart account using EIP-7702 while keeping the same address. Best for users with existing embedded EOAs. | + +If the user hasn't specified which implementation they need, present the options. + +## Delegations vs Advanced Permissions + +| Name | Usage | +|------|-------| +| Delegations (ERC-7710) | You create, sign, and manage delegations programmatically. The delegator is a smart account you control. You handle the full lifecycle: creation, signing, storage, and redemption. | +| Advanced Permissions (ERC-7715) | You request permissions from a MetaMask user through a human-readable UI in the extension. MetaMask creates and enforces the delegations internally. The user can review and adjust parameters before approving. | + +Advanced Permissions use delegations under the hood — ERC-7715 creates ERC-7710 delegations internally. If the user hasn't specified which to use, present the options. + +## API references + +| Use case | Reference | Workflows | +|----------|-----------|-----------| +| Create a smart account | [toMetaMaskSmartAccount](./references/smart-accounts.md) | [Create hybrid account](./workflows/create-hybrid-account.md), [Create multisig account](./workflows/create-multisig-account.md), [Create 7702 account](./workflows/create-7702-account.md) | +| Create a delegation | [createDelegation](./references/delegations.md) | [Create delegation](./workflows/create-delegation.md), [Create redelegation](./workflows/create-redelegation.md) | +| Request ERC-7715 permissions | [requestExecutionPermissions](./references/advanced-permissions.md) | [Request permissions](./workflows/request-permissions.md), [Redeem — smart account](./workflows/redeem-permissions-smart-account.md), [Redeem — EOA](./workflows/redeem-permissions-eoa.md) | + +## Workflows + +| Use case | Workflow | +|----------|----------| +| Create a Hybrid smart account | [Create hybrid account](./workflows/create-hybrid-account.md) | +| Create a MultiSig smart account | [Create multisig account](./workflows/create-multisig-account.md) | +| Create a Stateless7702 smart account | [Create 7702 account](./workflows/create-7702-account.md) | +| Create and sign a delegation | [Create delegation](./workflows/create-delegation.md) | +| Create a delegation chain (redelegation) | [Create redelegation](./workflows/create-redelegation.md) | +| Redeem a delegation when the delegate is a smart account | [Redeem delegation — smart account](./workflows/redeem-delegation-smart-account.md) | +| Redeem a delegation when the delegate is an EOA | [Redeem delegation — EOA](./workflows/redeem-delegation-eoa.md) | +| Request ERC-7715 Advanced Permissions | [Request permissions](./workflows/request-permissions.md) | +| Redelegate an ERC-7715 permission context | [Create redelegation for permissions](./workflows/create-redelegation-permissions.md) | +| Redeem ERC-7715 permissions when the session account is a smart account | [Redeem permissions — smart account](./workflows/redeem-permissions-smart-account.md) | +| Redeem ERC-7715 permissions when the session account is an EOA | [Redeem permissions — EOA](./workflows/redeem-permissions-eoa.md) | + +## Important notes + +- Always use caveats — never create unrestricted delegations. +- Deploy the delegator first — the account must be deployed before redeeming delegations. +- Function call scope defaults to no native token — use `valueLte` to allow it. +- Caveats are cumulative in delegation chains — restrictions stack. +- ERC-7715 requires MetaMask Flask 13.5.0+ or MetaMask stable 13.23.0+, and the user must have a smart account. +- Always check that the project builds successfully after making changes. +- Smart Accounts Kit version: 1.6.0 | Delegation Framework: 1.3.0 + +## Resources + +- NPM: `@metamask/smart-accounts-kit` +- Contract deployments: [Delegation Framework deployments](https://github.com/MetaMask/delegation-framework/blob/main/documents/Deployments.md) +- Docs: https://docs.metamask.io/smart-accounts-kit +- MetaMask Flask: https://metamask.io/flask diff --git a/skills/smart-accounts-kit/references/advanced-permissions.md b/skills/smart-accounts-kit/references/advanced-permissions.md new file mode 100644 index 00000000..544f24d3 --- /dev/null +++ b/skills/smart-accounts-kit/references/advanced-permissions.md @@ -0,0 +1,69 @@ +--- +name: Advanced Permissions +description: API reference for requesting ERC-7715 Advanced Permissions from MetaMask extension +--- + +# Advanced Permissions + +## When to use + +- You want to request fine-grained permissions from MetaMask with a human-readable UI. +- You want periodic or streaming spending allowances without per-transaction approvals. +- You want to build dApps with background or automated transaction execution. + +## API reference + +### `requestExecutionPermissions` parameters + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `chainId` | `number` | Yes | The chain ID on which the permission is being requested | +| `to` | `Address` | Yes | The account to which the permission will be assigned | +| `permission` | `SupportedPermissionParams` | Yes | The permission type being requested, with `isAdjustmentAllowed` flag | +| `expiry` | `number` | Yes | The timestamp (in seconds) by which the permission must expire | +| `from` | `Address` | No | The wallet address from which permission is requested | + +For more details, see the [requestExecutionPermissions reference](https://docs.metamask.io/smart-accounts-kit/reference/advanced-permissions/wallet-client.md). + +### Supported permission types + +| Type | When to use | +|------|-------------| +| `erc20-token-allowance` | You want a fixed ERC-20 transfer limit that depletes until the total reaches the allowance | +| `erc20-token-periodic` | You want a per-period ERC-20 limit that resets at the start of each new period | +| `erc20-token-stream` | You want a linear streaming ERC-20 limit that accrues over time | +| `native-token-allowance` | You want a fixed native token transfer limit that depletes until the total reaches the allowance | +| `native-token-periodic` | You want a per-period native token limit that resets at the start of each new period | +| `native-token-stream` | You want a linear streaming native token limit that accrues over time | +| `token-approval-revocation` | You want to revoke existing token approvals (ERC-20, ERC-721, Permit2) on behalf of the user | + +The token allowance, native token allowance, periodic, and stream permission types accept optional `startTime`, `justification`, and `isAdjustmentAllowed` parameters. The `token-approval-revocation` type only accepts `justification` and `isAdjustmentAllowed`. For full parameter details, see the [permissions reference](https://docs.metamask.io/smart-accounts-kit/reference/advanced-permissions/permissions.md). + +### Response structure + +```typescript +{ + context: Hex, // Encoded permission context for redemption + delegationManager: Address, // Delegation Manager address + chainId: number, + from: Address, // Granting address + to: Hex, // Receiving address + permission: PermissionTypes, + dependencies: { factory: Address, factoryData: Hex }[], +} +``` + +## Important rules + +- You need MetaMask Flask 13.5.0+ or MetaMask stable 13.23.0+. +- The user must have a smart account — ERC-7715 creates ERC-7710 delegations under the hood. +- Place `isAdjustmentAllowed` inside the `permission` object. It's always recommended. +- Handle denial gracefully — provide a manual transaction fallback. +- `erc20-token-revocation` is deprecated — use `token-approval-revocation` instead. + +## Workflows + +- [Request permissions](../workflows/request-permissions.md) — request ERC-7715 permissions from MetaMask +- [Create redelegation for permissions](../workflows/create-redelegation-permissions.md) — redelegate a permission context to another account +- [Redeem permissions — smart account](../workflows/redeem-permissions-smart-account.md) — redeem when the session account is a smart account +- [Redeem permissions — EOA](../workflows/redeem-permissions-eoa.md) — redeem when the session account is an EOA \ No newline at end of file diff --git a/skills/smart-accounts-kit/references/delegations.md b/skills/smart-accounts-kit/references/delegations.md new file mode 100644 index 00000000..8dfd0347 --- /dev/null +++ b/skills/smart-accounts-kit/references/delegations.md @@ -0,0 +1,104 @@ +--- +name: Delegations +description: API reference for creating delegations with scopes and caveat enforcers — ERC-7710 delegation framework +--- + +# Delegations + +## When to use + +- You want to grant permissions from one account to another (delegator to delegate). +- You want to set spending limits (ERC-20, native token, NFT). +- You want to restrict function calls to specific contracts or methods. +- You want to add time limits, call limits, or other restrictions via caveats. +- You want to create delegation chains (redelegation). + +## API reference + +### `createDelegation` parameters + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `from` | `Hex` | Yes | The address granting the delegation | +| `to` | `Hex` | Yes | The address receiving the delegation | +| `scope` | `ScopeConfig` | Yes | Defines the initial authority of the delegation | +| `environment` | `SmartAccountsEnvironment` | Yes | Contract addresses for framework interactions | +| `caveats` | `Caveats` | No | Caveats that further refine the authority granted by the scope | +| `parentDelegation` | `Delegation \| Hex` | No | Parent delegation for creating a chain (mutually exclusive with `parentPermissionContext`) | +| `parentPermissionContext` | `PermissionContext` | No | Parent chain as hex or decoded values, leaf first (mutually exclusive with `parentDelegation`) | +| `salt` | `Hex` | No | Salt for generating the delegation hash to prevent collisions | + +For more details, see the [delegation reference](https://docs.metamask.io/smart-accounts-kit/reference/delegation.md). + +### Scope types + +Use the `ScopeType` enum for type-safe scope configuration: + +| ScopeType | When to use | +|-----------|-------------| +| `Erc20TransferAmount` | You want to set a fixed cumulative ERC-20 transfer limit | +| `Erc20PeriodTransfer` | You want a per-period ERC-20 limit that resets at the start of each new period | +| `Erc20Streaming` | You want a linear streaming ERC-20 limit that accrues over time | +| `NativeTokenTransferAmount` | You want to set a fixed cumulative native token transfer limit | +| `NativeTokenPeriodTransfer` | You want a per-period native token limit that resets at the start of each new period | +| `NativeTokenStreaming` | You want a linear streaming native token limit that accrues over time | +| `Erc721Transfer` | You want to restrict the delegation to a specific NFT transfer | +| `FunctionCall` | You want to restrict the delegation to specific methods, addresses, or calldata | +| `OwnershipTransfer` | You want to restrict the delegation to ownership transfer calls only | + +For full scope parameters, see the [delegation scopes guide](https://docs.metamask.io/smart-accounts-kit/guides/delegation/use-delegation-scopes.md). + +### Caveat types + +| Type | When to use | +|------|-------------| +| `allowedTargets` | You want to restrict which contract addresses the delegate can call | +| `allowedMethods` | You want to restrict which methods the delegate can call | +| `allowedCalldata` | You want to validate specific calldata at a byte offset | +| `exactCalldata` | You want to require an exact calldata match | +| `exactCalldataBatch` | You want to require an exact calldata match for a batch of executions | +| `exactExecution` | You want to require an exact match on target, value, and calldata | +| `exactExecutionBatch` | You want to require an exact match for a batch of executions | +| `valueLte` | You want to cap the native token value per call | +| `erc20TransferAmount` | You want to set a max cumulative ERC-20 transfer amount | +| `erc20BalanceChange` | You want to validate that an ERC-20 balance changes as expected | +| `erc20PeriodTransfer` | You want a per-period ERC-20 limit that resets each period | +| `erc20Streaming` | You want a linear streaming ERC-20 limit that accrues over time | +| `erc721Transfer` | You want to restrict the delegation to a specific NFT transfer | +| `erc721BalanceChange` | You want to validate that an ERC-721 balance changes as expected | +| `erc1155BalanceChange` | You want to validate that an ERC-1155 balance changes as expected | +| `nativeTokenPeriodTransfer` | You want a per-period native token limit that resets each period | +| `nativeTokenStreaming` | You want a linear streaming native token limit that accrues over time | +| `nativeBalanceChange` | You want to validate that a native token balance changes as expected | +| `nativeTokenPayment` | You want to require the redeemer to pay a fee to redeem | +| `timestamp` | You want to restrict redemption to a specific time window | +| `blockNumber` | You want to restrict redemption to a specific block range | +| `limitedCalls` | You want to limit how many times the delegation can be redeemed | +| `redeemer` | You want to restrict which addresses can redeem the delegation | +| `id` | You want a one-time delegation identified by a unique ID | +| `nonce` | You want to enable bulk revocation of delegations sharing the same nonce | +| `deployed` | You want to auto-deploy a contract before the delegation executes | +| `ownershipTransfer` | You want to restrict the delegation to ownership transfer calls only | +| `multiTokenPeriod` | You want to set per-period limits across multiple tokens at once | +| `specificActionERC20TransferBatch` | You want to combine a specific action with an ERC-20 transfer in a batch | +| `argsEqualityCheck` | You want to validate that function arguments match a specific value | + +For full caveat parameters, see the [caveats reference](https://docs.metamask.io/smart-accounts-kit/reference/delegation/caveats.md). + +## Important rules + +- The delegator must always be a MetaMask smart account created with `toMetaMaskSmartAccount`. +- You must deploy the delegator before redeeming delegations — the DelegationManager reverts with `0xb9f0f171` for counterfactual accounts. +- Always use caveats — without them, delegations have infinite authority. +- Function call scope defaults to no native token — use `valueLte` to allow it. +- Caveats are cumulative in chains — restrictions stack, and a delegate can only redelegate with equal or lesser authority. +- Caveat order matters — place state-changing caveats (payment) before balance checks. +- Use the `ScopeType` enum for type-safe scope configuration. + +## Workflows + +- [Create delegation](../workflows/create-delegation.md) — create, sign, and store a delegation +- [Create redelegation](../workflows/create-redelegation.md) — delegation chains with attenuated authority +- [Redeem delegation — smart account](../workflows/redeem-delegation-smart-account.md) — redeem when the delegate is a smart account +- [Redeem delegation — EOA](../workflows/redeem-delegation-eoa.md) — redeem when the delegate is an EOA + diff --git a/skills/smart-accounts-kit/references/smart-accounts.md b/skills/smart-accounts-kit/references/smart-accounts.md new file mode 100644 index 00000000..9a272f12 --- /dev/null +++ b/skills/smart-accounts-kit/references/smart-accounts.md @@ -0,0 +1,63 @@ +--- +name: Smart accounts +description: API reference for creating ERC-4337 smart accounts — Hybrid, MultiSig, or Stateless7702 implementations +--- + +# Smart accounts + +## When to use + +- You want to create an ERC-4337 smart account. +- You want to configure signers (EOA private key, passkey/WebAuthn, wallet client, multisig). +- You want to deploy a smart account via a bundler. +- You want to choose between Hybrid, MultiSig, or Stateless7702 implementations. + +## API reference + +### `toMetaMaskSmartAccount` parameters + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `client` | `Client` | Yes | Viem client for retrieving smart account data | +| `implementation` | `Implementation` | Yes | `Hybrid`, `MultiSig`, or `Stateless7702` | +| `signer` | `SignerConfigByImplementation` | No | Viem Account, Wallet Client, or WebAuthn Account (Hybrid only). Omitting disables signing operations. | +| `deployParams` | `DeployParams` | If no `address` | Parameters for deployment (not needed for Stateless7702) | +| `deploySalt` | `Hex` | If no `address` | Salt for deployment (not needed for Stateless7702) | +| `address` | `Address` | If no `deployParams` | Existing smart account or 7702 EOA address | +| `environment` | `SmartAccountsEnvironment` | No | Environment for smart contract resolution | +| `nonceKeyManager` | `NonceManager` | No | Custom nonce key manager for parallel user operation execution | + +For more details, see the [smart account reference](https://docs.metamask.io/smart-accounts-kit/reference/smart-account.md). + +### Which implementation to use + +| Name | Enum | Usage | +|------|------|-------| +| Hybrid | `Implementation.Hybrid` | A flexible account with EOA, wallet client, and passkey (WebAuthn) signers. The most flexible option for standard dApp users. | +| MultiSig | `Implementation.MultiSig` | Requires multiple signers to meet a threshold before transactions execute. Best for treasury, DAO, or shared custody use cases. | +| Stateless7702 | `Implementation.Stateless7702` | Upgrades an existing EOA to a smart account using EIP-7702 while keeping the same address. Best for users with existing embedded EOAs. | + +If the user hasn't specified which implementation they need, present the options. + +## Important rules + +- You must deploy the account before creating delegations — use `sendUserOperation` to deploy. +- `signer` is optional — if you omit it, signing operations throw. +- For Hybrid `deployParams`, pass empty arrays `[]` if you don't need passkeys. +- The number of MultiSig signers must be ≥ the threshold. +- Stateless7702 requires an EIP-7702 upgrade first and only works with Viem local accounts. +- The paymaster is optional — without it, the smart account must have funds to pay gas. + +## Workflows + +- [Create hybrid account](../workflows/create-hybrid-account.md) — EOA, wallet client, or passkey signers +- [Create multisig account](../workflows/create-multisig-account.md) — threshold-based signing with multiple signers +- [Create 7702 account](../workflows/create-7702-account.md) — upgrade an existing EOA using EIP-7702 +- [Create delegation](../workflows/create-delegation.md) — uses `toMetaMaskSmartAccount` to set up the delegator +- [Create redelegation](../workflows/create-redelegation.md) — delegation chains across accounts +- [Redeem delegation — smart account](../workflows/redeem-delegation-smart-account.md) — redeem when the delegate is a smart account +- [Redeem delegation — EOA](../workflows/redeem-delegation-eoa.md) — redeem when the delegate is an EOA + +## Docs + +- [Smart account quickstart](https://docs.metamask.io/smart-accounts-kit/development/get-started/smart-account-quickstart.md) diff --git a/skills/smart-accounts-kit/workflows/create-7702-account.md b/skills/smart-accounts-kit/workflows/create-7702-account.md new file mode 100644 index 00000000..961ce915 --- /dev/null +++ b/skills/smart-accounts-kit/workflows/create-7702-account.md @@ -0,0 +1,97 @@ +--- +name: Create a Stateless7702 account +description: Upgrade an existing EOA to a smart account using EIP-7702 +--- + +# Create a Stateless7702 account + +The Stateless7702 implementation upgrades an existing EOA to support smart account functionality using EIP-7702. The EOA keeps its address and gains delegation capabilities. + +## Prerequisites + +- This implementation only works with Viem local accounts, not JSON-RPC accounts like MetaMask. +- The EOA must be upgraded using EIP-7702 before creating the smart account. You can do this yourself using the authorization flow below, or it may already be done if the user upgraded through MetaMask. + +## Upgrade the EOA using EIP-7702 (optional) + +If the EOA hasn't been upgraded yet, you can send an EIP-7702 authorization transaction. This sets the EOA's code to delegate to the `EIP7702StatelessDeleGatorImpl` contract: + +```typescript +import { getSmartAccountsEnvironment } from '@metamask/smart-accounts-kit' +import { createWalletClient, http } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' + +const account = privateKeyToAccount('') +const environment = getSmartAccountsEnvironment(chain.id) + +const walletClient = createWalletClient({ + account, + chain, + transport: http(), +}) + +const authorization = await walletClient.signAuthorization({ + contractAddress: environment.implementations.EIP7702StatelessDeleGatorImpl, +}) + +await walletClient.sendTransaction({ + to: account.address, + authorizationList: [authorization], +}) +``` + +## Verify the EIP-7702 upgrade + +Check whether the EOA has been upgraded before creating the smart account. You can use the `isValid7702Implementation` helper: + +```typescript +import { isValid7702Implementation } from '@metamask/smart-accounts-kit/actions' + +const isUpgraded = await isValid7702Implementation({ + client: publicClient, + accountAddress: account.address, + environment, +}) +``` + +Or inspect the code manually: + +```typescript +const code = await publicClient.getCode({ address: account.address }) + +if (code) { + const delegatorAddress = `0x${code.substring(8)}` + const statelessDelegatorAddress = environment.implementations.EIP7702StatelessDeleGatorImpl + + const isUpgraded = + delegatorAddress.toLowerCase() === statelessDelegatorAddress.toLowerCase() +} +``` + +## Create the account + +Once the EOA is upgraded, pass its address to `toMetaMaskSmartAccount`. No `deployParams` or `deploySalt` are needed: + +```typescript +import { Implementation, toMetaMaskSmartAccount } from '@metamask/smart-accounts-kit' + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Stateless7702, + address: account.address, + signer: { account }, +}) +``` + +You can also use a wallet client signer: + +```typescript +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Stateless7702, + address: walletClient.account.address, + signer: { walletClient }, +}) +``` + +For more details, see the [smart account reference](https://docs.metamask.io/smart-accounts-kit/reference/smart-account.md). diff --git a/skills/smart-accounts-kit/workflows/create-delegation.md b/skills/smart-accounts-kit/workflows/create-delegation.md new file mode 100644 index 00000000..36edb218 --- /dev/null +++ b/skills/smart-accounts-kit/workflows/create-delegation.md @@ -0,0 +1,109 @@ +--- +name: Create a delegation +description: Create and sign a delegation with scopes and caveats +--- + +# Create a delegation + +## Create the smart account (delegator) + +Create a MetaMask smart account that will act as the delegator. This example uses a Hybrid implementation with an EOA signer: + +```typescript +import { Implementation, toMetaMaskSmartAccount } from '@metamask/smart-accounts-kit' +import { privateKeyToAccount } from 'viem/accounts' + +const account = privateKeyToAccount('') + +const delegatorSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: '0x', + signer: { account }, +}) +``` + +## Set up the bundler client + +Configure the bundler client with a paymaster to sponsor gas fees: + +```typescript +import { createBundlerClient, createPaymasterClient } from 'viem/account-abstraction' +import { http } from 'viem' + +const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http('https://api.pimlico.io/v2//rpc?apikey='), + paymaster: createPaymasterClient({ + transport: http('https://api.pimlico.io/v2//rpc?apikey='), + }), + chain, +}) +``` + +## Deploy the smart account + +You must deploy the delegator before creating delegations. Send a no-op user operation to trigger deployment: + +```typescript +const userOpHash = await bundlerClient.sendUserOperation({ + account: delegatorSmartAccount, + calls: [{ to: delegatorSmartAccount.address, value: 0n, data: '0x' }], +}) +``` + +## Create the delegation + +Define the delegation with a scope and caveats. This example grants the delegate permission to transfer up to 10 USDC, limited to 5 calls within a time window. For all available scope types and caveat types, see the [delegations reference](../references/delegations.md). + +```typescript +import { createDelegation, ScopeType } from '@metamask/smart-accounts-kit' +import { parseUnits } from 'viem' + +const delegation = createDelegation({ + to: delegateAddress, + from: delegatorSmartAccount.address, + environment: delegatorSmartAccount.environment, + scope: { + type: ScopeType.Erc20TransferAmount, + tokenAddress: '', + maxAmount: parseUnits('10', 6), + }, + caveats: [ + { type: 'timestamp', afterThreshold: now, beforeThreshold: expiry }, + { type: 'limitedCalls', limit: 5 }, + ], +}) +``` + +## Sign the delegation + +The delegator signs the delegation to authorize it. The signature is then attached to the delegation object: + +```typescript +const signature = await delegatorSmartAccount.signDelegation({ delegation }) +const signedDelegation = { ...delegation, signature } +``` + +You can also sign standalone (without a smart account instance): + +```typescript +import { signDelegation } from '@metamask/smart-accounts-kit' +import { sepolia } from 'viem/chains' + +const signature = await signDelegation({ + privateKey, + delegation, + chainId: sepolia.id, + delegationManager: environment.DelegationManager, +}) +``` + +## Store the signed delegation + +Store your signed delegation for later retrieval and redemption by the delegate. You can store it on IPFS, local storage, a database, or any other storage solution. + +The delegate can be either an EOA or a smart account. The redemption workflow differs depending on the delegate type: +- [Redeem delegation — smart account](./redeem-delegation-smart-account.md) +- [Redeem delegation — EOA](./redeem-delegation-eoa.md) diff --git a/skills/smart-accounts-kit/workflows/create-hybrid-account.md b/skills/smart-accounts-kit/workflows/create-hybrid-account.md new file mode 100644 index 00000000..0cea795d --- /dev/null +++ b/skills/smart-accounts-kit/workflows/create-hybrid-account.md @@ -0,0 +1,92 @@ +--- +name: Create a hybrid account +description: Create a Hybrid smart account with EOA, wallet client, or passkey signers +--- + +# Create a hybrid account + +The Hybrid implementation is the most flexible smart account type. It supports EOA, wallet client, and passkey (WebAuthn) signers. + +## Create with an EOA signer + +Create a Hybrid smart account using a private key. Pass empty arrays for the passkey parameters if you don't need passkey signers: + +```typescript +import { Implementation, toMetaMaskSmartAccount } from '@metamask/smart-accounts-kit' +import { privateKeyToAccount } from 'viem/accounts' + +const account = privateKeyToAccount('') + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: '0x', + signer: { account }, +}) +``` + +## Create with a wallet client signer + +Use a wallet client (for example, from MetaMask or another browser wallet) instead of a raw private key: + +```typescript +const [address] = await walletClient.getAddresses() + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [address, [], [], []], + deploySalt: '0x', + signer: { walletClient }, +}) +``` + +## Create with a passkey (WebAuthn) signer + +Use a passkey for biometric authentication. This requires the `ox` library for public key conversion: + +```typescript +import { toWebAuthnAccount } from 'viem/account-abstraction' +import { createWebAuthnCredential } from 'viem/account-abstraction' +import { Address, PublicKey } from 'ox' +import { toHex } from 'viem' + +const credential = await createWebAuthnCredential({ name: 'MetaMask smart account' }) +const webAuthnAccount = toWebAuthnAccount({ credential }) +const publicKey = PublicKey.fromHex(credential.publicKey) +const owner = Address.fromPublicKey(publicKey) + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [owner, [toHex(credential.id)], [publicKey.x], [publicKey.y]], + deploySalt: '0x', + signer: { webAuthnAccount, keyId: toHex(credential.id) }, +}) +``` + +## Deploy the account + +You must deploy the smart account before creating delegations. Send a no-op user operation to trigger deployment: + +```typescript +import { createBundlerClient, createPaymasterClient } from 'viem/account-abstraction' +import { http } from 'viem' + +const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http('https://api.pimlico.io/v2//rpc?apikey='), + paymaster: createPaymasterClient({ + transport: http('https://api.pimlico.io/v2//rpc?apikey='), + }), + chain, +}) + +const userOpHash = await bundlerClient.sendUserOperation({ + account: smartAccount, + calls: [{ to: smartAccount.address, value: 0n, data: '0x' }], +}) +``` + +For more details, see the [smart account reference](https://docs.metamask.io/smart-accounts-kit/reference/smart-account.md). diff --git a/skills/smart-accounts-kit/workflows/create-multisig-account.md b/skills/smart-accounts-kit/workflows/create-multisig-account.md new file mode 100644 index 00000000..f418c079 --- /dev/null +++ b/skills/smart-accounts-kit/workflows/create-multisig-account.md @@ -0,0 +1,80 @@ +--- +name: Create a multisig account +description: Create a MultiSig smart account with threshold-based signing +--- + +# Create a multisig account + +The MultiSig implementation requires multiple signers to approve transactions. You configure a threshold that determines how many signatures are needed. + +## Create the multisig account + +Provide an array of signer addresses and a threshold. The number of signers must be greater than or equal to the threshold: + +```typescript +import { Implementation, toMetaMaskSmartAccount } from '@metamask/smart-accounts-kit' +import { privateKeyToAccount } from 'viem/accounts' + +const signer1 = privateKeyToAccount('') +const signer2 = privateKeyToAccount('') + +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.MultiSig, + deployParams: [[signer1.address, signer2.address], 2n], + deploySalt: '0x', + signer: [{ account: signer1 }, { account: signer2 }], +}) +``` + +You can also mix signer types (EOA accounts and wallet clients): + +```typescript +const smartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.MultiSig, + deployParams: [[account1.address, account2.address], 2n], + deploySalt: '0x', + signer: [{ account: account1 }, { walletClient: walletClient2 }], +}) +``` + +## Aggregate signatures + +When collecting signatures from multiple signers separately, use `aggregateSignature` to combine them: + +```typescript +import { aggregateSignature } from '@metamask/smart-accounts-kit' + +const aggregatedSignature = aggregateSignature({ + signatures: [ + { signer: signer1.address, signature: signature1, type: 'ECDSA' }, + { signer: signer2.address, signature: signature2, type: 'ECDSA' }, + ], +}) +``` + +## Deploy the account + +You must deploy the smart account before creating delegations: + +```typescript +import { createBundlerClient, createPaymasterClient } from 'viem/account-abstraction' +import { http } from 'viem' + +const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http('https://api.pimlico.io/v2//rpc?apikey='), + paymaster: createPaymasterClient({ + transport: http('https://api.pimlico.io/v2//rpc?apikey='), + }), + chain, +}) + +const userOpHash = await bundlerClient.sendUserOperation({ + account: smartAccount, + calls: [{ to: smartAccount.address, value: 0n, data: '0x' }], +}) +``` + +For more details, see the [smart account reference](https://docs.metamask.io/smart-accounts-kit/reference/smart-account.md). diff --git a/skills/smart-accounts-kit/workflows/create-redelegation-permissions.md b/skills/smart-accounts-kit/workflows/create-redelegation-permissions.md new file mode 100644 index 00000000..d1f61060 --- /dev/null +++ b/skills/smart-accounts-kit/workflows/create-redelegation-permissions.md @@ -0,0 +1,77 @@ +--- +name: Create a redelegation for Advanced Permissions +description: Redelegate ERC-7715 permission contexts to another account using wallet actions +--- + +# Create a redelegation for Advanced Permissions + +You can redelegate an ERC-7715 permission context to another account using the `redelegatePermissionContext` wallet action. This creates a new delegation in the chain and returns an updated `permissionContext` that the new delegate can redeem. + +## Extend the wallet client + +Create a wallet client for the current delegate and extend it with `erc7710WalletActions` to enable redelegation: + +```typescript +import { createWalletClient, http } from 'viem' +import { erc7710WalletActions } from '@metamask/smart-accounts-kit/actions' + +const delegateWalletClient = createWalletClient({ + account: delegateAccount, + chain, + transport: http(), +}).extend(erc7710WalletActions()) +``` + +## Redelegate the permission context to a specific account + +Pass the original permission context along with an optional narrowed scope and additional caveats. This example redelegates 50 USDC (a subset of the original grant) with a time constraint: + +```typescript +const { delegation, permissionContext: newPermissionContext } = + await delegateWalletClient.redelegatePermissionContext({ + environment, + permissionContext: grantedPermissions[0].context, + to: secondDelegateAddress, + scope: { + type: ScopeType.Erc20TransferAmount, + tokenAddress: '', + maxAmount: parseUnits('50', 6), + }, + caveats: [ + { type: 'timestamp', afterThreshold: now, beforeThreshold: expiry }, + ], + }) +``` + +The returned `newPermissionContext` is a hex-encoded delegation chain that the second delegate can use to redeem. + +## Create an open redelegation + +Use `redelegatePermissionContextOpen` to create a redelegation without specifying a delegate. Any account can redeem it. + +```typescript +const { delegation, permissionContext: newPermissionContext } = + await delegateWalletClient.redelegatePermissionContextOpen({ + environment, + permissionContext: grantedPermissions[0].context, + scope: { + type: ScopeType.Erc20TransferAmount, + tokenAddress: '', + maxAmount: parseUnits('50', 6), + }, + }) +``` + +## Redeem the redelegated permission + +The second delegate redeems using the new `permissionContext`: +- [Redeem Advanced Permissions with a smart account session](./redeem-permissions-smart-account.md) +- [Redeem Advanced Permissions with an EOA session](./redeem-permissions-eoa.md) + +For full parameter details, see the [ERC-7710 wallet client reference](https://docs.metamask.io/smart-accounts-kit/reference/erc7710/wallet-client.md). + +## Rules + +- Caveats are cumulative — each redelegation inherits all restrictions from its parents. +- You can only redelegate with equal or lesser authority. +- Use open redelegation carefully — any account can redeem it. Add a `redeemer` caveat to restrict who can redeem. diff --git a/skills/smart-accounts-kit/workflows/create-redelegation.md b/skills/smart-accounts-kit/workflows/create-redelegation.md new file mode 100644 index 00000000..c10cf6f0 --- /dev/null +++ b/skills/smart-accounts-kit/workflows/create-redelegation.md @@ -0,0 +1,88 @@ +--- +name: Create a redelegation +description: Create delegation chains where a delegate re-grants permissions to another account +--- + +# Create a redelegation + +Redelegation lets a delegate re-grant permissions they've received to another account, forming a delegation chain. Caveats are cumulative — each link can only add restrictions, never remove them. + +## Create the root delegation + +Create the first delegation in the chain. This example grants the delegate permission to transfer up to 100 USDC: + +```typescript +import { createDelegation, ScopeType } from '@metamask/smart-accounts-kit' +import { parseUnits } from 'viem' + +const rootDelegation = createDelegation({ + to: delegateAddress, + from: delegatorAddress, + environment, + scope: { + type: ScopeType.Erc20TransferAmount, + tokenAddress: '', + maxAmount: parseUnits('100', 6), + }, +}) + +const signature = await delegatorSmartAccount.signDelegation({ delegation: rootDelegation }) +const signedRootDelegation = { ...rootDelegation, signature } +``` + +## Create the redelegation + +Pass the parent delegation to create the chain. The redelegation must have equal or lesser authority. + +```typescript +const redelegation = createDelegation({ + to: secondDelegateAddress, + from: delegateAddress, + environment, + scope: { + type: ScopeType.Erc20TransferAmount, + tokenAddress: '', + maxAmount: parseUnits('50', 6), + }, + parentDelegation: signedRootDelegation, + caveats: [ + { type: 'timestamp', afterThreshold: now, beforeThreshold: expiry }, + ], +}) + +const redelegationSignature = await delegateSmartAccount.signDelegation({ delegation: redelegation }) +const signedRedelegation = { ...redelegation, signature: redelegationSignature } +``` + +You can also redelegate from an encoded delegation chain: + +```typescript +const redelegation = createDelegation({ + to: secondDelegateAddress, + from: delegateAddress, + environment, + scope: { type: ScopeType.Erc20TransferAmount, tokenAddress: '', maxAmount: parseUnits('50', 6) }, + parentPermissionContext: encodedDelegationChain, +}) +``` + +## Redeem the chain + +When redeeming a delegation chain, pass the full array of delegations ordered from leaf to root. The DelegationManager validates the entire chain before executing: + +```typescript +import { DelegationManager } from '@metamask/smart-accounts-kit/contracts' +import { createExecution, ExecutionMode } from '@metamask/smart-accounts-kit' + +const redeemCalldata = DelegationManager.encode.redeemDelegations({ + delegations: [[signedRedelegation, signedRootDelegation]], + modes: [ExecutionMode.SingleDefault], + executions: [[execution]], +}) +``` + +## Rules + +- Caveats are cumulative — each link in the chain inherits all restrictions from its parents. +- A delegate can only redelegate with equal or lesser authority. +- All delegator accounts in the chain must be deployed. diff --git a/skills/smart-accounts-kit/workflows/redeem-delegation-eoa.md b/skills/smart-accounts-kit/workflows/redeem-delegation-eoa.md new file mode 100644 index 00000000..d7b7adae --- /dev/null +++ b/skills/smart-accounts-kit/workflows/redeem-delegation-eoa.md @@ -0,0 +1,46 @@ +--- +name: Redeem delegation with an EOA delegate +description: Redeem delegations when the delegate is an EOA +--- + +# Redeem delegation with an EOA delegate + +Use this workflow when the delegate is an EOA. If the delegate is a smart account, use [Redeem delegation — smart account](./redeem-delegation-smart-account.md) instead. + +## Prepare and encode the execution + +Encode the function call you want to execute on behalf of the delegator. This example transfers 1 USDC to a recipient: + +```typescript +import { createExecution, ExecutionMode } from '@metamask/smart-accounts-kit' +import { DelegationManager } from '@metamask/smart-accounts-kit/contracts' +import { encodeFunctionData, erc20Abi, parseUnits } from 'viem' + +const callData = encodeFunctionData({ + abi: erc20Abi, + args: [recipient, parseUnits('1', 6)], + functionName: 'transfer', +}) + +const execution = createExecution({ target: tokenAddress, callData }) + +const redeemCalldata = DelegationManager.encode.redeemDelegations({ + delegations: [[signedDelegation]], + modes: [ExecutionMode.SingleDefault], + executions: [[execution]], +}) +``` + +## Send the transaction + +Send the redeem calldata directly to the DelegationManager contract. The EOA delegate doesn't need a bundler: + +```typescript +import { getSmartAccountsEnvironment } from '@metamask/smart-accounts-kit' + +const txHash = await delegateWalletClient.sendTransaction({ + to: getSmartAccountsEnvironment(chain.id).DelegationManager, + data: redeemCalldata, + chain, +}) +``` diff --git a/skills/smart-accounts-kit/workflows/redeem-delegation-smart-account.md b/skills/smart-accounts-kit/workflows/redeem-delegation-smart-account.md new file mode 100644 index 00000000..477e5a7e --- /dev/null +++ b/skills/smart-accounts-kit/workflows/redeem-delegation-smart-account.md @@ -0,0 +1,108 @@ +--- +name: Redeem delegation with a smart account delegate +description: Redeem delegations when the delegate is a smart account +--- + +# Redeem delegation with a smart account delegate + +Use this workflow when the delegate is a smart account. If the delegate is an EOA, use [Redeem delegation — EOA](./redeem-delegation-eoa.md) instead. + +## Install dependencies + +```bash +npm install permissionless +``` + +## Set up clients + +Create a public client for your target network, a bundler client with a paymaster, and a Pimlico client to estimate gas fees: + +```typescript +import { createPublicClient, http } from 'viem' +import { baseSepolia } from 'viem/chains' +import { createBundlerClient, createPaymasterClient } from 'viem/account-abstraction' +import { createPimlicoClient } from 'permissionless/clients/pimlico' + +const chain = baseSepolia +const publicClient = createPublicClient({ chain, transport: http() }) + +const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http('https://api.pimlico.io/v2//rpc?apikey='), + paymaster: createPaymasterClient({ + transport: http('https://api.pimlico.io/v2//rpc?apikey='), + }), + chain, +}) + +const pimlicoClient = createPimlicoClient({ + transport: http('https://api.pimlico.io/v2//rpc?apikey='), +}) +``` + +## Create the delegate smart account + +Create a MetaMask smart account for the delegate that will redeem the delegation: + +```typescript +import { Implementation, toMetaMaskSmartAccount } from '@metamask/smart-accounts-kit' +import { privateKeyToAccount } from 'viem/accounts' + +const delegateOwner = privateKeyToAccount('') + +const delegateSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [delegateOwner.address, [], [], []], + deploySalt: '0x', + signer: { account: delegateOwner }, +}) +``` + +## Estimate gas fees + +Calculate `maxFeePerGas` and `maxPriorityFeePerGas` using the Pimlico client: + +```typescript +const { fast: { maxFeePerGas, maxPriorityFeePerGas } } = await pimlicoClient.getUserOperationGasPrice() +``` + +## Prepare and encode the execution + +Encode the function call you want to execute on behalf of the delegator. Use the `signedDelegation` obtained from the [create delegation](./create-delegation.md) workflow. This example transfers 1 USDC to a recipient: + +```typescript +import { createExecution, ExecutionMode } from '@metamask/smart-accounts-kit' +import { DelegationManager } from '@metamask/smart-accounts-kit/contracts' +import { encodeFunctionData, erc20Abi, parseUnits } from 'viem' + +const tokenAddress = '' +const recipient = '' + +const callData = encodeFunctionData({ + abi: erc20Abi, + args: [recipient, parseUnits('1', 6)], + functionName: 'transfer', +}) + +const execution = createExecution({ target: tokenAddress, callData }) + +const redeemCalldata = DelegationManager.encode.redeemDelegations({ + delegations: [[signedDelegation]], + modes: [ExecutionMode.SingleDefault], + executions: [[execution]], +}) +``` + +## Send the user operation + +Submit the user operation to the bundler. The delegate smart account calls the DelegationManager with the redeem calldata: + +```typescript +const userOpHash = await bundlerClient.sendUserOperation({ + account: delegateSmartAccount, + calls: [{ to: delegateSmartAccount.environment.DelegationManager, data: redeemCalldata }], + maxFeePerGas, + maxPriorityFeePerGas, +}) +``` diff --git a/skills/smart-accounts-kit/workflows/redeem-permissions-eoa.md b/skills/smart-accounts-kit/workflows/redeem-permissions-eoa.md new file mode 100644 index 00000000..6909c050 --- /dev/null +++ b/skills/smart-accounts-kit/workflows/redeem-permissions-eoa.md @@ -0,0 +1,59 @@ +--- +name: Redeem Advanced Permissions with an EOA session +description: Redeem ERC-7715 Advanced Permissions when the session account is an EOA +--- + +# Redeem Advanced Permissions with an EOA session + +Use this workflow when the session account is an EOA. If the session account is a smart account, use [Redeem permissions — smart account](./redeem-permissions-smart-account.md) instead. + +## Extend the wallet client + +Create a wallet client for the session EOA and extend it with `erc7710WalletActions` to enable `sendTransactionWithDelegation`: + +```typescript +import { createWalletClient, http } from 'viem' +import { erc7710WalletActions } from '@metamask/smart-accounts-kit/actions' + +const sessionWalletClient = createWalletClient({ + account: sessionAccount, + chain, + transport: http(), +}).extend(erc7710WalletActions()) +``` + +## Extract the permission context + +Extract the `context` and `delegationManager` from the stored `grantedPermissions` response: + +```typescript +const permissionContext = grantedPermissions[0].context +const delegationManager = grantedPermissions[0].delegationManager +``` + +## Prepare the calldata + +Encode the function call you want to execute on behalf of the user. This example transfers 1 USDC to a recipient: + +```typescript +import { encodeFunctionData, erc20Abi, parseUnits } from 'viem' + +const callData = encodeFunctionData({ + abi: erc20Abi, + args: [recipient, parseUnits('1', 6)], + functionName: 'transfer', +}) +``` + +## Send the transaction with delegation + +For more details, see the [ERC-7710 wallet client reference](https://docs.metamask.io/smart-accounts-kit/reference/erc7710/wallet-client.md). + +```typescript +const txHash = await sessionWalletClient.sendTransactionWithDelegation({ + to: tokenAddress, + data: callData, + permissionContext, + delegationManager, +}) +``` diff --git a/skills/smart-accounts-kit/workflows/redeem-permissions-smart-account.md b/skills/smart-accounts-kit/workflows/redeem-permissions-smart-account.md new file mode 100644 index 00000000..e74252c8 --- /dev/null +++ b/skills/smart-accounts-kit/workflows/redeem-permissions-smart-account.md @@ -0,0 +1,113 @@ +--- +name: Redeem Advanced Permissions with a smart account session +description: Redeem ERC-7715 Advanced Permissions when the session account is a smart account +--- + +# Redeem Advanced Permissions with a smart account session + +Use this workflow when the session account is a smart account. If the session account is an EOA, use [Redeem permissions — EOA](./redeem-permissions-eoa.md) instead. + +## Install dependencies + +```bash +npm install permissionless +``` + +## Set up clients + +Create a public client for your target network, a bundler client extended with `erc7710BundlerActions` to enable `sendUserOperationWithDelegation`, and a Pimlico client to estimate gas fees: + +```typescript +import { createPublicClient, http } from 'viem' +import { baseSepolia } from 'viem/chains' +import { createBundlerClient } from 'viem/account-abstraction' +import { erc7710BundlerActions } from '@metamask/smart-accounts-kit/actions' +import { createPimlicoClient } from 'permissionless/clients/pimlico' + +const chain = baseSepolia +const publicClient = createPublicClient({ chain, transport: http() }) + +const bundlerClient = createBundlerClient({ + client: publicClient, + transport: http('https://api.pimlico.io/v2//rpc?apikey='), + paymaster: true, +}).extend(erc7710BundlerActions()) + +const pimlicoClient = createPimlicoClient({ + transport: http('https://api.pimlico.io/v2//rpc?apikey='), +}) +``` + +## Create the session smart account + +Create a MetaMask smart account for the session key that was granted permissions: + +```typescript +import { Implementation, toMetaMaskSmartAccount } from '@metamask/smart-accounts-kit' +import { privateKeyToAccount } from 'viem/accounts' + +const sessionOwner = privateKeyToAccount('') + +const sessionAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [sessionOwner.address, [], [], []], + deploySalt: '0x', + signer: { account: sessionOwner }, +}) +``` + +## Estimate gas fees + +Calculate `maxFeePerGas` and `maxPriorityFeePerGas` using the Pimlico client: + +```typescript +const { fast: { maxFeePerGas, maxPriorityFeePerGas } } = await pimlicoClient.getUserOperationGasPrice() +``` + +## Extract the permission context + +Extract the `context` and `delegationManager` from the `grantedPermissions` response returned by [`requestExecutionPermissions`](./request-permissions.md): + +```typescript +const permissionContext = grantedPermissions[0].context +const delegationManager = grantedPermissions[0].delegationManager +``` + +## Prepare the calldata + +Encode the function call you want to execute on behalf of the user. This example transfers 1 USDC to a recipient: + +```typescript +import { encodeFunctionData, erc20Abi, parseUnits } from 'viem' + +const tokenAddress = '' +const recipient = '' + +const callData = encodeFunctionData({ + abi: erc20Abi, + args: [recipient, parseUnits('1', 6)], + functionName: 'transfer', +}) +``` + +## Send the user operation with delegation + +For more details, see the [ERC-7710 bundler client reference](https://docs.metamask.io/smart-accounts-kit/reference/erc7710/bundler-client.md). + +```typescript +const userOpHash = await bundlerClient.sendUserOperationWithDelegation({ + publicClient, + account: sessionAccount, + calls: [ + { + to: tokenAddress, + data: callData, + permissionContext, + delegationManager, + }, + ], + maxFeePerGas, + maxPriorityFeePerGas, +}) +``` diff --git a/skills/smart-accounts-kit/workflows/request-permissions.md b/skills/smart-accounts-kit/workflows/request-permissions.md new file mode 100644 index 00000000..c9d3b38e --- /dev/null +++ b/skills/smart-accounts-kit/workflows/request-permissions.md @@ -0,0 +1,96 @@ +--- +name: Request Advanced Permissions +description: Request ERC-7715 Advanced Permissions from MetaMask extension +--- + +# Request Advanced Permissions + +## Set up the wallet client + +Create a wallet client connected to MetaMask and extend it with `erc7715ProviderActions` to enable `requestExecutionPermissions`: + +```typescript +import { createWalletClient, custom } from 'viem' +import { erc7715ProviderActions } from '@metamask/smart-accounts-kit/actions' + +const walletClient = createWalletClient({ + transport: custom(window.ethereum), +}).extend(erc7715ProviderActions()) +``` + +## Set up the public client + +Create a public client to read chain state. This is needed when creating a smart account session: + +```typescript +import { createPublicClient, http } from 'viem' +import { sepolia as chain } from 'viem/chains' + +const publicClient = createPublicClient({ + chain, + transport: http(), +}) +``` + +## Create a session account + +The session account is the entity that redeems the granted permissions. It can be either an EOA or a smart account. The redemption workflow differs depending on the session account type: +- [Redeem permissions — smart account](./redeem-permissions-smart-account.md) +- [Redeem permissions — EOA](./redeem-permissions-eoa.md) + +As a smart account: + +```typescript +import { toMetaMaskSmartAccount, Implementation } from '@metamask/smart-accounts-kit' +import { privateKeyToAccount } from 'viem/accounts' + +const account = privateKeyToAccount(privateKey) + +const sessionAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: '0x', + signer: { account }, +}) +``` + +As an EOA: + +```typescript +import { privateKeyToAccount } from 'viem/accounts' + +const sessionAccount = privateKeyToAccount('') +``` + +## Request permissions + +Call `requestExecutionPermissions` to prompt MetaMask to display a human-readable permission request to the user. This example requests permission to transfer up to 10 USDC per day for one week: + +```typescript +import { parseUnits } from 'viem' + +const currentTime = Math.floor(Date.now() / 1000) + +const grantedPermissions = await walletClient.requestExecutionPermissions([ + { + chainId: chain.id, + expiry: currentTime + 604800, + to: sessionAccount.address, + permission: { + type: 'erc20-token-periodic', + data: { + tokenAddress: '', + periodAmount: parseUnits('10', 6), + periodDuration: 86400, + justification: 'Permission to transfer 10 USDC every day', + }, + isAdjustmentAllowed: true, + }, + }, +]) +``` + +## Store the permission response + +Store the `grantedPermissions` response for later redemption. You can store it on IPFS, local storage, a database, or any other storage solution. diff --git a/skills/x402-payments/SKILL.md b/skills/x402-payments/SKILL.md new file mode 100644 index 00000000..db06e99e --- /dev/null +++ b/skills/x402-payments/SKILL.md @@ -0,0 +1,69 @@ +--- +name: x402-payments +version: 1 +description: Build x402 payment flows using MetaMask Smart Accounts Kit, machine to machine payments over HTTP with ERC-7710 delegations and ERC-7715 Advanced Permissions +--- + +# x402 Payments + +x402 is an open payment protocol that uses the HTTP 402 status code to enable programmatic, machine-to-machine payments over HTTP. It lets servers charge for API access without requiring traditional payment infrastructure, buyer accounts, or API keys. + +## When to use + +- You want to charge for API access using onchain payments. +- You want to build AI agents that pay per request for API resources. +- You want to enable micro payments for premium data or services. +- You want to set up recurring payments with periodic budgets. +- You want to use MetaMask smart account delegations for x402 settlement. + +## Installation + +```bash +npm install @x402/core @x402/fetch @metamask/x402 @metamask/smart-accounts-kit +``` + +For seller endpoints: + +```bash +npm install @x402/core @x402/express @metamask/x402 +``` + +## How x402 works + +1. The buyer sends a request to a protected endpoint. +2. The server responds with HTTP 402 and a `PAYMENT-REQUIRED` header containing payment terms. +3. The buyer creates a payment payload (delegation or permission) matching the terms. +4. The buyer resends the request with the payment in the `PAYMENT-SIGNATURE` header. +5. The server validates the payment through a facilitator and returns the resource. + +## x402 ERC-7710 payments + +The standard x402 protocol supports direct token transfers (using ERC-20 Permit2 or EIP-3009). ERC-7710 extends this by enabling delegation-based payments from MetaMask smart accounts. + +With ERC-7710, a buyer's smart account creates a delegation that authorizes the facilitator to transfer tokens on their behalf. The buyer doesn't sign a direct token approval. Instead, they sign a delegation that the facilitator redeems during settlement. + +This approach enables buyers to pay from MetaMask wallet. Buyers can restrict delegations to specific facilitator addresses, amounts, and time windows using delegation scopes. They can also create long-lived delegations that allow recurring payments without re-signing for each request. + +## Workflows + +| Workflow | Use case | +|----------|----------| +| [Seller endpoint setup](./workflows/seller-endpoint-setup.md) | You want to protect API endpoints with x402 payment requirements. | +| [Delegation payments](./workflows/delegation-payments.md) | You control the buyer smart account and want to automate payments programmatically. | +| [Advanced Permissions (recurring)](./workflows/recurring-payments.md) | You want to request a periodic budget from a MetaMask user for ongoing API access. Best for subscriptions and recurring usage. | + +If the user hasn't specified which payment method they need, present the options. + +## Supported networks + +| Network | Chain ID | Facilitator URL | +|---------|----------|-----------------| +| Base | eip155:8453 | `https://tx-sentinel-base-mainnet.dev-api.cx.metamask.io/platform/v2/x402` | +| Base Sepolia | eip155:84532 | `https://tx-sentinel-base-sepolia.dev-api.cx.metamask.io/platform/v2/x402` | +| Monad | eip155:143 | `https://tx-sentinel-monad-mainnet.dev-api.cx.metamask.io/platform/v2/x402` | + +## Resources + +- x402 protocol: https://www.x402.org/ +- Smart Accounts Kit docs: https://docs.metamask.io/smart-accounts-kit +- [x402 overview](https://docs.metamask.io/smart-accounts-kit/guides/x402/overview.md) diff --git a/skills/x402-payments/workflows/delegation-payments.md b/skills/x402-payments/workflows/delegation-payments.md new file mode 100644 index 00000000..3c33bca2 --- /dev/null +++ b/skills/x402-payments/workflows/delegation-payments.md @@ -0,0 +1,79 @@ +--- +name: Delegation payments +description: Pay for x402-protected APIs using ERC-7710 delegations from a smart account +--- + +# Delegation payments + +Use this workflow when you control the buyer smart account and want to automate payments programmatically. Best for AI agents and backend services. + +## Install dependencies + +```bash +npm install @x402/core @x402/fetch @metamask/x402 @metamask/smart-accounts-kit +``` + +## Create the buyer smart account + +Create a public client for your target network, then create a MetaMask smart account. Ensure the smart account is funded with the required token (typically USDC): + +```typescript +import { createPublicClient, http } from 'viem' +import { baseSepolia } from 'viem/chains' +import { Implementation, toMetaMaskSmartAccount } from '@metamask/smart-accounts-kit' +import { privateKeyToAccount } from 'viem/accounts' + +const chain = baseSepolia +const publicClient = createPublicClient({ chain, transport: http() }) + +const account = privateKeyToAccount('') + +const buyerSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [account.address, [], [], []], + deploySalt: '0x', + signer: { account }, +}) +``` + +## Create the x402 delegation provider + +Use `createx402DelegationProvider` to create an ERC-7710 client that automatically creates, signs, and encodes open root delegations with the required caveats: + +```typescript +import { createx402DelegationProvider } from '@metamask/smart-accounts-kit/experimental' +import { x402Erc7710Client } from '@metamask/x402' + +const erc7710Client = new x402Erc7710Client({ + delegationProvider: createx402DelegationProvider({ + account: buyerSmartAccount, + }), +}) +``` + +For API reference, see [`createx402DelegationProvider`](https://docs.metamask.io/smart-accounts-kit/reference/x402.md). + +## Register the client and wrap fetch + +Register the ERC-7710 client for all EVM networks, then create an HTTP client and wrap `fetch` with automatic payment handling: + +```typescript +import { x402Client, x402HTTPClient } from '@x402/core/client' +import { wrapFetchWithPayment } from '@x402/fetch' + +const coreClient = new x402Client().register('eip155:*', erc7710Client) +const httpClient = new x402HTTPClient(coreClient) +const fetchWithPayment = wrapFetchWithPayment(fetch, httpClient) +``` + +## Make paid requests + +Call protected endpoints using the wrapped fetch. It automatically handles 402 responses, creates the delegation payment, and retries the request: + +```typescript +const response = await fetchWithPayment('https://api.example.com/paid-endpoint') +const data = await response.json() +``` + +For more details, see the [delegation payments guide](https://docs.metamask.io/smart-accounts-kit/development/guides/x402/buyer/delegations.md). diff --git a/skills/x402-payments/workflows/recurring-payments.md b/skills/x402-payments/workflows/recurring-payments.md new file mode 100644 index 00000000..bb53e128 --- /dev/null +++ b/skills/x402-payments/workflows/recurring-payments.md @@ -0,0 +1,116 @@ +--- +name: Recurring payments +description: Set up recurring x402 payments using ERC-7715 Advanced Permissions with periodic budgets +--- + +# Recurring payments + +Use this workflow when you want to request a periodic budget from a MetaMask user for ongoing API access. The permission resets each period, enabling subscription-style payment flows. + +## Install dependencies + +```bash +npm install @x402/core @x402/fetch @metamask/x402 @metamask/smart-accounts-kit +``` + +## Set up the wallet client + +Create a viem wallet client connected to MetaMask and extend it with ERC-7715 actions. This enables the `requestExecutionPermissions` method needed to request periodic spending budgets from the user: + +```typescript +import { createWalletClient, custom } from 'viem' +import { baseSepolia } from 'viem/chains' +import { erc7715ProviderActions } from '@metamask/smart-accounts-kit/actions' + +const chain = baseSepolia + +const walletClient = createWalletClient({ + chain, + transport: custom(window.ethereum), +}).extend(erc7715ProviderActions()) +``` + +## Create a session account + +Generate a session key that will act on behalf of the user within the granted permission. This account signs x402 payment delegations without requiring the user's approval for each individual payment: + +```typescript +import { privateKeyToAccount } from 'viem/accounts' + +const sessionAccount = privateKeyToAccount('') +``` + +## Request periodic permissions + +Request a periodic ERC-20 permission that resets each period. This example requests 10 USDC per week for 30 days: + +```typescript +import { parseUnits } from 'viem' + +const currentTime = Math.floor(Date.now() / 1000) + +const grantedPermissions = await walletClient.requestExecutionPermissions([ + { + chainId: chain.id, + expiry: currentTime + 30 * 86400, + to: sessionAccount.address, + permission: { + type: 'erc20-token-periodic', + data: { + tokenAddress: '', + periodAmount: parseUnits('10', 6), + periodDuration: 604800, + justification: 'Weekly budget for API access', + }, + isAdjustmentAllowed: true, + }, + }, +]) +``` + +## Create the x402 delegation provider + +Use `createx402DelegationProvider` with the granted permission context. This sets up open redelegation so facilitators can redeem payments within the granted budget: + +```typescript +import { createx402DelegationProvider } from '@metamask/smart-accounts-kit/experimental' +import { x402Erc7710Client } from '@metamask/x402' + +const permission = grantedPermissions[0] + +const erc7710Client = new x402Erc7710Client({ + delegationProvider: createx402DelegationProvider({ + account: sessionAccount, + parentPermissionContext: permission.context, + from: permission.from, + }), +}) +``` + +For API reference, see [`createx402DelegationProvider`](https://docs.metamask.io/smart-accounts-kit/reference/x402.md). + +## Register and wrap fetch + +Register the ERC-7710 client for all EVM networks, then create an HTTP client and wrap `fetch` with automatic payment handling. When a server responds with HTTP 402, the wrapped fetch creates a payment within the granted budget and retries the request: + +```typescript +import { x402Client, x402HTTPClient } from '@x402/core/client' +import { wrapFetchWithPayment } from '@x402/fetch' + +const coreClient = new x402Client().register('eip155:*', erc7710Client) +const httpClient = new x402HTTPClient(coreClient) +const fetchWithPayment = wrapFetchWithPayment(fetch, httpClient) +``` + +## Make recurring paid requests + +Each call automatically handles x402 payment within the granted periodic budget: + +```typescript +const response = await fetchWithPayment('https://api.example.com/paid-endpoint') +const data = await response.json() +``` + +The periodic budget resets at the start of each new period. The user doesn't need to approve each individual payment. + +For more details, see the [recurring payments guide](https://docs.metamask.io/smart-accounts-kit/development/guides/x402/buyer/recurring-payments.md). diff --git a/skills/x402-payments/workflows/seller-endpoint-setup.md b/skills/x402-payments/workflows/seller-endpoint-setup.md new file mode 100644 index 00000000..af2d5393 --- /dev/null +++ b/skills/x402-payments/workflows/seller-endpoint-setup.md @@ -0,0 +1,90 @@ +--- +name: Seller endpoint setup +description: Set up an Express server with x402 payment middleware for ERC-7710 payments +--- + +# Seller endpoint setup + +## Install dependencies + +```bash +npm install @metamask/x402 @x402/core @x402/express cors express +``` + +## Create the Express server + +Set up an Express app with CORS configured to expose the x402 payment headers: + +```typescript +import express from 'express' +import cors from 'cors' + +const app = express() + +app.use(cors({ + exposedHeaders: ['PAYMENT-REQUIRED', 'PAYMENT-RESPONSE'], +})) +``` + +## Configure the facilitator client and resource server + +Initialize an `HTTPFacilitatorClient` pointing to the MetaMask facilitator for your network, then create an `x402ResourceServer` and register the ERC-7710 server scheme so the middleware can parse and validate delegation-based payment payloads: + +```typescript +import { HTTPFacilitatorClient } from '@x402/core/server' +import { paymentMiddleware, x402ResourceServer } from '@x402/express' +import { x402ExactEvmErc7710ServerScheme } from '@metamask/x402' + +const facilitatorClient = new HTTPFacilitatorClient({ + url: '', +}) + +const resourceServer = new x402ResourceServer(facilitatorClient).register( + 'eip155:84532', + new x402ExactEvmErc7710ServerScheme(), +) +``` + +MetaMask facilitator URLs by network: +- Base Sepolia: `https://tx-sentinel-base-sepolia.dev-api.cx.metamask.io/platform/v2/x402` +- Base: `https://tx-sentinel-base-mainnet.dev-api.cx.metamask.io/platform/v2/x402` +- Monad: `https://tx-sentinel-monad-mainnet.dev-api.cx.metamask.io/platform/v2/x402` + +## Add payment middleware to protected routes + +Define the payment requirements for each route and apply the middleware with the resource server: + +```typescript +app.use( + paymentMiddleware( + { + 'GET /api/hello': { + accepts: [{ + scheme: 'exact', + price: '$0.01', + network: 'eip155:84532', + payTo: '', + extra: { assetTransferMethod: 'erc7710' }, + }], + description: 'A paid hello endpoint', + mimeType: 'application/json', + }, + }, + resourceServer, + ), +) +``` + +## Add the protected route handler + +Define the route handler as usual. The payment middleware runs before your handler — by the time your handler executes, the payment has already been verified and settled through the facilitator: + +```typescript +app.get('/api/hello', (req, res) => { + res.json({ message: 'Hello, paid user!' }) +}) + +app.listen(3000) +``` + +For more details, see the [seller endpoint guide](https://docs.metamask.io/smart-accounts-kit/development/guides/x402/seller.md).