文件内容
references/advanced-permissions.md
# Advanced Permissions (ERC-7715) Reference
## Overview
Advanced Permissions (ERC-7715) enable dapps to request fine-grained permissions from MetaMask users to execute transactions on their behalf. Permissions are requested directly via the MetaMask browser extension with human-readable confirmations.
**Key Benefits:**
- Eliminates need for users to approve every transaction
- Enables transaction execution without active wallet connection
- Human-readable permission UI in MetaMask
- Users can modify permission parameters (if allowed)
**⚠️ Requirements:**
- MetaMask Flask 13.5.0+ (or later stable versions with ERC-7715 support)
- User must be upgraded to MetaMask Smart Account
## ERC-7715 Technical Overview
### Core Method: `wallet_grantPermissions`
ERC-7715 defines this JSON-RPC method for requesting wallet permissions.
**Required Parameters:**
| Parameter | Description |
|-----------|-------------|
| `signer` | Entity requesting/managing permission (wallet signer, account signer, etc.) |
| `chainId` | Chain where permission is requested |
| `expiry` | Timestamp when permission expires (seconds) |
| `permission` | Permission configuration (type-specific data) |
| `isAdjustmentAllowed` | Whether user can modify requested permission |
### Signer Types
**Account Signer** (most common example):
- Session account created solely to request/redeem permissions
- Can be smart account or EOA
- Contains no tokens (only for signing)
- Granted permissions via ERC-7710 delegation
```typescript
signer: {
type: "account",
data: {
address: sessionAccount.address,
},
}
```
### How It Works
1. Dapp requests permission via `wallet_grantPermissions`
2. MetaMask displays human-readable confirmation UI
3. User approves (optionally modifying parameters)
4. MetaMask creates ERC-7710 delegation internally
5. Session account receives permission to execute on user's behalf
6. Session account redeems permission to execute transactions
## Advanced Permissions vs Regular Delegations
| Feature | Regular Delegations | Advanced Permissions |
| ---------------------- | -------------------------------------- | ------------------------------------ |
| Signing | Dapp constructs and requests signature | Via MetaMask extension |
| Human Readable | No (dapp provides context) | Yes (rich UI in MetaMask) |
| Constraints | Dapp responsibility | Enforced by MetaMask |
| User Modification | No | Yes (if `isAdjustmentAllowed: true`) |
| Smart Account Required | For delegator | For user (permission target) |
**Example UI:**
ERC-20 periodic permission displays:
- Start time
- Amount per period
- Period duration
- Token information
## Supported Permission Types
### ERC-20 Token Permissions
#### ERC-20 Periodic Permission
Allows periodic transfers of ERC-20 tokens up to specified amount per period.
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `type` | `"erc20-token-periodic"` | Permission type |
| `tokenAddress` | `Address` | ERC-20 token contract |
| `periodAmount` | `bigint` | Max amount per period (wei format) |
| `periodDuration` | `number` | Period duration in seconds |
| `justification` | `string` | Human-readable description |
**Example:**
```typescript
const grantedPermissions = await walletClient.requestExecutionPermissions([
{
chainId: sepolia.id,
expiry: Math.floor(Date.now() / 1000) + 604800, // 1 week
signer: {
type: 'account',
data: { address: sessionAccount.address },
},
permission: {
type: 'erc20-token-periodic',
data: {
tokenAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
periodAmount: parseUnits('10', 6), // 10 USDC
periodDuration: 86400, // 1 day
justification: 'Permission to transfer 10 USDC every day',
},
},
isAdjustmentAllowed: true,
},
])
```
#### ERC-20 Streaming Permission
Ensures a linear streaming transfer limit for ERC-20 tokens. Tokens accrue linearly at the configured rate, up to the maximum allowed amount.
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `type` | `"erc20-token-streaming"` | Permission type |
| `tokenAddress` | `Address` | ERC-20 token contract |
| `amountPerSecond` | `bigint` | The rate at which tokens accrue per second |
| `initialAmount` | `bigint` | The initial amount that can be transferred at start time (default: 0) |
| `maxAmount` | `bigint` | The maximum total amount that can be unlocked (default: no limit) |
| `startTime` | `number` | The start timestamp in seconds (default: current time) |
| `justification` | `string` | Human-readable description |
**Example:**
```typescript
permission: {
type: "erc20-token-streaming",
data: {
tokenAddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
amountPerSecond: parseUnits("0.0001", 6), // 0.0001 USDC per second
justification: "Permission to stream USDC continuously",
},
}
```
### Native Token Permissions
#### Native Token Periodic Permission
Allows periodic transfers of native token (ETH) up to specified amount per period.
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `type` | `"native-token-periodic"` | Permission type |
| `periodAmount` | `bigint` | Max amount per period (wei) |
| `periodDuration` | `number` | Period duration in seconds |
| `justification` | `string` | Human-readable description |
**Example:**
```typescript
permission: {
type: "native-token-periodic",
data: {
periodAmount: parseEther("0.01"), // 0.01 ETH per period
periodDuration: 86400, // 1 day
justification: "Permission to transfer 0.01 ETH daily",
},
}
```
#### Native Token Streaming Permission
Ensures a linear streaming transfer limit for native tokens (ETH). ETH accrues linearly at the configured rate, up to the maximum allowed amount.
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `type` | `"native-token-streaming"` | Permission type |
| `amountPerSecond` | `bigint` | The rate at which ETH accrues per second |
| `initialAmount` | `bigint` | The initial amount that can be transferred at start time (default: 0) |
| `maxAmount` | `bigint` | The maximum total amount that can be unlocked (default: no limit) |
| `startTime` | `number` | The start timestamp in seconds (default: current time) |
| `justification` | `string` | Human-readable description |
**Example:**
```typescript
permission: {
type: "native-token-streaming",
data: {
amountPerSecond: parseEther("0.00001"), // 0.00001 ETH per second
justification: "Permission to stream ETH continuously",
},
}
```
## Advanced Permissions Lifecycle
### Step 1: Setup Wallet Client
```typescript
import { createWalletClient, custom } from 'viem'
import { erc7715ProviderActions } from '@metamask/smart-accounts-kit/actions'
const walletClient = createWalletClient({
transport: custom(window.ethereum),
}).extend(erc7715ProviderActions())
```
### Step 2: Setup Public Client
```typescript
import { createPublicClient, http } from 'viem'
import { sepolia as chain } from 'viem/chains'
const publicClient = createPublicClient({
chain,
transport: http(),
})
```
### Step 3: Setup Session Account
**Smart Account:**
```typescript
import { toMetaMaskSmartAccount, Implementation } from '@metamask/smart-accounts-kit'
const sessionAccount = await toMetaMaskSmartAccount({
client: publicClient,
implementation: Implementation.Hybrid,
deployParams: [account.address, [], [], []],
deploySalt: '0x',
signer: { account },
})
```
**EOA:**
```typescript
import { privateKeyToAccount } from 'viem/accounts'
const sessionAccount = privateKeyToAccount('0x...')
```
### Step 4: Check User Smart Account Status
**For MetaMask Flask 13.9.0+:**
- Advanced Permissions support automatic upgrade
- No manual upgrade needed
**For earlier versions:**
```typescript
const addresses = await walletClient.requestAddresses()
const address = addresses[0]
const code = await publicClient.getCode({ address })
if (code) {
const delegatorAddress = `0x${code.substring(8)}` // Remove 0xef0100 prefix
const statelessDelegatorAddress = getSmartAccountsEnvironment(chain.id).implementations
.EIP7702StatelessDeleGatorImpl
const isAccountUpgraded =
delegatorAddress.toLowerCase() === statelessDelegatorAddress.toLowerCase()
if (!isAccountUpgraded) {
// Prompt user to upgrade or upgrade programmatically
}
}
```
**Why upgrade required?**
- Under the hood, ERC-7715 creates an ERC-7710 delegation
- ERC-7710 delegation requires MetaMask Smart Account
### Step 5: Request Permissions
```typescript
const currentTime = Math.floor(Date.now() / 1000)
const expiry = currentTime + 604800 // 1 week
const grantedPermissions = await walletClient.requestExecutionPermissions([
{
chainId: chain.id,
expiry,
signer: {
type: 'account',
data: { address: sessionAccount.address },
},
permission: {
type: 'erc20-token-periodic',
data: {
tokenAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
periodAmount: parseUnits('10', 6),
periodDuration: 86400,
justification: 'Permission to transfer 10 USDC every day',
},
},
isAdjustmentAllowed: true,
},
])
```
### Step 6: Setup Client for Redemption
**Smart Account (Bundler Client):**
```typescript
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'),
paymaster: true,
}).extend(erc7710BundlerActions())
```
**EOA (Wallet Client):**
```typescript
import { createWalletClient, http } from 'viem'
import { erc7710WalletActions } from '@metamask/smart-accounts-kit/actions'
const sessionAccountWalletClient = createWalletClient({
account: sessionAccount,
chain,
transport: http(),
}).extend(erc7710WalletActions())
```
### Step 7: Redeem Permissions
**Extract from permission response:**
```typescript
const permissionsContext = grantedPermissions[0].context
const delegationManager = grantedPermissions[0].signerMeta.delegationManager
```
**Smart Account Redemption:**
```typescript
const calldata = encodeFunctionData({
abi: erc20Abi,
args: [recipient, parseUnits('1', 6)],
functionName: 'transfer',
})
const userOpHash = await bundlerClient.sendUserOperationWithDelegation({
publicClient,
account: sessionAccount,
calls: [
{
to: tokenAddress,
data: calldata,
permissionsContext,
delegationManager,
},
],
maxFeePerGas: 1n,
maxPriorityFeePerGas: 1n,
})
```
**EOA Redemption:**
```typescript
const txHash = await sessionAccountWalletClient.sendTransactionWithDelegation({
to: tokenAddress,
data: calldata,
permissionsContext,
delegationManager,
})
```
## API Methods
### Wallet Client Actions
#### requestExecutionPermissions()
Requests Advanced Permissions from MetaMask extension.
**Requirements:**
- Wallet Client must be extended with `erc7715ProviderActions()`
- User must have MetaMask Flask 13.5.0+
**Parameters:**
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `chainId` | `number` | Yes | Chain ID for permission |
| `expiry` | `number` | Yes | Expiration timestamp (seconds) |
| `permission` | `SupportedPermissionParams` | Yes | Permission configuration |
| `signer` | `SignerParam` | Yes | Account receiving permission |
| `isAdjustmentAllowed` | `boolean` | Yes | Allow user modification |
| `address` | `Address` | No | Wallet address to request from |
**Returns:**
```typescript
{
context: Hex, // Encoded permissions context
signerMeta: {
delegationManager: Address, // Delegation Manager address
// ... other metadata
},
// ... other permission details
}
```
#### sendTransactionWithDelegation()
Sends transaction to redeem delegated permissions (EOA only).
**Requirements:**
- Wallet Client must be extended with `erc7710WalletActions()`
**Additional Parameters (beyond standard sendTransaction):**
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `delegationManager` | `Address` | Yes | Delegation Manager address |
| `permissionsContext` | `Hex` | Yes | Encoded calldata from permission response |
**Example:**
```typescript
const hash = await walletClient.sendTransactionWithDelegation({
to: tokenAddress,
value: 0n,
data: calldata,
permissionsContext,
delegationManager,
})
```
### Bundler Client Actions
#### sendUserOperationWithDelegation()
Sends user operation to redeem delegated permissions (Smart Account only).
**Requirements:**
- Bundler Client must be extended with `erc7710BundlerActions()`
**Parameters:**
| Name | Type | Required | Description |
|------|------|----------|-------------|
| `publicClient` | `PublicClient` | Yes | For reading chain state |
| `account` | `SmartAccount` | Yes | Session account |
| `calls` | `Call[]` | Yes | Calls to execute |
| `calls[].permissionsContext` | `Hex` | Yes | From permission response |
| `calls[].delegationManager` | `Address` | Yes | From permission response |
**Example:**
```typescript
const userOpHash = await bundlerClient.sendUserOperationWithDelegation({
publicClient,
account: sessionAccount,
calls: [
{
to: tokenAddress,
data: calldata,
permissionsContext,
delegationManager,
},
],
maxFeePerGas: 1n,
maxPriorityFeePerGas: 1n,
})
```
## Configuration
### Extending Clients
**Wallet Client for Requesting Permissions:**
```typescript
import { erc7715ProviderActions } from '@metamask/smart-accounts-kit/actions'
const walletClient = createWalletClient({
transport: custom(window.ethereum),
}).extend(erc7715ProviderActions())
```
**Wallet Client for EOA Redemption:**
```typescript
import { erc7710WalletActions } from '@metamask/smart-accounts-kit/actions'
const walletClient = createWalletClient({
account: sessionAccount,
transport: http(),
chain,
}).extend(erc7710WalletActions())
```
**Bundler Client for Smart Account Redemption:**
```typescript
import { erc7710BundlerActions } from '@metamask/smart-accounts-kit/actions'
const bundlerClient = createBundlerClient({
client: publicClient,
transport: http(bundlerUrl),
paymaster: true,
}).extend(erc7710BundlerActions())
```
## Best Practices
### Permission Design
1. **Appropriate Time Limits** - Set reasonable expiry times
2. **Justification Messages** - Clear descriptions help users understand
3. **Allow Adjustment** - Set `isAdjustmentAllowed: true` for better UX
4. **Periodic over Allowance** - Use periodic for recurring operations
5. **Test Amounts** - Use small amounts for testing
### Security
1. **Session Account Isolation** - Keep session accounts separate from user accounts
2. **No Token Storage** - Session accounts should not hold tokens
3. **Expiry Management** - Monitor and refresh expiring permissions
4. **Permission Validation** - Verify granted permissions match request
5. **Error Handling** - Handle permission denial gracefully
### Implementation
1. **Check Smart Account Status** - Verify or upgrade user before requesting
2. **Permission Caching** - Store granted permissions for reuse
3. **Batch Operations** - Group multiple calls when possible
4. **Gas Management** - Use paymasters for better UX
5. **Fallback Handling** - Provide manual transaction fallback
## Troubleshooting
| Issue | Solution |
| ------------------------ | ----------------------------------------------------------------------- |
| MetaMask not showing UI | Verify Flask 13.5.0+ installed |
| Permission request fails | Check user upgraded to smart account |
| Redemption fails | Verify permissionsContext and delegationManager extracted correctly |
| Gas estimation fails | Ensure paymaster configured or account has funds |
| Invalid permission type | Use supported types (erc20-token-periodic, erc20-token-streaming, native-token-periodic, native-token-streaming) |
| Expired permission | Request new permission with updated expiry |
| User denied permission | Provide fallback UI for manual approval |
## Integration Examples
### DeFi Dapp Integration
**Scenario:** Dapp wants to swap user tokens daily
```typescript
// 1. Setup
const walletClient = createWalletClient({
transport: custom(window.ethereum),
}).extend(erc7715ProviderActions())
// 2. Request daily swap permission
const grantedPermissions = await walletClient.requestExecutionPermissions([{
chainId: chain.id,
expiry: now + 30 * 86400, // 30 days
signer: { type: "account", data: { address: sessionAccount.address } },
permission: {
type: "erc20-token-periodic",
data: {
tokenAddress: USDC_ADDRESS,
periodAmount: parseUnits("100", 6),
periodDuration: 86400,
justification: "Daily automated token swaps",
},
},
isAdjustmentAllowed: true,
}])
// 3. Daily automated redemption (backend/schedule)
const userOpHash = await bundlerClient.sendUserOperationWithDelegation({
publicClient,
account: sessionAccount,
calls: [{
to: DEX_ROUTER,
data: encodeSwapData(...),
permissionsContext: grantedPermissions[0].context,
delegationManager: grantedPermissions[0].signerMeta.delegationManager,
}],
})
```
### Gaming Dapp Integration
**Scenario:** Game needs to make micro-transactions for user
```typescript
// Request small periodic native token allowance
const grantedPermissions = await walletClient.requestExecutionPermissions([
{
chainId: chain.id,
expiry: now + 7 * 86400, // 1 week
signer: { type: 'account', data: { address: sessionAccount.address } },
permission: {
type: 'native-token-periodic',
data: {
periodAmount: parseEther('0.001'), // Small amount
periodDuration: 3600, // Hourly
justification: 'In-game purchases and fees',
},
},
isAdjustmentAllowed: true,
},
])
```
## Resources
- **ERC-7715 Spec:** https://eips.ethereum.org/EIPS/eip-7715
- **ERC-7710 Spec:** https://eips.ethereum.org/EIPS/eip-7710
- **MetaMask Flask:** Required for Advanced Permissions
- **Smart Accounts Kit:** `@metamask/smart-accounts-kit`
## Related Concepts
- **Smart Accounts** - User must be upgraded to smart account
- **Delegations** - Underlying mechanism (ERC-7710)
- **Caveat Enforcers** - Enforce permission constraints