EIP.tools

EIP.tools

⚠️ DraftStandards Track: Core

EIP-8140: Token Gas Payments for EIP-8130

Enable gas payments using fungible tokens for EIP-8130 AA transactions via the fee_token field

Authors
Created2025-10-14
Discussion Linkhttps://ethereum-magicians.org/t/eip-8130-account-abstraction-by-account-configurations/25952
Requires

Markdown

https://raw.githubusercontent.com/ethereum/EIPs/ed...
Pull Request#11194PR closed (unmerged)

EIP-GPT summary

Contents
AbstractMotivationSpecificationConstantsFee Token FormatToken Payment RegistryToken Configuration StorageBlocklistDeactivation and Emergency PausePayer Modes with Token PaymentsNative PayerStorageOracle-Based PricingOracle MaintenanceBehaviorToken Transfer FlowExchange Rate ResolutionPre-Execution EscrowPost-Execution SettlementIntrinsic GasValidation Flow UpdatesBlock Execution UpdatesTransaction Context ExtensionRationaleWhy maxexchangerate Instead of maxamount?Why Payer Commitment via Signature Marker?Why No Permissionless Third-Party Payers?Why Direct Storage Reads for Balances?Backwards CompatibilityReference ImplementationITokenPaymentRegistryINativePayerSecurity ConsiderationsCopyright

Abstract

This proposal extends EIP-8130 to enable gas payments using ERC-20 tokens. It defines the token payment format for EIP-8130's fee_token field, a Token Payment Registry for allowlisting tokens, and an optional native payer system contract for chain-operated gas abstraction with oracle-based pricing.

Motivation

EIP-8130 provides account abstraction with ETH-based gas sponsorship. However, many users hold tokens (USDC, USDT, etc.) but no ETH, creating friction for onboarding and transaction submission. Token gas payments enable:

  • Gasless UX: Users transact with tokens they already hold
  • Stablecoin payments: Predictable transaction costs in USD-denominated tokens
  • Competitive market: Permissioned payers compete on exchange rates off-chain
  • Chain flexibility: Native payer option for chain-operated gas abstraction with oracle-based pricing
  • Zero token changes: Works with existing ERC-20 tokens using standard transfer logicβ€”no upgrades or storage migrations required

This proposal uses EIP-8130's fee_token field to add token payment capability without modifying the base transaction type.

Specification

Constants

NameValueComment
TOKEN_PAYMENT_REGISTRYTBDToken Payment Registry system contract address
TOKEN_TRANSFER_COST13000Token escrow/refund gas (see breakdown below)
DEACTIVATION_DELAYTBDBlocks before token deactivation takes effect
PAUSE_COSTTBDETH required to invoke emergency pause
NATIVE_PAYERTBD(Optional) Native gas AMM payer system contract address

Fee Token Format

Token payments use the EIP-8130 fee_token field with packed byte encoding:

LengthFormatDescription
0 bytes0x (empty)ETH payment only
52 bytestoken (20) || max_exchange_rate (32)Token gas payment
fee_token = token_address || max_exchange_rate
          = address (20 bytes) || uint256 (32 bytes)
          = 52 bytes total
FieldTypeSizeDescription
token_addressaddress20 bytesERC-20 token contract address
max_exchange_rateuint25632 bytesMaximum tokens per ETH the sender will accept, in token's smallest unit (e.g., 2000 USDC/ETH = 2000 Γ— 10^6)

The max_exchange_rate is denominated in the token's native decimals per ETH, making it oracle-decimal-agnostic. The sender does NOT commit to a specific payer in fee_token β€” payer commitment uses EIP-8130's extended payer modes (see Payer Modes with Token Payments).

When fee_token is non-empty, the transaction uses token-based gas payment instead of ETH.

Token Payment Registry

Tokens must be registered in the Token Payment Registry system contract at TOKEN_PAYMENT_REGISTRY before use for gas payment. Similar to EIP-8130's account configuration precompile, this is a protocol-level system contract with persistent storage. The registry stores token metadata required for protocol-level balance reads and transfers.

The registry defines a single protocol-level interface that all wallets and payers integrate with. Its governance model for write access (token registration, pause, blocklist) is implementation-defined β€” chain-operated, permissionless, or hybrid β€” and can be extended by the deployer. The protocol reads from the registry identically regardless of governance model.

Token Configuration Storage

All storage keys use byte concatenation (|| denotes concatenation of raw bytes, addresses as 20 bytes, strings as UTF-8):

