SDK
Errors
Two different things can go wrong, and they surface differently.
Failures before submission throw. Bad configuration, an unsupported signer, a chain with no relay. These are JavaScript Errors with a message string. Catch them with try/catch.
Failures at or after submission do not throw. execute and revokeSession return an ExecuteResult with status: "FAILED". Nothing is raised. If you only wrap calls in try/catch, you will not notice these.
const result = await client.execute({ wallet, signer, calls });
if (result.status !== "CONFIRMED") {
// Handle it here. No exception was thrown.
}grantSession is the exception: it throws Session grant did not confirm: status=<status> (with the relay code appended when one was observed, e.g. status=FAILED (relay code 300)) rather than returning a failed result, because there is no useful Session object to hand back.
Reading ExecuteResult
type ExecuteResult = {
callsId: Hex;
status: "CONFIRMED" | "FAILED" | "PENDING";
transactionHash?: Hex;
statusCode?: number; // the relay's raw numeric status, when observed
};| Status | Meaning |
|---|---|
CONFIRMED | Included and succeeded. transactionHash is populated. |
FAILED | Terminal. Either the relay rejected the bundle before inclusion, or it reverted on-chain — statusCode tells them apart (see below). |
PENDING | You passed noWait: true, or the SDK polled for four minutes without a terminal answer. |
Relay status codes
The relay answers status polls with a numeric code. The codes follow the EIP-5792 convention (the relay does not publish its own list, so the SDK classifies by band, the same way viem does):
statusCode | Band | What it means |
|---|---|---|
100–199 | still in flight | The SDK keeps polling. |
200–299 | success | Surfaced as CONFIRMED. |
300–499 | rejected before inclusion | Terminal, surfaced as FAILED immediately. Nothing reached the chain. Most common cause of 300: the session's spend cap cannot cover the relay fee (the cap pays fees too — see grantSession), or a relay policy refused the bundle. |
500–699 | failed on-chain | Terminal, surfaced as FAILED. 500 is a revert; 600 a partial failure. |
| anything else | unknown | The SDK keeps polling rather than guessing — FAILED is the "safe to resubmit" signal, and misreading an unknown code could cause a duplicate submission. A timeout then returns PENDING with the odd code in statusCode. |
What FAILED carries
FAILED comes with statusCode when the relay reported one, which splits the diagnosis in two: 3xx/4xx means nothing reached the chain (fee, cap, or relay policy — fix the session or funding and resubmit), while 5xx/6xx means it reverted on-chain. There is still no revert string or receipt on the result; for the on-chain reason, use the table below and the explorer.
If you need the onchain reason, look up the userOp yourself. You have callsId; the wallet address and chain are yours. Search the wallet address on the chain's explorer (BscScan for BNB, Etherscan for Ethereum) and inspect the most recent transaction to the account. The revert reason is in the trace.
Telling the failure classes apart
| What went wrong | How to recognize it | Fix |
|---|---|---|
Policy revert. The session exceeded its spend cap, called a contract outside permissions.calls, or is past expiry. | The session worked before and stopped, or fails only for certain calls or amounts. Read the key onchain with isValidKey; check your spend limits against the token's decimals. | Grant a new session with the right scope. Permissions are fixed at grant and cannot be widened. |
| Wrong decimals in a spend cap. A cap orders of magnitude smaller than intended. | Small payments revert against a limit that reads as generous. Extremely common on BNB Chain, where stablecoins use 18 decimals rather than 6. | See the decimals warning on grantSession. |
| Unfunded counterfactual wallet. The wallet has no native balance to pay for the first transaction. | Happens on the very first execute for a new wallet. createWallet does not touch the chain, so the address exists but holds nothing. | Send native tokens to wallet.address first. See createWallet. |
Session doesn't match the grant. The restored Session's values drifted from what was granted. | Every execute with that session fails, including ones that previously worked. Usually follows a lossy JSON round-trip (bigints turned into numbers, re-cased hex). | Persist with serializeSession and restore with deserializeSession — see grantSession. |
| Relay rejection. The relay refused the bundle before inclusion. | FAILED within seconds with statusCode in the 300–499 band. The most common case is a session spend cap too small to cover the relay fee — the cap pays fees as well as the agent's spending. | Raise the cap (or fix the input) and resubmit; nothing reached the chain. |
A contract paid the wallet native coin with .transfer() / .send(). An Altana wallet is an EOA delegated (EIP-7702) to the account contract, and running that code costs more than the 2300-gas stipend those Solidity primitives forward — so the paying contract reverts, with empty revert data. Known case: Venus core-pool vBNB redeem (its BNB payout is a .transfer()). All ERC-20 payouts are unaffected. | The call works from a plain EOA but reverts from the wallet, and the trace shows an out-of-gas or empty revert inside a native-coin send to the wallet's address. | Nothing wallet-side can add gas to someone else's .transfer(). Use the protocol's wrapped-token path instead (WBNB on Venus), a gateway that pays with a full-gas call (Venus's NativeTokenGateway, where deployed), or receive to a plain EOA. |
Errors the SDK throws
There is no error class and no error code: every one of these is a plain Error, so match on the message if you must branch on them.
Configuration
| Message | Cause |
|---|---|
createClient: at least one chain is required. | Empty chains array. |
createClient: duplicate chainId <id> in chains. | The same chain passed twice. |
createClient: defaultChainId <id> is not one of the configured chains (...) | defaultChainId names a chain you did not configure. |
Chain <id> is not configured on this client. Configured chains: ... | A chainId argument the client does not hold. |
createWallet: at least one network is required. | Empty networks. Also applies to createPasskeyWallet. |
Chain and relay
| Message | Cause |
|---|---|
No Altana relay serves chain <id> (<name>). ... | The chain has no relay and cannot execute. BASE is read-only in this sense: it is an L2 cache target, not an execution chain. |
Balance for <address> did not reach <n> wei within <ms>ms | A funding wait timed out. |
Signers
Passing a signer the SDK cannot use produces a multi-line message naming every supported constructor. SignerType is "privateKey" | "passkey"; a hand-rolled signer object with any other type — including "injected", which browser wallets can never satisfy — is rejected with guidance, e.g. "Injected wallet signers (e.g. MetaMask) need to sign a transaction but the current build of @altananetwork/sdk doesn't accept them as a signer type." Use signerFromPrivateKey, createPrivateKeySigner, createPasskey, or createHeadlessPasskey. For the browser-wallet story — why extension wallets cannot sign for a 7702 account, and the onboarding flow that works instead — see Onboard users from browser wallets.
Cross-chain sync
| Message | Cause |
|---|---|
syncKeyToL2: tx reverted (L1Block anchor moved); refresh to retry | The L1 anchor advanced between proof construction and inclusion. Call again. |
ensureKeyCached: L2 did not anchor past L1 block <n> within <ms>ms | Base did not catch up within 5 minutes. Base normally anchors 1 to 3 minutes behind L1. |
L2 L1Block predeploy reports zero hash — chain not anchored yet | The L2 has no anchored L1 block. |
l2WalletClient has no account configured / ... no chain configured | The L2 wallet client is missing an account or chain. |
Recovery
recoverFromPasskey needs at least one active key in Keystore, which means the wallet must have executed at least once. A wallet that was created but never used has nothing to recover from.
Related
- execute for the call itself
- Sessions for what makes a session valid
- createWallet for counterfactual wallets and funding