·Transfa Team

How to Use Access Keys and Session Keys on Tempo

Provision scoped access keys on Tempo using the Account Keychain precompile. Set spending limits, expiry times, and signature type constraints.

tempoaccess-keyssession-keysaccount-keychainsecurity

Tempo's Account Keychain lets an account's root key provision access keys with spending limits, expiry times, and signature type constraints. This is how you build session keys, delegated access, and scoped API keys, without deploying a smart contract wallet.

A practical example: a user authenticates with their passkey (Face ID), then provisions a session key that can spend up to $1,000 USDC for the next 24 hours. During that window, the session key signs transactions without additional biometric prompts. After 24 hours or $1,000, the key becomes inert.

Prerequisites

How the Account Keychain works

The Account Keychain is a precompile at 0xAAAAAAAA00000000000000000000000000000000. It's part of the protocol, not a contract you deploy.

Every Tempo account starts as a standard EOA controlled by a single root key. The root key can then authorize additional keys with three constraints:

  1. Signature type: restricts the key to secp256k1, P256 (passkey curve), or WebAuthn signatures
  2. Expiry: a Unix timestamp after which the key stops working. Checked during transaction validation, not execution.
  3. Spending limits: per-token caps on how much the key can transfer directly

Access keys sign transactions using a special signature format: 0x03 + 20-byte user address + inner signature. The protocol resolves the user's account, checks the key's constraints, and either accepts or rejects the transaction before EVM execution begins.

Authorize an access key

Generate a new key pair for the access key, then authorize it from the root key:

# Generate a new key pair for the access key
ACCESS_KEY_ADDRESS=0xACCESS_KEY_ADDRESS
 
# Authorize it with:
# - secp256k1 signature type (0)
# - 24-hour expiry
# - 1000 AlphaUSD spending limit
EXPIRY=$(($(date +%s) + 86400))
ALPHA_USD=0x20c0000000000000000000000000000000000001
 
cast send 0xAAAAAAAA00000000000000000000000000000000 \
  "authorizeKey(address,uint8,uint64,bool,(address,uint256)[])" \
  $ACCESS_KEY_ADDRESS \
  0 \
  $EXPIRY \
  true \
  "[($ALPHA_USD,1000000000)]" \
  --rpc-url https://rpc.moderato.tempo.xyz \
  --private-key $ROOT_PRIVATE_KEY

Parameters:

  • keyId: The address of the access key (derived from its public key)
  • signatureType: 0 = secp256k1, 1 = P256, 2 = WebAuthn
  • expiry: Unix timestamp. Set to 0 for no expiry.
  • enforceLimits: If true, spending limits are checked on direct transfers
  • limits: Array of (token_address, max_amount) tuples. Amounts use 6 decimals.

The 1000000000 limit = 1,000.000000 AlphaUSD.

What access keys can and cannot do

Access keys are intentionally restricted:

Can do:

  • Send TIP-20 transfers (subject to spending limits)
  • Call smart contracts
  • Sign any transaction that doesn't touch the keychain

Cannot do:

  • Authorize new keys (only root key)
  • Revoke keys (only root key)
  • Modify their own spending limits
  • Call mutable functions on the Account Keychain precompile

Spending limit rules:

  • Limits apply to transfer(), transferWithMemo(), approve(), and startReward() only when msg.sender == tx.origin (the access key is the direct caller)
  • transferFrom() bypasses limits (delegated transfers are controlled by allowances, not key limits)
  • Contract-internal token movements are not counted against the limit

This means an access key with a 1,000 USDC limit can spend up to 1,000 USDC through direct transfers, but a contract it calls could move additional tokens if it holds an allowance.

Revoke an access key

Revocation is immediate and permanent. Once revoked, a key ID can never be reused for that account. This prevents replay attacks.

cast send 0xAAAAAAAA00000000000000000000000000000000 \
  "revokeKey(address)" \
  $ACCESS_KEY_ADDRESS \
  --rpc-url https://rpc.moderato.tempo.xyz \
  --private-key $ROOT_PRIVATE_KEY

Update spending limits