Base slot: keccak256(token_address || TOKEN_PAYMENT_REGISTRY || "config")

- base_slot + 0: balance_slot_index (uint256)
- base_slot + 1: packed config (uint256)
    - byte 0: token_decimals (uint8)
    - byte 1: active (bool)
    - byte 2: paused (bool)
    - bytes 3-31: reserved
- base_slot + 2: deactivation_block (uint256) β€” 0 if no pending deactivation
FieldDescription
balance_slot_indexStorage slot index for token's balance mapping. User balance at: keccak256(user_address || balance_slot_index) in the token contract
token_decimalsToken decimal places (e.g., 6 for USDC, 18 for most tokens)
activeWhether token is currently accepted for gas payments
pausedWhether token is globally paused (emergency, instant)
deactivation_blockBlock number at which token becomes inactive. Set by requestDeactivation, takes effect after DEACTIVATION_DELAY blocks

Blocklist

Blocklist state is managed directly in the TOKEN_PAYMENT_REGISTRY rather than read from individual token contracts. This decouples the protocol from token storage internals and provides a single, predictable location for compliance checks:

Blocklist slot: keccak256(token_address || account_address || TOKEN_PAYMENT_REGISTRY || "blocklist")
Value: bool (true = blocked)

Token issuers or designated admins call setBlocked(token, account, blocked) on the registry to maintain the blocklist. For tokens like USDC, an admin or keeper bot monitors the token's native Blacklisted events and mirrors them to the registry. The number of blocklisted addresses is typically small (hundreds for major stablecoins β€” OFAC sanctions, stolen funds, etc.), making this approach practical.

Deactivation and Emergency Pause

Token removal and emergency pause use separate mechanisms:

Deactivation (planned removal, timelocked): requestDeactivation(token) sets deactivation_block = current_block + DEACTIVATION_DELAY. The token remains active until that block, giving pending transactions time to clear. cancelDeactivation(token) resets the countdown. Activation via activateToken(token) takes effect immediately β€” enabling a token does not invalidate transactions.

Emergency pause (instant, costs ETH): setTokenPaused(token, true) takes effect immediately and requires msg.value >= PAUSE_COST. The ETH cost discourages frivolous or spam pauses while preserving the ability to respond to genuine emergencies (e.g., a token exploit). Unpausing is free and immediate.

Payer Modes with Token Payments

This extension leverages EIP-8130's payer marker in the sender signature hash to add token payment capability. EIP-8130 defines the marker as 0x80 (self-pay) or 0x00 (any sponsor). This EIP extends the marker to accept a 20-byte payer address, enabling the sender to commit to a specific payer without modifying fee_token:

Sender Signs (marker)payer_auth (wire)Gas PayerToken RecipientDescription
0x80 (empty)MUST be emptyfrom (ETH only)β€”Self-pay; fee_token MUST be empty
0x0065-byte K1 signatureecrecover(sig)PayerAny permissioned payer; payer signs tx
payer_address (20 bytes)65-byte sig (must match)Committed payerPayerSpecific permissioned payer
NATIVE_PAYER (20 bytes)MUST be emptyNATIVE_PAYERNATIVE_PAYERNative payer (chain-operated)

The RLP encoding naturally disambiguates: 0 bytes = self-pay, 1 byte (0x00) = any sponsor, 20 bytes = specific payer address.

Permissioned payers sign each transaction, explicitly agreeing to the max_exchange_rate. They can use either 0x00 (any willing signer) or a 20-byte address (specific signer). The NATIVE_PAYER address is used directly as the marker for native payer mode. No permissionless (unsigned) third-party payers are supported β€” see Security Considerations for rationale.

Updated sender signature hash (extends EIP-8130 Signature Payload):

// Self-pay (from pays ETH):
keccak256(AA_TX_TYPE || rlp([..., fee_token, 0x80]))

// Any permissioned sponsor:
keccak256(AA_TX_TYPE || rlp([..., fee_token, 0x00]))

// Specific payer (permissioned or native):
keccak256(AA_TX_TYPE || rlp([..., fee_token, payer_address]))

Native Payer

The optional NATIVE_PAYER system contract provides chain-operated gas abstraction with oracle-based pricing. This is the only mechanism for unsigned (non-permissioned) token gas payments.

Storage

Per-token oracle config:
Token slot: keccak256(token_address || NATIVE_PAYER || "oracle")

