·Transfa Team

Smart Accounts Without Smart Contracts

How Tempo bakes passkeys, spending limits, and fee sponsorship into the protocol without bundlers, paymasters, or account factories.

deep divearchitecturetempo

Smart accounts on Ethereum today run on EIP-4337. To get passkey login, gas sponsorship, or transaction batching you need a stack of interdependent pieces: an EntryPoint contract, a smart contract wallet implementing validateUserOp, a factory contract to create one per user, a Paymaster to sponsor gas, and a bundler to relay UserOperations onchain.

The cost adds up. Three separate gas fields govern each operation, and a stablecoin transfer through this stack costs around 100,000 gas (roughly double what it'd cost natively) because everything routes through an onchain validation framework before it touches the token contract.

Ethereum's base protocol only knows two account types: EOAs controlled by a single secp256k1 key, and contract accounts. It has no native support for alternative signature schemes, gas sponsorship, or batched calls, so EIP-4337 builds a parallel system of contracts and infrastructure to provide them. Tempo took a different approach: build those features into the transaction type itself. This post covers how that works and what the tradeoffs are.

What payment apps actually need

Tempo's transaction model covers four things: passkey auth so users don't manage keys, fee sponsorship so users don't need to hold gas tokens, scoped delegation so backends can transact without the root key, and atomic batching so multi-step operations don't leave users in a half-baked state.

Arbitrary validation logic, social recovery, and plugin systems all require an onchain execution framework, and that framework is exactly what makes EIP-4337 expensive. Everything Tempo supports at the account level can be expressed as a transaction field validated before EVM execution.

The transaction

Tempo uses a custom EIP-2718 transaction type (0x76). A standard EVM transaction has to, value, and data. A Tempo Transaction looks like this:

pub struct TempoTransaction {
    chain_id: ChainId,
    nonce: u64,
    calls: Vec<Call>,                              // atomic multi-call
    fee_token: Option<Address>,                    // pay fees in any stablecoin
    fee_payer_signature: Option<Signature>,         // gas sponsorship
    valid_before: Option<u64>,                     // execution time window
    valid_after: Option<u64>,
    key_authorization: Option<SignedKeyAuthorization>,  // provision an access key
    // ... nonce key, gas fields, access list, authorization list
}

calls replaces the single call target with a vector executed atomically. fee_payer_signature lets someone else pay for gas. key_authorization provisions a scoped access key inline, in the same transaction that uses it. valid_before and valid_after give you time-bounded execution. The protocol processes all of these before the EVM touches it.

Passkeys at the protocol level

On Ethereum, passkey support (P256 signatures) still requires a smart contract wallet with custom verification logic. RIP-7212 added a P256 precompile that makes the signature check cheaper, but you still need the rest of the 4337 stack: the wallet contract, the factory, the bundler, and the EntryPoint routing overhead.

Tempo's protocol verifies three signature types directly:

  • secp256k1: standard Ethereum ECDSA
  • P256: the curve behind passkeys, Apple Secure Enclave, and Android Keystore
  • WebAuthn: full FIDO2 assertion verification including authenticator data parsing and client data challenge binding

When someone signs with Face ID, the chain validates the authenticator flags, checks the challenge against the transaction hash, computes sha256(authenticatorData || sha256(clientDataJSON)), and verifies the P256 signature. All of this runs before EVM execution starts.

Addresses are derived the same way regardless of key type: keccak256(pubKeyX || pubKeyY)[12:]. This is because if passkey accounts used a different derivation, you'd need factory contracts to deploy each one and counterfactual addressing to reference accounts that don't exist onchain yet. Having the same derivation path means a passkey gives you a deterministic address just like a private key does without requiring a deployment step.

Scoped access keys

Passkeys work well for user-facing auth but poorly for programmatic access. Every transaction triggers a biometric prompt, which makes sense for a conscious "send $500" action but doesn't work well for a payment processor batching transfers, a subscription service charging monthly, or an app doing routine operations in the background.

The Account Keychain precompile (at 0xAAAAAAAA00000000000000000000000000000000) handles this. A root key provisions access keys with three constraints: what signature type the key must use (this prevents algorithm confusion attacks), when it expires (checked during validation, not execution), and how much of each token it can spend. A provisioning call shows these three axes:

keychain.authorizeKey(
    accessKeyAddress,
    SignatureType.Secp256k1,     // constraint 1: signature type
    block.timestamp + 24 hours,  // constraint 2: expiry
    true,
    [TokenLimit(USDC, 1000e6)]   // constraint 3: per-token spending limit
);

Spending limits only kick in when msg.sender == tx.origin, meaning when the access key is directly calling a transfer. If it calls into a DeFi contract and that contract moves its own tokens, those transfers don't count against the budget. Otherwise an access key couldn't swap on a DEX or deposit into a lending protocol without the spending limit blocking the contract's internal token movements. The limit only applies to what the key transfers directly.

Once you revoke a key, that key ID can never be reused for that account. This closes a replay attack vector: without permanence, someone holding an old authorizeKey signature could re-enable a compromised key.

Fee sponsorship as a signature

Fee sponsorship on Ethereum means deploying a Paymaster contract and staking ETH into the EntryPoint. An OtterSec audit in December 2025 found that attackers can exploit the EntryPoint's 10% gas penalty to drain Paymaster deposits, and that post-execution charging failures can leave Paymasters covering transactions they didn't agree to. Tempo replaces all of that with two signatures on one transaction.

The sender signs with a 0x76 domain prefix, leaving the fee token unspecified. The fee payer signs a separate hash with 0x78, committing to the sender's address, the fee token, and the full transaction. The protocol validates both and charges the fee payer. Domain separation means neither signature works in the other's context. Without it, a fee payer's signature could be replayed to authorize transactions they never agreed to sponsor. The fee payer can only pay for the specific transaction the sender already signed.

Inline key provisioning

The key_authorization field lets a root key provision an access key and use it in the same transaction. The root key signs a KeyAuthorization (signature type, expiry, spending limits), and the access key signs the transaction body. The protocol processes the authorization first, then validates the access key against the entry it just registered.

In practice this means a returning user gets a single passkey prompt. That prompt provisions the session key and executes the first operation atomically. Every subsequent action uses the session key without any further prompts, until the key expires or exhausts its budget.

Compare that to EIP-4337, where the equivalent flow takes multiple transactions: deploy or initialize the wallet, register the session key, wait for confirmations. Each step is a separate block and a separate prompt.

Tradeoffs

The supported signature types (secp256k1, P256, WebAuthn) are baked into the protocol. Adding a new one means a network upgrade, not a contract deploy. You can't implement custom validation logic, social recovery, or unusual multisig schemes at the account level the way EIP-4337 lets you with its validateUserOp hook.

Although EIP-4337 is more flexible here, that flexibility carries infrastructure and gas costs. For passkey auth, fee sponsorship, session keys, and batching, the protocol approach covers what most payment apps need. EIP-4337 pushes account features into smart contracts (flexible but expensive) while Tempo pushes them into the transaction format (less flexible but cheap).

For more on how this works, the Account Keychain spec documents the precompile interface and spending limits. The passkey guide covers integration from key registration through session key setup.