Tempo Transactions provide native “smart account” functionality through the Account Keychain precompile, enabling modern authentication methods like passkeys, WebAuthn, and P256 signatures without requiring complex account abstraction infrastructure.
Why Smart Accounts?
Smart accounts on Tempo unlock:
Passkey authentication : Sign transactions with Face ID, Touch ID, or biometric devices
WebAuthn support : Integrate with hardware security keys (YubiKey, etc.)
P256 signatures : Use Secure Enclave and modern cryptographic standards
Access key delegation : Root keys authorize scoped secondary keys with limits
Key rotation : Update credentials without changing account addresses
Multi-device sync : Passkeys sync across devices via cloud keychain
Authentication Types
Tempo supports three signature types:
Type Use Case Description Secp256k1 Traditional wallets Standard Ethereum signatures P256 Mobile devices Secure Enclave, passkeys, biometric auth WebAuthn Hardware keys YubiKey, Titan keys, platform authenticators
Account Keychain Precompile
The Account Keychain is deployed at 0xaAAAaaAA00000000000000000000000000000000 and manages:
Root keys : Primary account controllers (address-derived)
Access keys : Delegated keys with expiry and spending limits
Signature verification : Protocol-level validation of P256/WebAuthn
Spending limits : Per-token caps for delegated keys
Basic Access Key Authorization
Authorize a secondary key with an expiry and optional spending limits:
use alloy :: {
primitives :: { Address , U256 , address},
providers :: ProviderBuilder ,
};
use tempo_alloy :: {
TempoNetwork ,
contracts :: precompiles :: IAccountKeychain ,
};
#[tokio :: main]
async fn main () -> Result <(), Box < dyn std :: error :: Error >> {
let provider = ProviderBuilder :: new_with_network :: < TempoNetwork >()
. connect ( & std :: env :: var ( "RPC_URL" ) ? )
. await ? ;
let keychain = IAccountKeychain :: new (
address! ( "0xaAAAaaAA00000000000000000000000000000000" ),
& provider ,
);
// Generate or derive an access key ID
let access_key_id = address! ( "0x1234567890abcdef1234567890abcdef12345678" );
// Set expiry (30 days from now)
let expiry = std :: time :: SystemTime :: now ()
. duration_since ( std :: time :: UNIX_EPOCH ) ?
. as_secs () + ( 30 * 24 * 60 * 60 );
// Define spending limits for specific tokens
let token_limits = vec! [
IAccountKeychain :: TokenLimit {
token : address! ( "0x20c0000000000000000000000000000000000001" ), // AlphaUSD
amount : U256 :: from ( 1_000_000_000 ), // 1,000 tokens
},
];
// Authorize the key (must be called by root key)
let receipt = keychain
. authorizeKey (
access_key_id ,
IAccountKeychain :: SignatureType :: P256 , // Use P256 for mobile
expiry ,
true , // Enforce spending limits
token_limits ,
)
. send ()
. await ?
. get_receipt ()
. await ? ;
println! ( "Access key authorized: {:?}" , receipt . transaction_hash);
Ok (())
}
Only the root key (address(0) as transaction key) can authorize or revoke access keys.
Using Passkeys (P256)
Integrate passkey authentication for mobile-native experiences:
import { P256Credential } from '@github/webauthn-json' ;
import { create } from '@github/webauthn-json' ;
// Create a passkey for the user
async function createPasskey ( userName : string ) {
const credential = await create ({
publicKey: {
challenge: new Uint8Array ( 32 ), // Random challenge
rp: {
name: 'My Tempo App' ,
id: 'app.example.com'
},
user: {
id: new Uint8Array ( 16 ), // User ID
name: userName ,
displayName: userName
},
pubKeyCredParams: [
{ alg: - 7 , type: 'public-key' } // ES256 (P256)
],
authenticatorSelection: {
authenticatorAttachment: 'platform' , // Use device biometrics
userVerification: 'required'
}
}
});
// Extract the P256 public key
const publicKey = extractPublicKey ( credential );
// Derive key ID from public key
const keyId = deriveKeyId ( publicKey );
// Authorize this key on Tempo
await authorizeP256Key ( keyId );
return { credential , keyId };
}
// Sign a transaction with the passkey
async function signWithPasskey ( transaction : Transaction , credentialId : string ) {
const txHash = hashTransaction ( transaction );
const assertion = await get ({
publicKey: {
challenge: txHash ,
allowCredentials: [{
id: credentialId ,
type: 'public-key'
}],
userVerification: 'required'
}
});
// Extract P256 signature
const signature = extractP256Signature ( assertion );
return signature ;
}
WebAuthn Authentication
Use hardware security keys for maximum security:
use alloy :: primitives :: B256 ;
// WebAuthn signature structure
struct WebAuthnSignature {
authenticator_data : Vec < u8 >,
client_data_json : Vec < u8 >,
challenge_index : u32 ,
type_index : u32 ,
r : B256 ,
s : B256 ,
}
// Authorize a WebAuthn key
let receipt = keychain
. authorizeKey (
webauthn_key_id ,
IAccountKeychain :: SignatureType :: WebAuthn ,
expiry ,
false , // No spending limits for admin key
vec! [],
)
. send ()
. await ?
. get_receipt ()
. await ? ;
Access Key Use Cases
Mobile App Keys Device-specific keys with passkey biometric auth
Session Keys Short-lived keys for temporary access
Spending Limits Delegate payment authority with caps
Employee Access Corporate accounts with scoped permissions
Gaming Keys In-game transaction keys with limits
API Keys Programmatic access with restrictions
Revoking Access Keys
Revoke a compromised or expired key:
let receipt = keychain
. revokeKey ( access_key_id )
. send ()
. await ?
. get_receipt ()
. await ? ;
println! ( "Key revoked: {:?}" , receipt . transaction_hash);
Revoked keys cannot be re-authorized. This prevents replay attacks.
Updating Spending Limits
Adjust limits for an existing access key:
let token_address = address! ( "0x20c0000000000000000000000000000000000001" );
let new_limit = U256 :: from ( 2_000_000_000 ); // Increase to 2,000 tokens
let receipt = keychain
. updateSpendingLimit ( access_key_id , token_address , new_limit )
. send ()
. await ?
. get_receipt ()
. await ? ;
Check the status and limits of an access key:
// Get key info
let key_info = keychain . getKey ( account_address , access_key_id ) . call () . await ? ;
println! ( "Signature type: {:?}" , key_info . signatureType);
println! ( "Expiry: {}" , key_info . expiry);
println! ( "Enforce limits: {}" , key_info . enforceLimits);
println! ( "Is revoked: {}" , key_info . isRevoked);
// Check remaining spending limit
let remaining = keychain
. getRemainingLimit ( account_address , access_key_id , token_address )
. call ()
. await ? ;
println! ( "Remaining limit: {} tokens" , remaining );
Multi-Device Sync
Passkeys automatically sync across devices via cloud keychain:
// User creates passkey on iPhone
const credential = await createPasskey ( '[email protected] ' );
// Passkey syncs to iPad, Mac via iCloud Keychain
// User can sign transactions from any device
// Sign on different device
const signature = await signWithPasskey ( transaction , credential . id );
Security Best Practices
Always set reasonable expiry times for access keys. Short-lived keys reduce exposure.
Enable limits for non-admin keys to cap potential damage from compromise.
Track which keys sign which transactions. Alert on suspicious patterns.
Immediately revoke keys if a device is lost or compromised.
Leverage Secure Enclave on iOS/Android for hardware-backed keys.
Key Rotation Pattern
Rotate keys periodically for security:
// 1. Authorize new key
let new_key_id = generate_new_key ();
keychain
. authorizeKey (
new_key_id ,
SignatureType :: P256 ,
expiry ,
true ,
limits . clone (),
)
. send ()
. await ? ;
// 2. Test new key
let test_tx = create_test_transaction ();
let signature = sign_with_key ( test_tx , new_key_id ) . await ? ;
assert! ( verify_signature ( signature ));
// 3. Revoke old key
keychain . revokeKey ( old_key_id ) . send () . await ? ;
// 4. Update application to use new key
update_stored_key_id ( new_key_id );
Integration Examples
Mobile Wallet with Passkeys
class TempoWallet {
private keyId : string ;
private credentialId : string ;
async initialize ( userName : string ) {
// Create passkey
const { credential , keyId } = await createPasskey ( userName );
this . keyId = keyId ;
this . credentialId = credential . id ;
// Authorize on Tempo
await this . authorizeKey ();
}
async sendPayment ( to : string , amount : bigint ) {
// Build transaction
const tx = buildTransaction ({ to , amount });
// Sign with passkey (Face ID / Touch ID)
const signature = await signWithPasskey ( tx , this . credentialId );
// Send signed transaction
return await sendTransaction ( tx , signature );
}
private async authorizeKey () {
const expiry = Date . now () / 1000 + ( 365 * 24 * 60 * 60 ); // 1 year
const limits = [{
token: ALPHA_USD_ADDRESS ,
amount: parseUnits ( '10000' , 6 )
}];
return await keychain . authorizeKey (
this . keyId ,
SignatureType . P256 ,
expiry ,
true ,
limits
);
}
}
Enterprise Multi-User Access
struct EnterpriseAccount {
root_key : PrivateKeySigner ,
employee_keys : HashMap < String , AccessKeyConfig >,
}
struct AccessKeyConfig {
key_id : Address ,
role : Role ,
limits : Vec < TokenLimit >,
expiry : u64 ,
}
enum Role {
Admin ,
Approver { daily_limit : U256 },
Operator { per_tx_limit : U256 },
}
impl EnterpriseAccount {
async fn add_employee (
& mut self ,
name : String ,
role : Role ,
) -> Result < Address > {
let ( key_id , limits , expiry ) = match role {
Role :: Admin => (
generate_key (),
vec! [], // No limits
u64 :: MAX , // Never expires
),
Role :: Approver { daily_limit } => (
generate_key (),
vec! [ TokenLimit { token : ALPHA_USD , amount : daily_limit }],
now () + 90_DAYS,
),
Role :: Operator { per_tx_limit } => (
generate_key (),
vec! [ TokenLimit { token : ALPHA_USD , amount : per_tx_limit }],
now () + 30_DAYS,
),
};
self . keychain
. authorizeKey ( key_id , SignatureType :: P256 , expiry , true , limits )
. send ()
. await ? ;
self . employee_keys . insert ( name , AccessKeyConfig {
key_id ,
role ,
limits : limits . clone (),
expiry ,
});
Ok ( key_id )
}
}
Next Steps
Fee Sponsorship Combine smart accounts with fee sponsorship
Batch Payments Use access keys to authorize batch operations
Tempo Transaction Learn about Tempo Transaction features
Account Keychain Detailed protocol specification