- token_slot + 0: oracle_address (address)
- token_slot + 1: oracle_slot (bytes32)
- token_slot + 2: oracle_decimals (uint8)

Chain operators configure accepted tokens and their oracle feeds via the NATIVE_PAYER contract. Only tokens registered in the Token Payment Registry AND configured in the native payer can be used with native payer mode.

Oracle-Based Pricing

The native payer reads exchange rates from chain-operator-configured oracles. The oracle value represents tokens per ETH, scaled by 10^oracle_decimals. The protocol normalizes the oracle value to the token's native units per ETH for comparison with max_exchange_rate:

oracle_value = SLOAD(native_payer.oracle_address, native_payer.oracle_slot)
exchange_rate = oracle_value * 10^token_decimals / 10^oracle_decimals
token_cost = ceil(gas_cost_wei * exchange_rate / 10^18)

Both exchange_rate and max_exchange_rate are in the same units: token's smallest unit per ETH. Validation requires exchange_rate <= max_exchange_rate.

Example: USDC payment for 0.001 ETH gas cost (1e15 wei), ETH at $2000:

  • oracle_value = 2000 Γ— 10^8 (Chainlink 8-decimal standard)
  • exchange_rate = 2000e8 Γ— 10^6 / 10^8 = 2000e6 (= 2000 USDC per ETH in USDC units)
  • max_exchange_rate = 2100e6 (sender accepts up to 2100 USDC/ETH)
  • token_cost = 1e15 Γ— 2000e6 / 1e18 = 2e6 = 2 USDC

If oracle returns 0, token_cost is 0 (chain operator assumes this risk).

Oracle Maintenance

Oracle maintenance is the chain operator's responsibility. Common approaches:

Oracle TypeDescription
Chainlink feedsDecentralized price updates via established infrastructure
Internal feedChain operator runs an updater service
DEX-integratedOracle contract queries configured on-chain liquidity pool

The oracle rate is read once and locked at the start of each AA transaction (see Exchange Rate Resolution). Stale prices are the chain operator's risk.

Behavior

  • Uses the Token Payment Registry for token allowlisting and metadata
  • Chain operators define supported tokens and oracle feeds
  • Referenced via the sender's payer marker: sender signs NATIVE_PAYER address as the 20-byte marker, payer_auth is empty
  • Chain operators may implement custom pricing logic (AMMs, fixed rates, or custom curves) within the native payer contract

Token Transfer Flow

Token transfers use a two-sided escrow/refund model. The exchange rate is resolved and locked once at the start of transaction execution β€” it does not change during the transaction.

Exchange Rate Resolution

The exchange rate is determined by payer type and locked at the start of AA transaction execution:

Payer TypeExchange Rate SourceMeaning of max_exchange_rate
Permissioned (65-byte sig)max_exchange_rate directlyMutually agreed rate (both parties signed over it)
Native (NATIVE_PAYER)Chain-operator oracleCap β€” oracle rate MUST NOT exceed max_exchange_rate

max_exchange_rate serves the same role for token pricing that max_fee_per_gas serves for ETH gas pricing β€” it is the sender's maximum willingness-to-pay. For native payer transactions, nodes SHOULD reject transactions where max_exchange_rate < current_oracle_rate, just as nodes reject transactions where max_fee_per_gas < base_fee. Wallets SHOULD set max_exchange_rate with a buffer above the current oracle rate (e.g., 10%) to avoid premature invalidation during normal price movement.

Pre-Execution Escrow

  1. Parse fee_token to extract token_address and max_exchange_rate
  2. Resolve exchange rate (locked for the duration of this tx):
    • Permissioned payer: exchange_rate = max_exchange_rate (mutually agreed)
    • Native payer: exchange_rate = oracle_rate. If oracle_rate > max_exchange_rate, transaction fails
  3. Verify token is not paused (registry paused flag)
  4. Verify sender is not blocklisted (registry blocklist)
  5. Compute max_token_cost = ceil(gas_limit Γ— max_fee_per_gas Γ— exchange_rate / 10^18)
  6. Token escrow: Deduct max_token_cost from sender's token balance (sender tokens on hold)
  7. ETH escrow: Deduct gas_limit Γ— max_fee_per_gas from payer's ETH balance (payer ETH on hold, per EIP-8130)

Post-Execution Settlement

