Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Concepts

Keystore

Keystore is a public onchain registry. For every Altana wallet, it stores which keys are currently authorized to act on it. Anyone (any app, any agent, any chain that bridges to it) can read this state and verify authority without a vendor in the middle.

For how the Keystore fits into the wider system, alongside the account contracts and the intent relay, see the Altana architecture overview.

To browse that state without writing any code, use the Keystore Explorer.

Why this matters

Traditional wallet vendors keep their authorization state inside proprietary contracts or backend services. Two agents acting on the same user's wallet can't verify each other unless they're both clients of the same vendor.

Keystore inverts this: authorization is the source of truth, onchain, vendor-neutral.

This unlocks:

  • Cross-agent verification. An agent can verify another agent's authority before delegating work to it.
  • Cross-app authorization. A DEX, lending protocol, or orderbook can check whether a key is allowed to act on a wallet, without integrating with any wallet vendor.
  • One-tx revocation. Pulling a key from Keystore is a single transaction, effective immediately on that chain. Where the key has been mirrored to an L2 cache, the cache needs a post-revocation proof before it agrees.

Writes vs reads

Writes (onchain transactions):
  • Registering a key (admin on first execute; session on grantSession by default, or later via registerSessionKey). Goes through the Controller.
  • Revoking a key (revokeSession). Calls Keystore directly. Gated by access control (onlyKeyOwnerOrValidator); only the wallet itself or a designated validator can revoke, so there is no open write surface. Revocation is monotonic: a revoked key cannot be reactivated.
Reads (eth_call, free, unlimited):
  • Checking which keys are active on a wallet.
  • Verifying whether a given key is currently authorized.
  • Looking up a key's stored public bytes.

Reads are off-chain RPC calls. An agent can verify authorization a million times per second, from any RPC, without paying anything. This is what makes cross-agent and cross-app verification practical at scale.

How a read works

import { createPublicClient, http, keccak256 } from "viem";
import { bsc } from "viem/chains";
import { BNB } from "@altananetwork/sdk";
 
const client = createPublicClient({
  chain: bsc,
  transport: http(BNB.publicRpcUrl),
});
 
const KEYSTORE_ABI = [{
  name: "isValidKey",
  type: "function",
  stateMutability: "view",
  inputs: [
    { name: "user", type: "address" },
    { name: "keyId", type: "bytes32" },
  ],
  outputs: [{ type: "bool" }],
}] as const;
 
// A key's id is the keccak256 hash of its SEC1-encoded public key.
const authorized = await client.readContract({
  address: BNB.keyStore,
  abi: KEYSTORE_ABI,
  functionName: "isValidKey",
  args: [walletAddress, keccak256(sessionPublicKey)],
});

A single eth_call answers: is this key allowed to act on this wallet right now?

isValidKey folds the three questions that matter into one read: the key exists, it has not been revoked, and it has not expired. To list every key on a wallet rather than check one, read getKeys(user), which returns bytes32[].

The two are not interchangeable. Revoking a key removes it from the getKeys list in the same transaction, but expiry does not. An expiry is just a stored timestamp, and no transaction fires when it passes, so a long-expired key still appears in getKeys. Treat getKeys as "keys registered and not revoked" and use isValidKey whenever the answer has to account for expiry.

When the SDK writes to Keystore

SDK callWrite?Notes
createWalletNoWallet is counterfactual until first execute
createPasskeyWalletNoSame as above
First execute on a fresh walletYesAdmin key auto-registered via initialRegisterKey, batched into your userOp
Subsequent execute callsNoJust your calls
grantSessionYes (default)Session public key registered, batched with onchain authorization. register: false skips it; add later with registerSessionKey
revokeSessionYesPulls the session's authority. Revocation is monotonic: once revoked, a key cannot be reactivated.
recoverFromPasskeyNoPure read, two eth_calls

All writes are batched into the same userOp as your actual call. You never call Keystore directly through the SDK.

Reaching other chains

The L1 Keystore is the source of truth. For another chain to honor an authorization, the registry state has to be mirrored to a cache contract on that chain.

A cache works as a verifier: it accepts a storage proof against the L1 Keystore, walks it from the L1 block hash exposed by the L2's L1Block predeploy down to the relevant slot, and stores the result locally. Once cached, the L2 reads authorization with a single eth_call, with no L1 round-trip and no bridge message.

Chains

NetworkChain IDRole
BNB Smart Chain56Standalone Keystore + wallet execution; SDK default
Ethereum1L1 Keystore source of truth for cross-chain proofs + wallet execution
Base8453L2 Keystore cache for cross-chain verification

All three SDK exports are live today: BNB, ETHEREUM, and BASE. BNB and Ethereum support wallet writes. Base is the supported L2 cache/read target for verifying Ethereum-granted sessions cross-chain.

For testnet, the SDK also exports BNB_TESTNET (chain 97) — see Testnet.