Increase or decrease a key's spending limit for a specific token without revoking and re-authorizing:

# Increase AlphaUSD limit to 5,000
cast send 0xAAAAAAAA00000000000000000000000000000000 \
  "updateSpendingLimit(address,address,uint256)" \
  $ACCESS_KEY_ADDRESS \
  $ALPHA_USD \
  5000000000 \
  --rpc-url https://rpc.moderato.tempo.xyz \
  --private-key $ROOT_PRIVATE_KEY

Query access keys with the Transfa Data API

Instead of calling the keychain precompile directly, use the Transfa Data API to list access keys for any account:

curl -X GET "https://api.transfa.com/v1/accounts/0xYOUR_ADDRESS/access-keys?chain=moderato" \
  -H "x-api-key: YOUR_API_KEY"

Get details for a specific key:

curl -X GET "https://api.transfa.com/v1/accounts/0xYOUR_ADDRESS/access-keys/0xKEY_ID?chain=moderato" \
  -H "x-api-key: YOUR_API_KEY"

This returns the key's signatureType, expiry, enforceLimits, and isRevoked status, all indexed and queryable without RPC calls.

Check remaining spending limits

Query how much budget an access key has left:

cast call 0xAAAAAAAA00000000000000000000000000000000 \
  "getRemainingLimit(address,address,address)(uint256)" \
  $ACCOUNT_ADDRESS \
  $ACCESS_KEY_ADDRESS \
  $ALPHA_USD \
  --rpc-url https://rpc.moderato.tempo.xyz

Session key patterns

Time-limited session for a web app

A user authenticates with their passkey, and your app provisions a browser-local session key:

import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
 
// Generate an ephemeral key in the browser
const sessionKey = generatePrivateKey();
const sessionAccount = privateKeyToAccount(sessionKey);
 
// Root key authorizes session key (done once per session)
const expiry = Math.floor(Date.now() / 1000) + 3600; // 1 hour
 
await walletClient.sendTransaction({
  to: "0xAAAAAAAA00000000000000000000000000000000",
  data: encodeFunctionData({
    abi: keychainAbi,
    functionName: "authorizeKey",
    args: [
      sessionAccount.address,
      0, // secp256k1
      BigInt(expiry),
      true, // enforce limits
      [
        {
          token: "0x20c0000000000000000000000000000000000001",
          amount: parseUnits("500", 6), // 500 AlphaUSD max
        },
      ],
    ],
  }),
});
 
// Session key can now sign transactions directly
// No further passkey prompts needed for 1 hour / 500 AlphaUSD

Automated payment agent

A backend service that makes periodic payments, constrained to a daily budget:

# Authorize a server key with daily limits
EXPIRY=$(($(date +%s) + 86400))
 
cast send 0xAAAAAAAA00000000000000000000000000000000 \
  "authorizeKey(address,uint8,uint64,bool,(address,uint256)[])" \
  $SERVER_KEY_ADDRESS \
  0 \
  $EXPIRY \
  true \
  "[(0x20c0000000000000000000000000000000000001,10000000000)]" \
  --rpc-url https://rpc.moderato.tempo.xyz \
  --private-key $ROOT_PRIVATE_KEY

The server can process up to 10,000 AlphaUSD in payments for 24 hours. After that, the root key must re-authorize it.

Comparison with smart contract wallets

Tempo Account KeychainEIP-4337 smart contract wallets
Account typeEOA with extended capabilitiesSmart contract
Key managementProtocol precompileOn-chain contract logic
Spending limitsProtocol-enforced per tokenContract-enforced (custom logic)
New signature typesRequires network upgradeDeploy new contract
Gas overhead~30,000 gas for key authorizationHigher (contract deployment + execution)
FlexibilityFixed constraint modelArbitrary logic
CompatibilityStandard EOA address everywhereMay not work with all protocols

Tempo's approach trades flexibility for simplicity. You get scoped keys with spending limits and expiry out of the box, but you can't implement arbitrary access control logic (e.g., "only allow transfers to whitelisted addresses" or "require 2-of-3 multisig"). For most payment use cases, the fixed constraint model is sufficient.

What to read next