TIP-1011: Enhanced Access Key Permissions
Protocol Version: TBD (requires hardfork)
Status: Draft
Authors: Tanishk Goyal
Related: TIP-1000, AccountKeychain, IAccountKeychain.sol
Status: Draft
Authors: Tanishk Goyal
Related: TIP-1000, AccountKeychain, IAccountKeychain.sol
Abstract
This TIP extends Access Keys with two new permission features: (1) periodic spending limits that automatically reset after a configurable time period, enabling subscription-based access patterns, and (2) destination address scoping that restricts keys to only interact with specific contract addresses.Motivation
Currently, Access Keys support spending limits (per-TIP-20 token caps) and expiry timestamps. However, these primitives are insufficient for common real-world patterns:Periodic Spending Limits
The existingTokenLimit specifies a one-time spending cap that depletes permanently. This model doesn’t support subscription-based patterns where:
- A service needs recurring access to a fixed amount per billing cycle
- Users want to authorize “up to X tokens per month” without re-authorizing
- dApps implement subscription models (e.g., streaming services, SaaS payments)
- Subscription services: Authorize a streaming service to charge 10 USDC/month
- Recurring donations: Allow an NPO to withdraw up to 5 USDC weekly
- Payroll systems: Enable payroll contracts to transfer salaries monthly
- Rate-limited APIs: Authorize API access keys with per-period token budgets
Destination Address Scoping
Users have requested the ability to bind Access Keys to specific destinations (e.g., “only allow transactions to Uniswap”). This provides more granular permission scoping similar to Solana’s delegate primitive. Use cases:- DeFi integrations: Allow a trading bot key to only interact with specific DEX contracts
- Gaming: Scope a session key to only interact with a game contract
- Subscription services: Allow a key to only call a specific payment contract
Specification
Extended Data Structures
TokenLimit
Current:KeyAuthorization
Current fields retained:spendingLimits:TokenLimit[]expiry:uint64
Interface Changes
IAccountKeychain.sol
Semantic Behavior
Periodic Limit Reset Logic
When a spending attempt occurs:Destination Validation Logic
When a transaction is submitted with an Access Key:Interaction Rules
- Mixed limits: Keys can have a mix of one-time and periodic limits for different tokens
- Destination + limits: Both constraints are evaluated independently; both must pass
- Period updates: Calling
updateSpendingLimit()updates the per-period amount and resets the current period - Empty destinations: An empty
allowedDestinationsarray means the key is unrestricted (can call any address)
Gas Costs
| Operation | Additional Gas |
|---|---|
authorizeKey with N destinations | ~20,000 + 5,000 × N |
authorizeKey with periodic limit | ~3,000 per periodic token |
| Destination check (per tx) | ~2,100 + 200 × N |
| Period reset (when triggered) | ~5,000 |
Backward Compatibility
This TIP requires a hardfork due to changes in transaction encoding and execution semantics.RLP Encoding Changes
TokenLimit (2 fields → 5 fields)
The currentTokenLimit struct encodes as [token, limit]. This TIP extends it to [token, limit, remainingInPeriod, period, periodEnd].
Breaking change: Old nodes cannot decode new transactions with 5-field TokenLimit. New nodes must implement version-tolerant decoding:
TokenLimit encodings MUST use the 5-field format for consistency.
KeyAuthorization (trailing field addition)
KeyAuthorization uses #[rlp(trailing)] which allows appending optional fields. Adding allowedDestinations: Option<Vec<Address>> as the last field is compatible with this pattern:
- Old encodings (without
allowedDestinations) decode asallowedDestinations = None(unrestricted) - New encodings with
allowedDestinationswill be rejected by old nodes
Precompile Storage Changes
The current storage layout:keys[account][keyId] → AuthorizedKey(packed slot)spending_limits[key][token] → U256(remaining amount)
| Mapping | Type | Description |
|---|---|---|
spending_limits[key][token] | U256 | Reinterpreted as remainingInPeriod |
spending_limit_max[key][token] | U256 | Per-period cap (new) |
spending_limit_period[key][token] | u64 | Period duration in seconds (new) |
spending_limit_period_end[key][token] | u64 | Current period end timestamp (new) |
| Mapping | Type | Description |
|---|---|---|
allowed_destinations_len[key] | u64 | Number of allowed destinations |
allowed_destinations[key][index] | Address | Allowed destination at index |
period = 0, max = 0, and behave as one-time limits.
Hardfork-Gated Features
The following MUST be gated behind the hardfork activation:- RLP decoding: Accept 5-field
TokenLimitandallowedDestinationsinKeyAuthorization - Periodic limit reset logic: Check
periodEndand resetremainingInPeriodon spend - Destination scoping enforcement: Validate transaction
toagainstallowedDestinations - New precompile storage writes: Write to new storage slots for period/destination data
- New precompile interface methods:
getAllowedDestinations(), updatedgetRemainingLimit()return type
Invariants
-
Period monotonicity:
periodEndMUST only increase; it cannot be set to a past timestamp. -
Limit conservation: For periodic limits,
remainingInPeriodMUST NOT exceedlimitafter any reset. -
Destination enforcement: If
allowedDestinationsis non-empty, transactions to addresses not in the list MUST revert. -
Backward compatibility: Keys authorized without the new fields MUST behave as unrestricted (
allowedDestinations = []) with one-time limits (period = 0). - Expiry precedence: Key expiry MUST be checked before spending limits or destination restrictions.