Error codes
This page lists the internal error codes Smart Router assigns to upstream failures, so you can look up a code from the router's logs or metrics and see whether it is retried.
Smart Router classifies every upstream failure into an internal error code. The router uses it for logging, metrics, and retry decisions. Each code has a name, a layer, and a retryable flag. The failover state machine reads this flag to decide whether to rotate to another node.
These codes are internal - clients never see them
Smart Router is a transparent hop. Your client always receives the original
error from the upstream node, unchanged: the same JSON-RPC / HTTP / gRPC error you
would get from the node directly. Lava error codes appear only in the router's
logs and in the smartrouter_errors_total
metric. Use this page to read those logs and metrics, not to handle codes on the
wire.
The registry lives in
protocol/common/error_registry.go.
The four layers at a glance
| Layer | Range | Category | Means | Typical retryable? |
|---|---|---|---|---|
PROTOCOL_* |
1000s | Internal | The router's own internals: connection, session, consistency. | Mostly yes |
NODE_* |
2000s | External | The node responded with an error (5xx, rate-limit, syncing, method-not-found). | Mixed |
CHAIN_* |
3000s | External | The chain's execution/state layer (revert, nonce, block-not-found). | Mixed |
USER_* |
4000s | External | The client sent a bad request (malformed JSON, invalid params). | No |
General rule: PROTOCOL_* / NODE_* 5xx errors are infrastructure noise that
failover absorbs. A spike in CHAIN_* or USER_* usually means a client or contract
problem, not a problem with your deployment.
Where you'll see these
In logs (structured fields on every classified failure):
{"level":"warn","error_name":"NODE_RATE_LIMITED","error_category":"external",
"retryable":true,"chain_id":"ETH1","chain_error_code":429,
"chain_error_message":"429 Too Many Requests","provider":"eth-publicnode"}
In metrics (smartrouter_errors_total, labelled by name/category/retryability/chain):
# Which errors are firing, by name?
sum by (error_name) (rate(smartrouter_errors_total[5m]))
# Terminal (non-retryable) errors only - these reach the client
sum by (error_name) (rate(smartrouter_errors_total{retryable="false"}[5m]))
Debugging with error codes
| You see… | Likely cause | Next step |
|---|---|---|
NODE_RATE_LIMITED climbing |
A node is throttling you | Add upstreams, or check your node plan; failover already spreads load. |
PROTOCOL_NO_PROVIDERS |
No node was left in the pool for a relay | Every node was excluded. Check health and the circuit breaker. |
CHAIN_STATE_PRUNED on historical queries |
The request reached a non-archive node | Mark archive upstreams with the archive addon; see config. |
NODE_METHOD_NOT_FOUND |
The node does not expose that method | Terminal and cached. Confirm that the chain spec exposes it. |
USER_* spike |
Clients sending bad requests | Not an infrastructure issue. Inspect the client that sends them. |
CHAIN_EXECUTION_REVERTED |
Contract reverted | Expected for failing eth_call calls and simulations. Terminal, not retried. |
How classification works
The router classifies errors with a two-level lookup that takes the chain family into account:
- Tier 2 - chain-specific matchers run first. They exist only where a chain's
error has different retryability from the generic pattern (e.g. Solana
-32009vs-32007, Bitcoin warmup, Starknet class errors). - Tier 1 - generic semantic matchers are the fallback. They are split by
transport (
jsonrpc/rest/grpc), so an EVM chain never evaluates gRPC matchers, and the reverse.
Connection-level failures (timeouts, refused, reset, context cancellation) are
detected before either tier and always take precedence. Anything unmatched becomes
UNKNOWN_ERROR (code 0, retryable).
Retry, CU, and caching
retryable is the main signal. A retryable: No classification stops retries on
every terminal error, not only on unsupported methods. Two sub-categories have
extra behaviour:
- Unsupported method (
NODE_METHOD_NOT_FOUND,NODE_UNIMPLEMENTED,NODE_ENDPOINT_NOT_FOUND,NODE_METHOD_NOT_ALLOWED) → zero retries, zero CU, response is cached, no node scoring. The node is not called again for it. - Rate limit (
NODE_RATE_LIMITED) → back off without marking the endpoint unhealthy (it is busy, not broken). - User-input errors (
USER_*) are non-retryable but charge normal CU. The response is not cached, because the next request from the same client may be valid, so the node still does real work.
Layer A - Protocol errors (PROTOCOL_*, 1000–1999)
Category: Internal. The Lava protocol itself raises these errors, not nodes or chains.
| Code | Name | Description | Retryable |
|---|---|---|---|
| 1001 | PROTOCOL_CONNECTION_TIMEOUT |
Network operation timed out connecting to node | Yes |
| 1002 | PROTOCOL_CONNECTION_REFUSED |
Node connection refused | Yes |
| 1003 | PROTOCOL_DNS_FAILURE |
DNS resolution failed | Yes |
| 1004 | PROTOCOL_TLS_MISMATCH |
HTTP/HTTPS protocol mismatch | No |
| 1005 | PROTOCOL_CONNECTION_RESET |
Connection reset by peer | Yes |
| 1006 | PROTOCOL_CONNECTION_CLOSED |
Connection closed (EOF) | Yes |
| 1007 | PROTOCOL_CONTEXT_DEADLINE |
Context deadline expired before the relay completed | Yes |
| 1008 | PROTOCOL_CONTEXT_CANCELED |
Request context canceled (client disconnect or relay race resolved) | No |
| 1009 | PROTOCOL_NETWORK_UNREACHABLE |
Network or host unreachable (no route) | Yes |
| 1010 | PROTOCOL_NO_PROVIDERS |
No nodes/pairings available | No |
| 1011 | PROTOCOL_ALL_ENDPOINTS_DISABLED |
All node endpoints disabled | No |
| 1012 | PROTOCOL_PROVIDER_UNAVAILABLE |
Node service unavailable (gRPC UNAVAILABLE) | Yes |
| 1013 | PROTOCOL_PROVIDER_ABORTED |
Node aborted (gRPC ABORTED) | Yes |
| 1014 | PROTOCOL_PROVIDER_DATA_LOSS |
Node data loss (gRPC DATA_LOSS) | Yes |
| 1015 | PROTOCOL_INSUFFICIENT_PROVIDERS |
Insufficient providers available for addon or cross-validation | No |
| 1020 | PROTOCOL_RATE_LIMITED |
Lava-side rate limit exceeded | No |
| 1021 | PROTOCOL_MAX_CU_EXCEEDED |
Maximum compute units exceeded for session | No |
| 1022 | PROTOCOL_BATCH_SIZE_EXCEEDED |
Batch request size exceeded limit | No |
| 1023 | PROTOCOL_CU_MISMATCH |
CU accounting inconsistency or security violation | No |
| 1030 | PROTOCOL_SESSION_NOT_FOUND |
Session does not exist | No |
| 1031 | PROTOCOL_EPOCH_MISMATCH |
Epoch mismatch or too old | No |
| 1032 | PROTOCOL_CONSUMER_BLOCKED |
Consumer is blocklisted | No |
| 1033 | PROTOCOL_CONSUMER_NOT_REGISTERED |
Consumer not registered | No |
| 1034 | PROTOCOL_RELAY_NUMBER_MISMATCH |
Relay number mismatch | No |
| 1035 | PROTOCOL_SESSION_OUT_OF_SYNC |
Session out of sync | No |
| 1036 | PROTOCOL_INVALID_RELAY_REQUEST |
Relay request validation failed (wrong provider/specID/chainID/seen block/content hash) | No |
| 1037 | PROTOCOL_REQUEST_BLOCK_MISMATCH |
Block height mismatch between consumer request and provider state | Yes |
| 1038 | PROTOCOL_SESSION_ACCOUNTING_FAILED |
Session accounting (OnSessionDone/OnSessionFailure) failed | No |
| 1039 | PROTOCOL_SUBSCRIPTION_CLEANUP_FAILED |
Subscription consumer removal (RemoveConsumer) failed | No |
| 1040 | PROTOCOL_FINALIZATION_ERROR |
Node finalization data incorrect | Yes |
| 1041 | PROTOCOL_CONSISTENCY_ERROR |
Response consistency validation failed | Yes |
| 1042 | PROTOCOL_HASH_CONSENSUS_ERROR |
Conflicting response hashes detected | Yes |
| 1043 | PROTOCOL_NO_RESPONSE_TIMEOUT |
Relay race timeout - no node returned within the deadline | Yes |
| 1044 | PROTOCOL_RELAY_PROCESSING_FAILED |
Relay response processing failed on consumer side | Yes |
| 1050 | PROTOCOL_SUBSCRIPTION_NOT_FOUND |
Subscription not found | No |
| 1051 | PROTOCOL_SUBSCRIPTION_INIT_FAILED |
Failed to initialize subscription | No |
| 1052 | PROTOCOL_WEBSOCKET_IDLE_TIMEOUT |
WebSocket idle timeout | No |
| 1053 | PROTOCOL_SUBSCRIPTION_ALREADY_EXISTS |
Subscription already exists for this consumer/key | No |
Layer B - Node errors (NODE_*, 2000–2999)
Category: External. The blockchain node itself returns these errors (they are not execution or state errors). "Standard code" is the protocol-level code that the matcher looks for.
| Code | Name | Description | Retryable | Standard code |
|---|---|---|---|---|
| 2001 | NODE_METHOD_NOT_FOUND |
Method does not exist on this node (unknown to the API surface) | No | JSON-RPC -32601 |
| 2002 | NODE_METHOD_NOT_SUPPORTED |
Method exists but is disabled on this node; retryable on a different node | Yes | JSON-RPC -32004 |
| 2003 | NODE_INTERNAL_ERROR |
Internal node error | Yes | JSON-RPC -32603 |
| 2004 | NODE_SERVER_ERROR |
Generic server error | Yes | JSON-RPC -32000 |
| 2005 | NODE_RATE_LIMITED |
Rate limited by node | Yes | HTTP 429 |
| 2006 | NODE_SERVICE_UNAVAILABLE |
Node temporarily unavailable | Yes | HTTP 503 |
| 2007 | NODE_SYNCING |
Node is syncing/catching up | Yes | message match |
| 2008 | NODE_UNIMPLEMENTED |
gRPC method unimplemented | No | gRPC 12 |
| 2009 | NODE_ENDPOINT_NOT_FOUND |
REST endpoint not found | No | HTTP 404 |
| 2010 | NODE_METHOD_NOT_ALLOWED |
REST method not allowed | No | HTTP 405 |
| 2011 | NODE_LIMIT_EXCEEDED |
Request exceeds node limit (e.g. eth_getLogs range) |
No | JSON-RPC -32005 |
| 2012 | NODE_RESOURCE_NOT_FOUND |
Resource not found at node level | Yes | JSON-RPC -32001 |
| 2013 | NODE_RESOURCE_UNAVAILABLE |
Resource exists but unavailable | Yes | JSON-RPC -32002 |
| 2014 | NODE_GATEWAY_TIMEOUT |
Gateway timeout from node | Yes | HTTP 504 |
| 2015 | NODE_BAD_GATEWAY |
Bad gateway from node | Yes | HTTP 502 |
| 2016 | NODE_UNAUTHORIZED |
Upstream rejected router credentials (HTTP 401) | No | HTTP 401 |
| 2101 | NODE_BITCOIN_WARMUP |
Node still warming up (Bitcoin -28) | Yes | - |
| 2102 | NODE_BITCOIN_INITIAL_DOWNLOAD |
Node in initial block download (Bitcoin -10) | Yes | - |
| 2103 | NODE_BITCOIN_NOT_CONNECTED |
Node has no peers (Bitcoin -9) | Yes | - |
| 2150 | NODE_SOLANA_UNHEALTHY |
Solana node behind/unhealthy (-32005) | Yes | - |
Layer C - Blockchain errors (CHAIN_*, 3000–3999)
Category: External. These errors come from the blockchain execution and state layer: transaction failures, state queries, and similar. Tier-2 (chain-specific) codes exist only where retryability differs from the generic pattern.
Transaction errors (3000–3099)
| Code | Name | Description | Retryable | Chains |
|---|---|---|---|---|
| 3001 | CHAIN_NONCE_TOO_LOW |
Nonce/sequence too low | No | EVM, Cosmos, Starknet, XRP, NEAR |
| 3002 | CHAIN_NONCE_TOO_HIGH |
Nonce too high | No | EVM, XRP |
| 3003 | CHAIN_INSUFFICIENT_FUNDS |
Insufficient funds for transfer/gas | No | Universal |
| 3004 | CHAIN_GAS_TOO_LOW |
Intrinsic gas too low | No | EVM |
| 3005 | CHAIN_GAS_LIMIT_EXCEEDED |
Exceeds block gas limit | No | EVM |
| 3006 | CHAIN_TX_UNDERPRICED |
Transaction gas price too low | No | EVM |
| 3007 | CHAIN_TX_ALREADY_KNOWN |
Transaction already in mempool | No | EVM, Starknet, XRP |
| 3008 | CHAIN_TX_REPLACEMENT_UNDERPRICED |
Replacement tx gas too low | No | EVM |
| 3009 | CHAIN_MEMPOOL_FULL |
Mempool/tx pool is full | No | EVM, Cosmos |
| 3010 | CHAIN_TX_TOO_LARGE |
Transaction exceeds size limit | No | EVM, Solana |
| 3011 | CHAIN_MAX_FEE_BELOW_BASE |
Max fee per gas below base fee | No | EVM (EIP-1559) |
| 3012 | CHAIN_INVALID_SEQUENCE |
Invalid sequence (Cosmos nonce equivalent) | No | Cosmos |
| 3013 | CHAIN_INSUFFICIENT_FEE |
Insufficient fee | No | Cosmos |
| 3014 | CHAIN_TX_REJECTED |
Transaction rejected by network rules | No | Universal |
| 3015 | CHAIN_DOUBLE_SPEND |
Double spend / UTXO already spent | No | Bitcoin/UTXO |
| 3016 | CHAIN_INVALID_SIGNATURE |
Invalid transaction signature | No | Universal |
Execution errors (3100–3199)
| Code | Name | Description | Retryable | Chains |
|---|---|---|---|---|
| 3101 | CHAIN_EXECUTION_REVERTED |
Smart contract execution reverted | No | EVM, Starknet, NEAR, TON |
| 3102 | CHAIN_OUT_OF_GAS |
Out of gas during execution | No | EVM, Cosmos, TON |
| 3103 | CHAIN_STACK_OVERFLOW |
Stack limit reached | No | EVM, TON |
| 3104 | CHAIN_INVALID_OPCODE |
Invalid opcode encountered | No | EVM, TON |
| 3105 | CHAIN_WRITE_PROTECTION |
Write in STATICCALL context | No | EVM |
| 3106 | CHAIN_CONTRACT_SIZE_EXCEEDED |
Contract bytecode exceeds 24KB EIP-170 limit | No | EVM |
| 3107 | CHAIN_ACCOUNT_NOT_FOUND |
Account/contract does not exist | No | Cosmos |
| 3108 | CHAIN_ZKEVM_OUT_OF_COUNTERS |
Polygon zkEVM prover exceeded circuit counter budget | No | EVM (Polygon zkEVM) |
State / data errors (3200–3299)
| Code | Name | Description | Retryable | Chains |
|---|---|---|---|---|
| 3201 | CHAIN_BLOCK_NOT_FOUND |
Block not found | Yes | Universal |
| 3202 | CHAIN_TX_NOT_FOUND |
Transaction not found | Yes | Universal |
| 3203 | CHAIN_RECEIPT_NOT_FOUND |
Transaction receipt not found | Yes | EVM (Cosmos-EVM variant) |
| 3204 | CHAIN_STATE_PRUNED |
State pruned/missing trie node | Yes | EVM |
| 3205 | CHAIN_DATA_NOT_AVAILABLE |
Historical data not available | Yes | Universal |
| 3206 | CHAIN_BLOCK_TOO_OLD |
Block results only for recent blocks | Yes | Cosmos |
| 3207 | CHAIN_LOG_RESPONSE_TOO_LARGE |
Log query returned too many results | No | EVM |
Chain-specific Tier-2 ranges also exist for Solana (3300–3319), Starknet
(3320–3339), Bitcoin/UTXO (3340–3359), and NEAR (3360–3379). See the
registry source
for the full list. Most are non-retryable. The retryable exceptions are the Solana
CHAIN_SOLANA_LEDGER_JUMP, CHAIN_SOLANA_BLOCK_STATUS_UNAVAILABLE, and
CHAIN_SOLANA_MIN_CONTEXT_SLOT_NOT_REACHED; the Starknet CHAIN_STARKNET_BLOCK_NOT_FOUND,
CHAIN_STARKNET_TX_HASH_NOT_FOUND, and CHAIN_STARKNET_UNEXPECTED_ERROR; and the NEAR
block/chunk garbage-collection codes (CHAIN_NEAR_UNKNOWN_BLOCK, CHAIN_NEAR_UNKNOWN_CHUNK,
CHAIN_NEAR_NOT_SYNCED_YET).
Layer D - User errors (USER_*, 4000–4999)
Category: External. These are malformed or invalid client requests. They are classified by the nature of the error, whether Lava catches it before forwarding or the node returns it. All are non-retryable, and all charge normal CU (responses are not cached).
| Code | Name | Description | Retryable | Standard code |
|---|---|---|---|---|
| 4001 | USER_PARSE_ERROR |
Invalid JSON in request | No | JSON-RPC -32700 |
| 4002 | USER_INVALID_REQUEST |
Not a valid JSON-RPC/REST/gRPC object. Also returned by the router, before any node is called, for a JSON-RPC batch carrying an XRP Ledger submit or submit_multisigned (see XRP Ledger submit verdicts) |
No | JSON-RPC -32600 |
| 4003 | USER_INVALID_PARAMS |
Invalid method parameters | No | JSON-RPC -32602 |
| 4004 | USER_INVALID_BLOCK_FORMAT |
Invalid block number format (e.g. non-hex) | No | message match |
| 4005 | USER_INVALID_ADDRESS |
Invalid address format | No | message match |
| 4006 | USER_REQUEST_TOO_LARGE |
Request body exceeds size limit | No | HTTP 413 |
| 4007 | USER_INVALID_HEX |
Invalid hex encoding | No | message match |
XRP Ledger submit verdicts
An XRP Ledger node reports the outcome of a transaction inside an ordinary HTTP 200
result, not in a JSON-RPC error object. So on a submit or submit_multisigned
reply, the router reads result.engine_result (and result.error when the call
itself failed) and classifies both:
| Reply | Treated as | Example codes |
|---|---|---|
tesSUCCESS, terQUEUED |
Success | - |
tec* (applied, fee claimed) |
Success: the transaction's final outcome, not a refusal | - |
tef*, tem*, tel*, other ter* |
Node error | CHAIN_NONCE_TOO_LOW (tefPAST_SEQ), CHAIN_TX_ALREADY_KNOWN (tefALREADY), CHAIN_NONCE_TOO_HIGH (terPRE_SEQ), CHAIN_TX_REJECTED (the rest) |
A catalogued API error beside "status":"error" |
Node error | NODE_SYNCING (noNetwork, notSynced), NODE_SERVICE_UNAVAILABLE (amendmentBlocked), NODE_RATE_LIMITED (tooBusy, slowDown), NODE_INTERNAL_ERROR (internal*), USER_INVALID_PARAMS (invalidTransaction, notSupported, …) |
This matters for writes. A transaction submission is broadcast to every upstream,
and the caller gets the first success. If the router read a rejection as a success,
it would end that broadcast early. A node that already had the transaction through
peer gossip could answer tefPAST_SEQ before the node that applied it answered
tesSUCCESS. The caller would then be told that a validated payment had failed.
These replies carry lava-identified-node-error: true (see
Response headers). A ledger verdict is
non-retryable and is not charged against the answering node's availability,
because the node reported the truth. An API error about the node's own state, such
as noNetwork, is charged against it.
Every other XRPL method keeps the envelope rule: an engine result in any other reply,
such as the one simulate returns, is the answer the caller asked for. A batch that
carries a submit is refused with USER_INVALID_REQUEST before any node is called.
The reason is that a batch is classified by its envelopes alone, so a rejection
inside one would again read as a success.
Chain families
Tier-2 classification is based on a chain's family. An unregistered chain id
resolves to the Unknown sentinel. Tier-2 lookups then miss on purpose, and
classification falls through to the transport-scoped Tier-1 matchers.
| Family | Example chain ids |
|---|---|
EVM |
ETH1, SEP1, HOL1, ARBITRUM, POLYGON, BASE, OPTM, AVAX, BSC, BLAST, FTM250, SONIC, … |
Solana |
SOLANA, SOLANAT, KOII, KOIIT |
Bitcoin |
BTC, BTCT, LTC, LTCT, DOGE, DOGET, BCH, BCHT |
CosmosSDK |
COSMOSHUB, LAVA, LAV1, AXELAR, EVMOS, OSMOSIS, JUN1, CELESTIA, … |
Starknet |
STRK, STRKS |
Aptos |
APT1 |
Sui |
SUIT |
NEAR |
NEAR, NEART |
XRP |
XRP, XRPT |
Stellar |
XLM, XLMT |
TON |
TON, TONT |
Tron |
TRX, TRXT |
Cardano |
CARDANO, CARDANOT |
Unknown |
sentinel - Tier-2 misses, falls through to Tier-1 |