After EVM execution completes (regardless of revert):

  1. Compute actual_gas_cost = gas_used Γ— effective_fee_per_gas
  2. Compute actual_token_cost = ceil(actual_gas_cost Γ— exchange_rate / 10^18)
  3. Token settlement: Transfer actual_token_cost from escrow to payer; refund max_token_cost - actual_token_cost back to sender
  4. ETH settlement: Refund (gas_limit Γ— max_fee_per_gas) - actual_gas_cost back to payer (per EIP-8130)

All token operations occur outside EVM execution via direct storage manipulation.

Intrinsic Gas

When fee_token is non-empty, add to EIP-8130 intrinsic gas:

intrinsic_gas = EIP8130_intrinsic_gas + TOKEN_TRANSFER_COST

TOKEN_TRANSFER_COST (~13,000 gas) covers the protocol's direct storage operations for the escrow/refund flow:

OperationGasNotes
Sender balance read (cold SLOAD)2,100From token contract storage
Sender balance write (warm SSTORE)2,900Non-zero β†’ non-zero
Payer balance read (cold SLOAD)2,100From token contract storage
Payer balance write (warm SSTORE)2,900Non-zero β†’ non-zero
Oracle read (cold SLOAD)2,100From oracle contract storage (native payer only; 0 for permissioned)
Blocklist + pause check~200Registry storage (warm β€” same contract as token config reads)
Post-execution refund (2Γ— warm SSTORE)~800Both balance slots already warm
Total~13,000

Validation Flow Updates

Extend EIP-8130 mempool acceptance:

  1. (EIP-8130 steps 1-6: signature, nonce, etc.)
  2. If fee_token is non-empty (52 bytes):
    • Parse fee_token: extract token_address, max_exchange_rate
    • Verify token is registered and active in Token Payment Registry
    • Verify token is not paused (registry paused flag)
    • Verify sender is not blocklisted (registry blocklist)
    • Resolve payer from sender's signed marker and payer_auth:
      • Marker = empty (0x80): REJECT (self-pay not valid with token payments)
      • Marker = 0x00: recover payer via ecrecover from payer_auth (any permissioned payer)
      • Marker = 20-byte address with 65-byte payer_auth: specific permissioned payer (ecrecover must match marker)
      • Marker = NATIVE_PAYER: payer_auth MUST be empty; verify native payer has oracle config for token
    • For native payer: read oracle exchange rate; REJECT if max_exchange_rate < current_oracle_rate
    • For permissioned payer: use max_exchange_rate directly (mutually agreed rate)
    • Compute max_token_cost = ceil(gas_limit Γ— max_fee_per_gas Γ— exchange_rate / 10^18)
    • Verify sender token balance β‰₯ max_token_cost
  3. (EIP-8130 remaining steps: nonce, balance, expiry)

Nodes SHOULD treat max_exchange_rate the same way they treat max_fee_per_gas: reject transactions where the cap is below the current rate. For native payer transactions, this means max_exchange_rate >= current_oracle_rate. Transactions that were valid but become invalid due to oracle rate increases are evicted from the mempool, the same as transactions evicted when base_fee rises above max_fee_per_gas.

Block Execution Updates

Extend EIP-8130 block execution:

  1. Exchange rate lock (if fee_token non-empty): Read and lock exchange rate for this tx
  2. Token escrow (if fee_token non-empty): Deduct max_token_cost from sender
  3. ETH escrow: Deduct gas_limit Γ— max_fee_per_gas from payer (per EIP-8130)
  4. Process authorization_list (EIP-7702)
  5. Account initialization (if applicable)
  6. Apply key changes (if applicable)
  7. Deliver calldata to sender via self-call
  8. Token settlement (if fee_token non-empty): Transfer actual_token_cost to payer, refund excess to sender
  9. ETH settlement: Refund unused ETH gas to payer (per EIP-8130)

