Perform executions on a MetaMask user's behalf
Advanced Permissions (ERC-7715) are fine-grained permissions that your dapp can request from a MetaMask user to execute transactions on their behalf. For example, a user can grant your dapp permission to spend 10 USDC per day to buy ETH over the course of a month. Once the permission is granted, your dapp can use the allocated 10 USDC each day to purchase ETH directly from the MetaMask user's account.
In this guide, you'll request an ERC-20 periodic transfer permission from a MetaMask user to transfer 1 USDC every day on their behalf.
Prerequisites
Steps
1. Set up a Wallet Client
Set up a Wallet Client using Viem's createWalletClient function. This client will
help you interact with MetaMask.
Then, extend the Wallet Client functionality using erc7715ProviderActions.
These actions enable you to request Advanced PermissionsAdvanced Permissions Fine-grained, wallet execution permissions that dapps can request from MetaMask extension users. Based on ERC-7715. from the user.
import { createWalletClient, custom } from 'viem'
import { erc7715ProviderActions } from '@metamask/smart-accounts-kit/actions'
const walletClient = createWalletClient({
transport: custom(window.ethereum),
}).extend(erc7715ProviderActions())
2. Set up a Public Client
Set up a Public Client using Viem's createPublicClient function.
This client will help you query the account state and interact with the blockchain network.
import { createPublicClient, http } from 'viem'
import { sepolia as chain } from 'viem/chains'
const publicClient = createPublicClient({
chain,
transport: http(),
})
3. Set up a session account
Set up a session account, which can be either a smart account or an EOAExternally owned account (EOA) A private-key-controlled account with no built-in programmable execution logic., to request Advanced PermissionsAdvanced Permissions Fine-grained, wallet execution permissions that dapps can request from MetaMask extension users. Based on ERC-7715.. The requested permissions are granted to the session account, which is responsible for executing transactions on behalf of the user.
- Smart account
- EOA
import { privateKeyToAccount } from 'viem/accounts'
import { toMetaMaskSmartAccount, Implementation } from '@metamask/smart-accounts-kit'
const privateKey = '0x...'
const account = privateKeyToAccount(privateKey)
const sessionAccount = await toMetaMaskSmartAccount({
client: publicClient,
implementation: Implementation.Hybrid,
deployParams: [account.address, [], [], []],
deploySalt: '0x',
signer: { account },
})
import { privateKeyToAccount } from 'viem/accounts'
import { sepolia as chain } from 'viem/chains'
import { createWalletClient, http } from 'viem'
const sessionAccount = privateKeyToAccount('0x...')
4. Request Advanced Permissions
Request Advanced Permissions from the user with the Wallet Client's requestExecutionPermissions action.
In this example, you'll request an
ERC-20 periodic permission.
See the requestExecutionPermissions API reference for more information.
import { sepolia as chain } from 'viem/chains'
import { parseUnits } from 'viem'
// Since current time is in seconds, we need to convert milliseconds to seconds.
const currentTime = Math.floor(Date.now() / 1000)
// 1 week from now.
const expiry = currentTime + 604800
// USDC address on Ethereum Sepolia.
const tokenAddress = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238'
const grantedPermissions = await walletClient.requestExecutionPermissions([
{
chainId: chain.id,
expiry,
// The requested permissions will granted to the
// session account.
to: sessionAccount.address,
permission: {
type: 'erc20-token-periodic',
data: {
tokenAddress,
// 10 USDC in WEI format. Since USDC has 6 decimals, 10 * 10^6
periodAmount: parseUnits('10', 6),
// 1 day in seconds
periodDuration: 86400,
justification: 'Permission to transfer 10 USDC every day',
},
isAdjustmentAllowed: true,
},
},
])
5. Set up a Viem client
Set up a Viem client depending on your session account type.
For a smart account, set up a Bundler Client using Viem's createBundlerClient function.
This lets you use the bundlerBundler An ERC-4337 component that manages the alternate mempool: it collects user operations from smart accounts, packages them, and submits them to the network. service
to estimate gas for user operations and submit transactions to the network.
For an EOA, set up a Wallet Client using Viem's createWalletClient function.
This lets you send transactions directly to the network.
The toolkit provides public actions for both of the clients which can be used to redeem Advanced Permissions, and execute transactions on a user's behalf.
- Smart account
- EOA
import { createBundlerClient } from 'viem/account-abstraction'
import { erc7710BundlerActions } from '@metamask/smart-accounts-kit/actions'
const bundlerClient = createBundlerClient({
client: publicClient,
transport: http('https://your-bundler-rpc.com'),
// Allows you to use the same Bundler Client as paymaster.
paymaster: true,
}).extend(erc7710BundlerActions())
import { createWalletClient, http } from 'viem'
import { erc7710WalletActions } from '@metamask/smart-accounts-kit/actions'
import { sepolia as chain } from 'viem/chains'
const sessionAccountWalletClient = createWalletClient({
account: sessionAccount,
chain,
transport: http(),
}).extend(erc7710WalletActions())
6. Redeem Advanced Permissions
The session account can now redeem the permissions. The redeem transaction is sent to the DelegationManager contract, which validates the delegation and executes actions on the user's behalf.
To redeem the permissions, use the client action based on your session account type.
A smart account uses the Bundler Client's sendUserOperationWithDelegation action,
and an EOA uses the Wallet Client's sendTransactionWithDelegation action.
See the sendUserOperationWithDelegation and sendTransactionWithDelegation API reference for more information.
- Smart account
- EOA
- config.ts
import { calldata } from './config.ts'
// These properties must be extracted from the permission response.
const permissionContext = grantedPermissions[0].context
const delegationManager = grantedPermissions[0].delegationManager
// USDC address on Ethereum Sepolia.
const tokenAddress = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238'
// Calls without permissionContext and delegationManager will be executed
// as a normal user operation.
const userOperationHash = await bundlerClient.sendUserOperationWithDelegation({
publicClient,
account: sessionAccount,
calls: [
{
to: tokenAddress,
data: calldata,
permissionContext,
delegationManager,
},
],
// Appropriate values must be used for fee-per-gas.
maxFeePerGas: 1n,
maxPriorityFeePerGas: 1n,
})
import { calldata } from './config.ts'
// These properties must be extracted from the permission response.
const permissionContext = grantedPermissions[0].context
const delegationManager = grantedPermissions[0].delegationManager
// USDC address on Ethereum Sepolia.
const tokenAddress = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238'
const transactionHash = await sessionAccountWalletClient.sendTransactionWithDelegation({
to: tokenAddress,
data: calldata,
permissionContext,
delegationManager,
})
import { encodeFunctionData, erc20Abi, parseUnits } from 'viem'
export const calldata = encodeFunctionData({
abi: erc20Abi,
args: [sessionAccount.address, parseUnits('1', 6)],
functionName: 'transfer',
})
Next steps
- See how to get the supported execution permissions.
- See how to configure different ERC-20 token permissions and native token permissions.