Technical Dive: Key Permissions for Accounts

Joan Alavedra10 min read
Technical Dive: Key Permissions for Accounts

Smart accounts need permission systems. You can't give every key full access to everything. The question is: where do you enforce those rules?

This guide covers both approaches and goes deep on onchain permissions—the stuff that's actually enforced by smart contracts on the blockchain.

Two Approaches to Permissions

AspectOffchain PermissionsOnchain Permissions
Core MechanismPolicy engines running in backends or TEEs. Checks happen before signing.Rules enforced directly by smart contracts. Checks happen during execution.
How It WorksBackend evaluates rules (spending limits, time windows, etc.) before signing.Smart account validates every transaction against on-chain rules. Invalid ones revert.
ProsFlexible: Change rules without deploying contracts
Advanced Logic: Can use ML/AI (fraud detection)
Low Cost: Validation is off-chain
Iteration: Easy to update/test
Trustless: No backend needed
Immutable: Rules require consent to change
Transparent: Users can verify rules
Resilient: Works if dApp goes offline
Cons• Requires trust in operator
• Rules can change secretly
• No on-chain proof of enforcement
• Centralized point of failure
• Higher gas costs
• Updates require transactions
• Limited by EVM constraints
• More upfront design work

Onchain Permissions Deep Dive

Openfort's EIP-7702 Smart Accounts implement a comprehensive onchain permission system. There are two versions with different capabilities.

Supported Key Types

The system supports four key types, each validated differently:

Key TypeDescriptionUse CasesValidation
EOATraditional ECDSA keysStandard wallets, developmentECDSA signature
WEBAUTHNWebAuthn credentialsBiometrics, hardware keysWebAuthn assertion
P256Standard P-256 keysExtractable P-256 signaturesP-256 ECDSA
P256NONKEYHardware-bound P-256Non-extractable hardware keysSHA-256 digest

The Basics

The first version provides essential permission controls: time bounds, usage limits, spending caps, and contract whitelisting.

Key Manager v0.0.1 Architecture

Permission Fields

PermissionTypeDescription
validAfteruint48Unix timestamp—key activates after this time
validUntiluint48Unix timestamp—key expires after this time
limituint48Maximum operations allowed (decrements per call)
ethLimituint256Maximum ETH the key can spend (in wei)
spendTokenInfo.tokenaddressERC-20 token address for spend tracking
spendTokenInfo.limituint256Maximum token amount the key can transfer
whitelistingboolWhen true, enforces contract whitelist
whitelistmappingAllowed target contract addresses
allowedSelectorsbytes4[]Permitted function selectors (max 10)

Time Bounds

Control when a key can be used:


_10
validAfter = block.timestamp // active now
_10
validUntil = block.timestamp + 1 days // expires in 24 hours

The key is rejected if block.timestamp < validAfter or block.timestamp > validUntil.

Usage Quota

Control how many times a key can execute operations:


_10
limit = 100 // allows 100 transactions, then the key is invalid

The limit decrements by 1 for each call. Master keys use limit = 0 for unlimited operations.

ETH Spending

Control how much ETH the key can transfer:


_10
ethLimit = 500000000000000000 // 0.5 ETH in wei

Cumulative tracking—each transaction's value is subtracted. Reverts if value > ethLimit.

Token Spending

Control how much of a single ERC-20 the key can transfer:


_10
spendTokenInfo.token = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 // USDC
_10
spendTokenInfo.limit = 1000000000 // 1000 USDC (6 decimals)

Only one token per key. Tracks transfer() and transferFrom() calls.

Contract Whitelisting

Control which contracts the key can interact with:


_10
whitelisting = true
_10
whitelist[UniswapRouter] = true
_10
whitelist[USDC] = true

When enabled, only whitelisted addresses can be called. The spend token is automatically whitelisted.

Function Filtering

Control which functions the key can call:


_10
allowedSelectors = [
_10
0xa9059cbb, // transfer(address,uint256)
_10
0x095ea7b3 // approve(address,uint256)
_10
]

Maximum 10 selectors per key. Reverts if the called function isn't in the list.

Example: Gaming Session Key

ParameterValueRationale
validAfternowImmediately active
validUntilnow + 4 hoursGaming session duration
limit500Max 500 in-game actions
ethLimit0No ETH transfers
spendTokenInfo.tokenGAME_TOKENIn-game currency
spendTokenInfo.limit10000 * 10^1810,000 tokens max
whitelist[GameContract, GAME_TOKEN]Only game interactions
allowedSelectors[transfer, performAction]Limited functions

v0.0.1 Constraints

ConstraintRequirement
Session key limitMust be > 0 (0 reserved for master keys)
Session key whitelistingMust be true (prevents unrestricted access)
Token per keySingle token only
Selector countMaximum 10
Token standardERC-20 only
Self-callsBlocked

V2: Advanced Permissions

V2 introduces period-based spending limits, wildcard permissions, multi-token support, and dynamic permission management.

Key Manager v0.0.2 Architecture

What Changed

Featurev0.0.1v0.0.2
Contract Permissionswhitelist + allowedSelectors[] (max 10)ExecutePermissions with EnumerableSet (capacity 2048)
Token SpendingSingle token, absolute limitMultiple tokens (64), period-based limits
Native ETHSeparate ethLimit fieldUnified via 0xEeeE...EEeE sentinel
WildcardsNoneANY_TARGET, ANY_FN_SEL, EMPTY_CALLDATA_FN_SEL
Key ControlBoolean whitelistingisDelegatedControl flag
Key ManagementRegister/Revoke only+ updateKeyData(), pauseKey(), unpauseKey()
Permission ManagementFixed at registrationDynamic via setCanCall(), setTokenSpend()