Steps 1–6 and 8–9 are protocol-level direct state operations (no EVM execution). Step 7 is the single EVM execution frame. Token escrow (step 2) ensures the sender cannot move tokens during execution; settlement (step 8) transfers the actual cost. If step 7 reverts, token and ETH settlement still complete (consistent with EIP-8130's revert semantics).

Transaction Context Extension

Extend EIP-8130's IAccountConfig with:

// Transaction context (only valid during AA transaction execution)
function getGasPaymentInfo() external view returns (address token, uint256 amount);

Returns the token address and amount transferred for gas payment. Returns (address(0), 0) for ETH-only payments.

Rationale

Why max_exchange_rate Instead of max_amount?

The sender specifies a maximum exchange rate (tokens per ETH) rather than an absolute token amount:

  1. Scales naturally: The actual token cost scales with gas used β€” no over-escrow beyond what the gas limit requires
  2. Rate-centric: Users think in terms of "what rate am I paying?" not "what absolute amount might be deducted"
  3. Oracle-comparable: Directly comparable to the oracle's exchange rate without additional conversions
  4. Simpler escrow: max_token_cost = gas_limit Γ— max_fee_per_gas Γ— max_exchange_rate / 10^18 β€” deterministic from tx fields
  5. max_fee_per_gas analogy: max_exchange_rate is to the oracle rate what max_fee_per_gas is to base_fee β€” a sender-specified ceiling that determines validity. Wallets set both with headroom above the current value; transactions become invalid when the underlying rate exceeds the cap. This is a familiar pattern for mempool nodes and wallet implementations.

Why Payer Commitment via Signature Marker?

EIP-8130 already includes a payer marker in the sender's signature hash (0x80 for self-pay, 0x00 for any sponsor). Extending this to accept a 20-byte payer address lets the sender commit to a specific payer without modifying fee_token. This prevents payer attachment attacks where an attacker replaces the intended payer with one offering worse rates. The sender's max_exchange_rate provides a hard cap, but the payer marker provides the primary protection by binding the sender's signature to a specific payer.

Why No Permissionless Third-Party Payers?

Oracle-based pricing without a per-transaction signature creates a mass invalidation attack surface. A permissionless payer registers an oracle, and the mempool fills with transactions referencing that oracle. If the oracle price spikes (maliciously or organically), all pending transactions where oracle_rate > max_exchange_rate become invalid simultaneously. Worse, an attacker can register multiple Sybil payer identities pointing to the same oracle, circumventing per-payer pending limits and amplifying the blast radius.

Oracle-based pricing is instead scoped to the native payer β€” a chain-operated system contract where the chain operator controls the oracle feed. This provides a single trusted operator with aligned incentives, no Sybil risk, and a simple invalidation model. Competitive pricing is achieved through permissioned payers who sign each transaction, agreeing to a specific max_exchange_rate β€” no oracle dependency in the mempool.

Why Direct Storage Reads for Balances?

Reading token balances directly from storage slots rather than calling ERC-20 methods:

  1. No token upgrades: Existing tokens with standard mapping(address => uint256) balance storage work without modificationβ€”just register the balance slot index
  2. No storage migrations: Tokens don't need to move data or conform to new interfaces
  3. Atomic execution: Balance reads and transfers happen at protocol level, before and after EVM execution (escrow/refund model)

Blocklist and pause state are managed in the TOKEN_PAYMENT_REGISTRY rather than read from token contracts. This decouples the protocol from token storage internalsβ€”each token's blocklist implementation is different (high-bit flags, separate mappings, struct fields), but the registry provides a single, predictable interface. Designated admins mirror token issuer blocklist events to the registry.

Backwards Compatibility

This proposal extends EIP-8130. Nodes not supporting this EIP will reject transactions with non-empty fee_token as specified in EIP-8130.

Reference Implementation

ITokenPaymentRegistry

interface ITokenPaymentRegistry {
    event TokenRegistered(address indexed token, uint256 balanceSlotIndex, uint8 tokenDecimals);
    event TokenActivated(address indexed token);
    event DeactivationRequested(address indexed token, uint256 effectiveBlock);
    event DeactivationCancelled(address indexed token);
    event TokenPaused(address indexed token, bool paused);
    event BlocklistUpdated(address indexed token, address indexed account, bool blocked);
    event BlocklistAdminUpdated(address indexed token, address indexed admin, bool authorized);
    
    // Token registration β€” idempotent, calling on an already-registered token updates its config
    function registerToken(address token, uint256 balanceSlotIndex, uint8 tokenDecimals) external;
    
    // Activation (immediate) and deactivation (timelocked by DEACTIVATION_DELAY)
    function activateToken(address token) external;
    function requestDeactivation(address token) external;
    function cancelDeactivation(address token) external;
    
    // Emergency pause β€” pausing requires msg.value >= PAUSE_COST
    function setTokenPaused(address token, bool paused) external payable;
    
    // Blocklist management (token admin or designated blocklist admin)
    function setBlocklistAdmin(address token, address admin, bool authorized) external;
    function setBlocked(address token, address account, bool blocked) external;
    
    // View functions β€” isTokenActive returns false if paused or past deactivation_block
    function isTokenActive(address token) external view returns (bool);
    function isBlocked(address token, address account) external view returns (bool);
    function getTokenConfig(address token) external view returns (
        uint256 balanceSlotIndex, uint8 tokenDecimals, bool active, bool paused, uint256 deactivationBlock
    );
}

INativePayer

interface INativePayer {
    event OracleConfigured(address indexed token, address oracleAddress, bytes32 oracleSlot, uint8 oracleDecimals);
    event OracleRemoved(address indexed token);
    
    // Oracle configuration (chain operator only)
    function configureOracle(address token, address oracleAddress, bytes32 oracleSlot, uint8 oracleDecimals) external;
    function removeOracle(address token) external;
    
    // View functions
    function getOracleConfig(address token) external view returns (address oracleAddress, bytes32 oracleSlot, uint8 oracleDecimals);
    function getExchangeRate(address token) external view returns (uint256);
}

Security Considerations

Token Spend Protection: Token gas payments occur before wallet code executes, bypassing wallet-level spend limits. Mitigations:

  • max_exchange_rate cap: Hard limit on the exchange rate per transaction; combined with max_fee_per_gas and gas_limit, this bounds maximum token spend
  • requiresSponsor policy (EIP-8130): Accounts can enable the requiresSponsor flag in their gas payment policy, which disables ALL self-paid gas (ETH and tokens). When enabled, transactions must have a sponsor signature in payer_auth. This completely eliminates the gas spend attack vector and is strongly recommended for accounts using EVM-layer multisig.
  • Blocklist checks: Protocol reads the registry-managed blocklist before escrow, ensuring blocklisted accounts cannot spend tokens on gas

Escrow/Griefing Prevention: The two-step escrow model (deduct max_token_cost pre-execution, refund excess post-execution) prevents the sender from transferring tokens away during EVM execution. Without escrow, the sender could drain their token balance in calldata execution, causing the post-execution token deduction to fail while the payer still pays ETH gas.

Oracle Risks: Oracle-based pricing is restricted to the chain-operated native payer. The oracle rate is read once and locked at the start of AA transaction execution. If another transaction in the same block updates the oracle, the new rate is used β€” but the sender's max_exchange_rate cap protects against spikes. Stale oracle prices are the chain operator's risk.

Payer Security: Permissioned payers sign each transaction, explicitly agreeing to the max_exchange_rate. When the sender uses 0x00 (any sponsor), only permissioned payers who sign can attach. The native payer requires the sender to sign NATIVE_PAYER as the 20-byte marker. In all cases, max_exchange_rate provides a hard cap on token cost, preventing payer attachment attacks with worse rates.

Mass Invalidation Prevention: Multiple layers protect against mass mempool invalidation:

  • No permissionless third-party payers: Oracle-based payers where anyone can register create a Sybil-amplified invalidation vector. Oracle pricing is restricted to the chain-operated native payer.
  • Timelocked deactivation: requestDeactivation starts a DEACTIVATION_DELAY countdown, giving pending transactions time to clear. Mempool nodes can stop accepting new transactions for a token with a pending deactivation.
  • Paid emergency pause: setTokenPaused requires a non-zero ETH payment (PAUSE_COST), making frivolous or spam pauses economically costly. Emergency pauses may still invalidate pending transactions, but the ETH cost bounds abuse.
  • Permissioned payer rate lock: Permissioned payers sign each transaction with an agreed max_exchange_rate, fixing the rate at signature time with no oracle dependency in the mempool.

Balance Manipulation: Protocol reads token balances directly from storage slots. Tokens with non-standard balance storage (rebasing, fee-on-transfer) may behave unexpectedly. Only register well-understood tokens.

Balance Slot Changes: If a registered token upgrades and changes its balance storage slot, the registry configuration becomes stale. The admin can call registerToken again to update the config atomically (no deactivation gap). However, tokens used for gas payment should have immutable balance storage layouts β€” frequent slot changes indicate an unstable token that should not be registered.

Blocklist Sync: The registry-managed blocklist may lag behind a token issuer's native blocklist by a few blocks. In the worst case, a newly-blacklisted address could spend a small amount of tokens on gas before the registry is updated. This is an acceptable tradeoff for the simplicity of not coupling to each token's internal storage layout. Designated admins should monitor token blocklist events and mirror updates promptly.

Copyright and related rights waived via CC0.

EIP.tools

EIP.tools

Search, read, and map Ethereum improvement proposals, ERCs, RIPs, and CAIPs from one focused interface.

Farcaster
by @apoorveth