Tempo's Account KeychainA protocol-level precompile that lets root keys provision scoped access keys with spending limits, expiry times, and signature type constraints. Not a smart contract wallet. It extends standard EOAs. 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
- Foundry installed
- A funded wallet on Moderato (see How to Get Started Building on Tempo)
- A Transfa API key (app.transfa.com)
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:
- Signature type: restricts the key to secp256k1, P256 (passkey curve), or WebAuthn signatures
- Expiry: a Unix timestamp after which the key stops working. Checked during transaction validation, not execution.
- 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_KEYParameters:
- keyId: The address of the access key (derived from its public key)
- signatureType:
0= secp256k1,1= P256,2= WebAuthn - expiry: Unix timestamp. Set to
0for 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(), andstartReward()only whenmsg.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_KEYUpdate 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_KEYQuery 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.xyzSession 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 AlphaUSDAutomated 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_KEYThe 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 Keychain | EIP-4337 smart contract wallets | |
|---|---|---|
| Account type | EOA with extended capabilities | Smart contract |
| Key management | Protocol precompile | On-chain contract logic |
| Spending limits | Protocol-enforced per token | Contract-enforced (custom logic) |
| New signature types | Requires network upgrade | Deploy new contract |
| Gas overhead | ~30,000 gas for key authorization | Higher (contract deployment + execution) |
| Flexibility | Fixed constraint model | Arbitrary logic |
| Compatibility | Standard EOA address everywhere | May 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
- Smart Accounts Without Smart Contracts: how Tempo bakes passkeys, spending limits, and fee sponsorship into the protocol
- How to Sponsor Transaction Fees on Tempo: combine session keys with fee sponsorship for a fully gasless UX
- How to Get Started Building on Tempo: set up your development environment
- Transfa Data API reference: query access key state and account activity