Requirements
- Target platform
- OpenClaw
- Install method
- Manual import
- Extraction
- Extract archive
- Prerequisites
- OpenClaw
- Primary doc
- SKILL.md
Web3 development using MetaMask Smart Accounts Kit. Use when the user wants to build dApps with ERC-4337 smart accounts, send user operations, batch transactions, configure signers (EOA, passkey, multisig), implement gas abstraction with paymasters, create delegations, or request advanced permissions (ERC-7715). Supports Viem integration, multiple signer types (Dynamic, Web3Auth, Wagmi), gasless transactions, and the Delegation Framework.
Web3 development using MetaMask Smart Accounts Kit. Use when the user wants to build dApps with ERC-4337 smart accounts, send user operations, batch transactions, configure signers (EOA, passkey, multisig), implement gas abstraction with paymasters, create delegations, or request advanced permissions (ERC-7715). Supports Viem integration, multiple signer types (Dynamic, Web3Auth, Wagmi), gasless transactions, and the Delegation Framework.
Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.
I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete.
I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run.
This skill file provides quick access to the MetaMask Smart Accounts Kit v0.3.0. For detailed information, refer to the specific reference files. π Detailed References: Smart Accounts Reference - Account creation, implementations, signers Delegations Reference - Delegation lifecycle, scopes, caveats Advanced Permissions Reference - ERC-7715 permissions via MetaMask
npm install @metamask/smart-accounts-kit@0.3.0 For custom caveat enforcers: forge install metamask/delegation-framework@v1.3.0
Three implementation types: ImplementationBest ForKey FeatureHybrid (Implementation.Hybrid)Standard dApp usersEOA + passkey signers, most flexibleMultiSig (Implementation.MultiSig)Treasury/DAO operationsThreshold-based security, Safe-compatibleStateless7702 (Implementation.Stateless7702)Power users with existing EOAKeep same address, add smart account features via EIP-7702 Decision Guide: Building for general users? β Hybrid Managing treasuries or multi-party control? β MultiSig Upgrading existing EOAs without address change? β Stateless7702
Grant permissions from delegator to delegate: Scopes - Initial authority (spending limits, function calls) Caveats - Restrictions enforced by smart contracts Types - Root, open root, redelegation, open redelegation Lifecycle - Create β Sign β Store β Redeem
Request permissions via MetaMask extension: Human-readable UI confirmations ERC-20 and native token permissions Requires MetaMask Flask 13.5.0+ User must have smart account
import { Implementation, toMetaMaskSmartAccount } from '@metamask/smart-accounts-kit' import { privateKeyToAccount } from 'viem/accounts' const account = privateKeyToAccount('0x...') const smartAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [account.address, [], [], []], deploySalt: '0x', signer: { account }, })
import { createDelegation } from '@metamask/smart-accounts-kit' import { parseUnits } from 'viem' const delegation = createDelegation({ to: delegateAddress, from: delegatorSmartAccount.address, environment: delegatorSmartAccount.environment, scope: { type: 'erc20TransferAmount', tokenAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', maxAmount: parseUnits('10', 6), }, caveats: [ { type: 'timestamp', afterThreshold: now, beforeThreshold: expiry }, { type: 'limitedCalls', limit: 5 }, ], })
const signature = await smartAccount.signDelegation({ delegation }) const signedDelegation = { ...delegation, signature }
import { createExecution, ExecutionMode } from '@metamask/smart-accounts-kit' import { DelegationManager } from '@metamask/smart-accounts-kit/contracts' import { encodeFunctionData, erc20Abi } 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]], }) // Via smart account const userOpHash = await bundlerClient.sendUserOperation({ account: delegateSmartAccount, calls: [{ to: delegateSmartAccount.address, data: redeemCalldata }], }) // Via EOA const txHash = await delegateWalletClient.sendTransaction({ to: environment.DelegationManager, data: redeemCalldata, })
import { erc7715ProviderActions } from '@metamask/smart-accounts-kit/actions' const walletClient = createWalletClient({ transport: custom(window.ethereum), }).extend(erc7715ProviderActions()) const grantedPermissions = await walletClient.requestExecutionPermissions([ { chainId: chain.id, expiry: now + 604800, signer: { type: 'account', data: { address: sessionAccount.address }, }, permission: { type: 'erc20-token-periodic', data: { tokenAddress, periodAmount: parseUnits('10', 6), periodDuration: 86400, justification: 'Transfer 10 USDC daily', }, }, isAdjustmentAllowed: true, }, ])
// Smart account import { erc7710BundlerActions } from '@metamask/smart-accounts-kit/actions' const bundlerClient = createBundlerClient({ client: publicClient, transport: http(bundlerUrl), }).extend(erc7710BundlerActions()) const permissionsContext = grantedPermissions[0].context const delegationManager = grantedPermissions[0].signerMeta.delegationManager const userOpHash = await bundlerClient.sendUserOperationWithDelegation({ publicClient, account: sessionAccount, calls: [ { to: tokenAddress, data: calldata, permissionsContext, delegationManager, }, ], }) // EOA import { erc7710WalletActions } from '@metamask/smart-accounts-kit/actions' const walletClient = createWalletClient({ account: sessionAccount, chain, transport: http(), }).extend(erc7710WalletActions()) const txHash = await walletClient.sendTransactionWithDelegation({ to: tokenAddress, data: calldata, permissionsContext, delegationManager, })
toMetaMaskSmartAccount() - Create smart account aggregateSignature() - Combine multisig signatures signDelegation() - Sign delegation signUserOperation() - Sign user operation signMessage() / signTypedData() - Standard signing
createDelegation() - Create delegation with delegate createOpenDelegation() - Create open delegation createCaveatBuilder() - Build caveats array createExecution() - Create execution struct redeemDelegations() - Encode redemption calldata signDelegation() - Sign with private key getSmartAccountsEnvironment() - Resolve environment deploySmartAccountsEnvironment() - Deploy contracts overrideDeployedEnvironment() - Override environment
erc7715ProviderActions() - Wallet client extension for requesting requestExecutionPermissions() - Request permissions erc7710BundlerActions() - Bundler client extension sendUserOperationWithDelegation() - Redeem with smart account erc7710WalletActions() - Wallet client extension sendTransactionWithDelegation() - Redeem with EOA
Permission TypeDescriptionerc20-token-periodicPer-period limit that resets at each perioderc20-token-streamLinear streaming with amountPerSecond rate
Permission TypeDescriptionnative-token-periodicPer-period ETH limit that resetsnative-token-streamLinear ETH streaming with amountPerSecond rate
ScopeDescriptionerc20TransferAmountFixed ERC-20 limiterc20PeriodTransferPer-period ERC-20 limiterc20StreamingLinear streaming ERC-20nativeTokenTransferAmountFixed native token limitnativeTokenPeriodTransferPer-period native token limitnativeTokenStreamingLinear streaming nativeerc721TransferERC-721 (NFT) transfer
ScopeDescriptionfunctionCallSpecific methods/addresses allowedownershipTransferOwnership transfers only
allowedTargets - Limit callable addresses allowedMethods - Limit callable methods allowedCalldata - Validate specific calldata exactCalldata / exactCalldataBatch - Exact calldata match exactExecution / exactExecutionBatch - Exact execution match
valueLte - Limit native token value erc20TransferAmount - Limit ERC-20 amount erc20BalanceChange - Validate ERC-20 balance change erc721Transfer / erc721BalanceChange - ERC-721 restrictions erc1155BalanceChange - ERC-1155 validation
timestamp - Valid time range (seconds) blockNumber - Valid block range limitedCalls - Limit redemption count erc20PeriodTransfer / erc20Streaming - Time-based ERC-20 nativeTokenPeriodTransfer / nativeTokenStreaming - Time-based native
redeemer - Limit redemption to specific addresses id - One-time delegation with ID nonce - Bulk revocation via nonce deployed - Auto-deploy contract ownershipTransfer - Ownership transfer only nativeTokenPayment - Require payment nativeBalanceChange - Validate native balance multiTokenPeriod - Multi-token period limits
ModeChainsProcessingOn FailureSingleDefaultOneSequentialRevertSingleTryOneSequentialContinueBatchDefaultMultipleInterleavedRevertBatchTryMultipleInterleavedContinue
ContractAddressEntryPoint0x0000000071727De22E5E9d8BAf0edAc6f37da032SimpleFactory0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77cDelegationManager0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3MultiSigDeleGatorImpl0x56a9EdB16a0105eb5a4C54f4C062e2868844f3A7HybridDeleGatorImpl0x48dBe696A4D990079e039489bA2053B36E8FFEC4
Always use caveats - Never create unrestricted delegations Deploy delegator first - Account must be deployed before redeeming Check smart account status - ERC-7715 requires user has smart account
Caveats are cumulative - In delegation chains, restrictions stack Function call default - v0.3.0 defaults to NO native token (use valueLte) Batch mode caveat - No compatible caveat enforcers available
ERC-7715 requirements - MetaMask Flask 13.5.0+, smart account Multisig threshold - Need at least threshold signers 7702 upgrade - Stateless7702 requires EIP-7702 upgrade first
Smart accounts use a 256-bit nonce structure: 192-bit key + 64-bit sequence. Each unique key has its own independent sequence, enabling parallel execution. This is critical for backend services processing multiple delegations concurrently. Installation For proper nonce handling, install the permissionless SDK alongside the Smart Accounts Kit: npm install permissionless How Parallel Nonces Work ERC-4337 uses a single uint256 nonce where: 192 bits = key identifier (allows parallel streams) 64 bits = sequence number (increments per key) Each key has an independent sequence, so UserOps with different keys execute in parallel without ordering constraints. Getting Nonce with Permissionless import { getAccountNonce } from 'permissionless' import { entryPoint07Address } from 'viem/account-abstraction' // Get nonce for a specific key const parallelNonce = await getAccountNonce(publicClient, { address: smartAccount.address, entryPointAddress: entryPoint07Address, key: BigInt(Date.now()), // Unique key for parallel execution }) const userOpHash = await bundlerClient.sendUserOperation({ account: smartAccount, calls: [redeemCalldata], nonce: parallelNonce, // Properly encoded 256-bit nonce }) Parallel Execution Pattern import { getAccountNonce } from 'permissionless' import { entryPoint07Address } from 'viem/account-abstraction' // Execute multiple redemption UserOps in parallel const redeems = await Promise.all( delegations.map(async (delegation, index) => { // Generate unique key for this operation const nonceKey = BigInt(Date.now()) + BigInt(index * 1000) // Get properly encoded nonce for this key const nonce = await getAccountNonce(publicClient, { address: backendSmartAccount.address, entryPointAddress: entryPoint07Address, key: nonceKey, }) const redeemCalldata = DelegationManager.encode.redeemDelegations({ delegations: [[delegation]], modes: [ExecutionMode.SingleDefault], executions: [[execution]], }) return bundlerClient.sendUserOperation({ account: backendSmartAccount, calls: [{ to: backendSmartAccount.address, data: redeemCalldata }], nonce, // Parallel execution enabled via unique key }) }) ) Without Permissionless (Manual Approach) The EntryPoint contract encodes nonce as: sequence | (key << 64) If not using permissionless, encode manually: // EntryPoint: nonceSequenceNumber[sender][key] | (uint256(key) << 64) const key = BigInt(Date.now()) const sequence = 0n // New key starts at sequence 0 const nonce = sequence | (key << 64n) // Or equivalently: (key << 64n) | sequence However, getAccountNonce from permissionless is recommended as it: Fetches the current sequence for the key from the EntryPoint Properly encodes the 256-bit value Handles edge cases and validation Key Points Different keys = parallel execution β no ordering guarantees between different keys Same key = sequential execution β sequence increments monotonically per key Use cases: Backend redemption services, DCA apps, high-frequency trading, batch operations Nonce generation: getAccountNonce returns the full 256-bit nonce properly encoded Common Mistakes MistakeResultReusing same nonce keySequential execution (defeats purpose)Using Date.now() without offsetPotential collision if multiple ops fire simultaneouslyNot using getAccountNonceMay miss current sequence, causing replacement instead of new opAssuming orderingRace conditions in dependent operations Error Handling const results = await Promise.allSettled(redeems) results.forEach((result, index) => { if (result.status === 'rejected') { // Check for specific errors if (result.reason.message?.includes('AA25')) { console.error(`Nonce collision for op ${index}`) } // Handle or retry } })
For server-side automation (DCA bots, keeper services, automated trading): // 1. Backend creates its own smart account as delegate const backendAccount = await toMetaMaskSmartAccount({ client: publicClient, implementation: Implementation.Hybrid, deployParams: [backendOwner.address, [], [], []], deploySalt: '0x', signer: { account: backendOwner }, }) // 2. Backend redeems by sending UserOp FROM its account const userOpHash = await bundlerClient.sendUserOperation({ account: backendAccount, calls: [{ to: backendAccount.address, data: DelegationManager.encode.redeemDelegations({ delegations: [[userDelegation]], modes: [ExecutionMode.SingleDefault], executions: [[swapExecution]], }) }], }) Use case: Automated dollar-cost averaging (DCA) bots that redeem swap delegations based on market signals or scheduled intervals.
Delegator accounts must be deployed before delegations can be redeemed. The DelegationManager reverts with 0xb9f0f171 for counterfactual accounts. Solution: Deploy automatically via first UserOp: // Build redemption calldata const redeemCalldata = DelegationManager.encode.redeemDelegations({ delegations: [[signedDelegation]], modes: [ExecutionMode.SingleDefault], executions: [[execution]], }) // First redemption deploys the account automatically via initCode const userOpHash = await bundlerClient.sendUserOperation({ account: smartAccount, // Will deploy if counterfactual calls: [{ to: smartAccount.address, data: redeemCalldata, value: 0n, }], })
For automated services, session accounts act as isolated signers that can only operate within granted delegations. The private key can be generated ephemerally, stored in environment variables, or managed via HSM/server wallets: // Session account created from various sources const sessionAccount = privateKeyToAccount( process.env.SESSION_KEY || generatePrivateKey() || hsmWallet.key ) // Request delegation from user to session account const delegation = createDelegation({ to: sessionAccount.address, from: userSmartAccount.address, environment, scope: { type: 'erc20TransferAmount', tokenAddress, maxAmount: parseUnits('100', 6) }, caveats: [ { type: 'timestamp', afterThreshold: now, beforeThreshold: expiry }, { type: 'limitedCalls', limit: 10 }, ], }) // Session account can only act within delegation constraints
const delegation = createDelegation({ to: delegate, from: delegator, environment, scope: { type: 'erc20TransferAmount', tokenAddress, maxAmount: parseUnits('100', 6), }, caveats: [ { type: 'timestamp', afterThreshold: now, beforeThreshold: expiry }, { type: 'limitedCalls', limit: 10 }, { type: 'redeemer', redeemers: [delegate] }, ], })
const delegation = createDelegation({ to: delegate, from: delegator, environment, scope: { type: 'functionCall', targets: [contractAddress], selectors: ['transfer(address,uint256)'], valueLte: { maxValue: parseEther('0.1') }, }, caveats: [{ type: 'allowedMethods', selectors: ['transfer(address,uint256)'] }], })
const delegation = createDelegation({ to: delegate, from: delegator, environment, scope: { type: 'nativeTokenPeriodTransfer', periodAmount: parseEther('0.01'), periodDuration: 86400, startDate: now, }, })
// Alice β Bob (100 USDC) const aliceToBob = createDelegation({ to: bob, from: alice, environment, scope: { type: 'erc20TransferAmount', tokenAddress, maxAmount: parseUnits('100', 6) }, }) // Bob β Carol (50 USDC, subset of authority) const bobToCarol = createDelegation({ to: carol, from: bob, environment, scope: { type: 'erc20TransferAmount', tokenAddress, maxAmount: parseUnits('50', 6) }, parentDelegation: aliceToBob, caveats: [{ type: 'timestamp', afterThreshold: now, beforeThreshold: expiry }], })
IssueSolutionAccount not deployedUse bundlerClient.sendUserOperation() to deployInvalid signatureVerify chain ID, delegation manager, signer permissionsCaveat enforcer revertedCheck caveat parameters match execution, verify orderRedemption failedCheck delegator balance, calldata validity, target contractsERC-7715 not workingUpgrade to Flask 13.5.0+, ensure user has smart accountPermission deniedHandle gracefully, provide manual fallbackThreshold not metAdd more signers for multisig7702 not workingConfirm EOA upgraded via EIP-7702 first
Error codes from the MetaMask Delegation Framework contracts (v1.3.0). Use a decoder like calldata.swiss-knife.xyz to identify error signatures.
Error CodeError NameMeaning0xb5863604InvalidDelegate()Caller is not the delegate β Most common error0xb9f0f171InvalidDelegator()Caller is not the delegator0x05baa052CannotUseADisabledDelegation()Attempting to redeem a disabled delegation0xded4370eInvalidAuthority()Delegation chain authority validation failed0x1bcaf69fBatchDataLengthMismatch()Array lengths don't match in batch0x005ecddbAlreadyDisabled()Delegation has already been disabled0xf2a5f75aAlreadyEnabled()Delegation is already enabled0xf645eedfECDSAInvalidSignature()Invalid ECDSA signature format0xfce698f7ECDSAInvalidSignatureLength(uint256)Signature length is incorrect0xd78bce0cECDSAInvalidSignatureS(bytes32)Signature S value is invalid0xac241e11EmptySignature()Signature is empty0xd93c0665EnforcedPause()Contract is paused0x3db6791cInvalidEOASignature()EOA signature verification failed0x155ff427InvalidERC1271Signature()Smart contract signature (ERC1271) failed0x118cdaa7OwnableUnauthorizedAccount(address)Unauthorized account attempted owner-only action0x1e4fbdf7OwnableInvalidOwner(address)Invalid owner address in ownership transfer0xf6b6ef5bInvalidShortString()String parameter too short0xaa0ea2d8StringTooLong(string)String parameter exceeds maximum length
Error CodeError NameMeaning0xd663742aNotEntryPoint()Caller is not the EntryPoint contract0x0796d945NotEntryPointOrSelf()Caller is neither EntryPoint nor this contract0x1a4b3a04NotDelegationManager()Caller is not the DelegationManager0xb96fcfe4UnsupportedCallType(bytes1)Execution call type not supported0x1187dc06UnsupportedExecType(bytes1)Execution type not supported0x29c3b7eeNotSelf()Caller is not this contract itself
Error StringMeaningAllowedTargetsEnforcer:target-address-not-allowedTarget contract not in allowed listAllowedTargetsEnforcer:invalid-terms-lengthTerms length not multiple of 20 bytesERC20TransferAmountEnforcer:invalid-terms-lengthTerms must be 52 bytesERC20TransferAmountEnforcer:invalid-contractTarget doesn't match allowed tokenERC20TransferAmountEnforcer:invalid-methodMethod is not transferERC20TransferAmountEnforcer:allowance-exceededTransfer exceeds delegated limitCaveatEnforcer:invalid-call-typeMust use single call typeCaveatEnforcer:invalid-execution-typeMust use default execution type
0xb5863604 β InvalidDelegate() Cause: Caller doesn't match the delegate address in delegation Fix: Verify msg.sender equals the to address in the delegation 0xb9f0f171 β InvalidDelegator() Cause: Attempting to enable/disable from wrong address, or counterfactual account Fix: Only delegator can enable/disable; for counterfactual, first UserOp auto-deploys 0x05baa052 β CannotUseADisabledDelegation() Cause: Delegation was disabled by delegator Fix: Ask delegator to re-enable, or use different delegation 0xded4370e β InvalidAuthority() Cause: Broken delegation chain (redelegation parent mismatch) Fix: Ensure redelegation chains are properly ordered (leaf β root) 0x1bcaf69f β BatchDataLengthMismatch() Cause: Array lengths don't match in redeemDelegations call Fix: Ensure permissionContexts, modes, executionCallDatas have equal length 0x3db6791c β InvalidEOASignature() Cause: EOA signature invalid, wrong chain, or wrong delegation manager Fix: Verify signature was created with correct chain ID and delegation manager
NPM: @metamask/smart-accounts-kit Contracts: metamask/delegation-framework@v1.3.0 ERC Standards: ERC-4337, ERC-7710, ERC-7715, ERC-7579 MetaMask Flask: https://metamask.io/flask
Toolkit: 0.3.0 Delegation Framework: 1.3.0 Breaking Change: Function call scope defaults to no native token transfer For detailed documentation, see the reference files in the /references directory.
Agent frameworks, memory systems, reasoning layers, and model-native orchestration.
Largest current source with strong distribution and engagement signals.