Execute Permissions System

Execute Permissions

Each permission is a packed bytes32:


_10
┌────────────────────────────────────────┬──────────────┐
_10
│ target address (20 bytes) │ selector (4) │
_10
└────────────────────────────────────────┴──────────────┘

Wildcard Constants


_10
ANY_TARGET = 0x3232323232323232323232323232323232323232 // Any contract
_10
ANY_FN_SEL = 0x32323232 // Any function
_10
EMPTY_CALLDATA_FN_SEL = 0xe0e0e0e0 // Plain ETH transfer

Permission Matching Hierarchy

The system checks permissions in order:

PriorityPatternMeaning
1(target, selector)Exact match
2(target, ANY_FN_SEL)Any function on specific contract
3(ANY_TARGET, selector)Specific function on any contract
4(ANY_TARGET, ANY_FN_SEL)Full wildcard

Examples:


_10
// Allow any function on Uniswap Router
_10
setCanCall(keyHash, UniswapRouter, ANY_FN_SEL, true);
_10
_10
// Allow transfer() on any ERC-20
_10
setCanCall(keyHash, ANY_TARGET, 0xa9059cbb, true);

Period-Based Token Spending

The killer feature of v0.0.2: spending limits that reset on calendar boundaries.

Spending Periods

PeriodResets At (UTC)Use Case
MinuteEvery minuteHigh-frequency micro-payments
HourEvery hourHourly rate limiting
Day00:00:00 UTC dailyDaily allowances
WeekMonday 00:00:00 UTCWeekly budgets
Month1st of month 00:00:00 UTCMonthly allowances
YearJanuary 1st 00:00:00 UTCAnnual budgets
ForeverNeverLifetime caps

How It Works

A limit is an amount-per-period, not transaction count.

Month, 1000 USDC means:

  • ✅ 2 × 500 USDC
  • ✅ 5 × 200 USDC
  • ✅ 1000 × 1 USDC
  • ❌ Any transaction pushing monthly total > 1000

On each spend:

  1. Compute currentPeriodStart = startOfPeriod(block.timestamp, period)
  2. If lastUpdated < currentPeriodStart → new period → spent = 0
  3. spent += amount
  4. If spent > limit → revert

Real-World Example: Monthly Allowance

Alice gives Bob a monthly USDC allowance of 1,000.

DateActionResult
Sep 03Bob sends 400 USDC✅ Month total = 400/1000
Sep 20Bob sends 600 USDC✅ Month total = 1000/1000
Sep 28Bob tries 50 USDC❌ Reverts (would exceed 1000)
Oct 01 00:00:00 UTCPeriod resetsCounter = 0. Bob has 1000 again

Important: Approvals count toward the limit. If Bob approves 1,000 USDC and then spends 600, the system charges 1,000 (the max of approval and actual spend). Approvals are revoked post-batch to prevent drain attacks.

Native ETH Spending

ETH uses the sentinel address:


_10
NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE

Allow 0.1 ETH per day:


_10
setTokenSpend(keyHash, NATIVE_TOKEN, SpendPeriod.Day, 0.1 ether);

Layered Limits

Set multiple periods on the same token:


_10
// Max 50 USDC per day
_10
setTokenSpend(keyHash, USDC, SpendPeriod.Day, 50e6);
_10
_10
// Max 1,000 USDC per month
_10
setTokenSpend(keyHash, USDC, SpendPeriod.Month, 1000e6);

Both limits are enforced. Transaction fails if it exceeds either limit.

Use Cases

Employee Expense Card

Employee Expense Card

You are issuing an expense card for a contractor. You need it to work for their one-year contract but want strict oversight on the funds.

The Setup: Set validUntil to now + 1 year and limits to 1000 to cover the employment period. Flag isDelegatedControl as true—the company pays the gas. For spend controls, layer them: a 5000 USDC monthly budget for general expenses, but a 500 USDC daily cap to mitigate impact from a compromised key. Restrict execution to approved vendors only: (VendorContract, ANY_FN_SEL).

Trading Bot Key

Trading Bot Key

A high-frequency bot needs autonomy to capture arbitrage opportunities without user confirmation for every trade.

The Setup: Authorize the key for 30 days with a high limits count of 10000. This is a power-user setup (isDelegatedControl: false), so the user retains custody. The bot needs volume: 10000 USDC daily and 100000 USDC lifetime. Permissioning is precise: allow swapExactTokensForTokens on the UniswapRouter and approve on ANY_TARGET. This lets the bot trade and manage approvals without touching the user's long-term savings.

Subscription Service

Subscription Service

For a subscription, you want a "set it and forget it" experience that mirrors a credit card standing order.

The Setup: This key runs forever (validUntil: type(uint48).max) and has no operation limit (limits: 0). The service acts as the operator (isDelegatedControl: true). Security relies entirely on the spending cap: strictly 100 USDC per month. To prevent abuse, whitelist only the charge function on the SubscriptionContract. This guarantees the key can only pay the subscription and nothing else.

Gaming Session

Gaming Session

A session key for gaming should act like an arcade token: use it for the session, then it's worthless.

The Setup: Create a short-lived key: validUntil is now + 4 hours. Cap it at 500 actions. Limit the exposure of in-game assets: 100 GAME_TOKEN per hour and 1000 for the key's lifetime. Grant full gameplay freedom by allowing ANY_FN_SEL on the GameContract. The player enjoys a signature-free experience, and you ensure the key can't drain the main wallet if the game client is compromised.

Getting Started

Onchain permissions are a core component of Openfort's Account Abstraction infrastructure.

Check out the documentation to start implementing permission systems in your application.

Share this article

Keep Reading