For the complete documentation index, see llms.txt. This page is also available as Markdown.

JSON-RPC Errors

Errors returned by the Pod RPC server follow the standard JSON-RPC 2.0 error format. In addition to the standard codes (-32600, -32602, -32603, etc.), Pod defines a small set of domain-specific codes for transaction validation, execution reverts, and account recovery.

Error response format

Every error is returned as a JSON-RPC 2.0 error object:

{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32000,
    "message": "transaction validation failed",
    "data": "Insufficient balance"
  }
}
Field
Type
Description

code

integer

Numeric error code (see below).

message

string

Short, human-readable description of the error category.

data

string / object / array (optional)

Extra context. Its shape depends on the error code — see each entry below.

Pod error codes

Code
Message
Meaning

3

execution reverted

A contract-level (enshrined application) execution failed. data is an ABI-encoded revert.

-32000

transaction validation failed

A protocol-level validation check failed (nonce, balance, chain ID, gas price, …).

-32003

Transaction rejected: …

A quorum of validators rejected the transaction.

999

Account locked

The account is locked pending recovery. data carries the recovery target.

997

Empty transaction required to make progress

From pod_sendRawTransaction: the account's head nonce will not finalize on its own. data carries the nonce to unblock.

-32002

Chain has no block yet

eth_blockNumber before the first block exists.

-32025

not available in your region

eth_sendRawTransaction / pod_sendRawTransaction from a geo-restricted source.

-32020 … -32023

(see below)

A websocket subscription was closed by the server. Delivered as a notification, not a response — see Subscription close notifications.

3 — execution reverted

Returned when an enshrined application (the order book, the bridge, or minting) rejects a transaction at execution time. The response mirrors the EIP-1474 / geth-style execution revert so that standard wallets and libraries (viem, ethers, alloy) decode the reason automatically.

data is a hex string: the 4-byte Error(string) selector (0x08c379a0) followed by the ABI-encoded reason string.

The decoded reason string is one of the contract-level validation messages below (e.g. CLOB validation failed: …).

-32000 — transaction validation failed

Returned when a protocol-level check fails before the transaction is attested. data is a plain string holding the specific reason — one of the validation messages below.

-32003 — transaction rejected

Returned when at least f + 1 validators reject a transaction (the rejection quorum). The message summarizes the distinct reasons; data is an array of objects, one per rejection.

999 — account locked

Returned when you submit a transaction for an account that is locked due to a pending recovery. data identifies the recovery target — see Recover a locked account.

data field

Type

Description

recovery_target

hash

Transaction hash of the recovery target.

recovery_target_nonce

number

Nonce of the recovery target.

997 — empty transaction required

Returned by pod_sendRawTransaction when the account's head nonce has no certificate and will not finalize on its own. Submit a deadline-free empty self-transfer (to = your own address, value 0, no calldata) at data.nonce; once that finalizes (or the head becomes recoverable), resubmit.

data field

Type

Description

nonce

number

The head nonce to submit the empty self-transfer at.

errors

string[]

Any validator rejections observed while waiting for attestations.

Subscription close notifications

The codes above answer a request. A websocket subscription created with eth_subscribe can also be ended by the server, and that arrives as a notification rather than a response: same eth_subscription method as a normal update, but carrying error in place of result.

The subscription is over once this arrives; nothing further is sent for it. The connection itself stays open, so any other subscription on it keeps working.

Code
Message
What happened, and what to do

-32020

subscription lagged behind the tick broadcast

The server dropped ticks before this subscriber read them, rather than serve a stream with a gap in it. Resubscribe with since = resume_since.

-32021

node is shutting down

The node is going away. Reconnect (to another node, if you have one) and resubscribe with since = resume_since.

-32022

subscriber did not accept a notification in time

The connection would not take a notification within the server's send timeout. Resubscribe with since = resume_since once you can keep up.

-32023

failed to serialize a subscription notification

A server-side bug. Do not retry in a loop — an immediate resubscribe will likely reproduce it. Please report it.

data field

Type

Description

resumable

boolean

Whether resubscribing recovers the stream. false only for -32023.

resume_since

number

Solution time (µs) of the newest tick this subscription is square on — every update it owed you was delivered (a tick that matched none of your filters owed nothing, so it counts). Pass it back as since. Absent if it is square on nothing yet; then reuse your original since.

resume_since_book

string

pod_orders_v2 only, and only when the batch it stopped in was partly delivered: the last book you were sent. Pass it back as since_book alongside resume_since. Absent when the batch landed whole.

missed

number

-32020 only: how many ticks the broadcast dropped.

resume_since names a whole tick, because since selects whole ticks. So if the subscription closed midway through one, resuming redelivers that tick in full and you may see a few deltas twice. That is deliberate — a repeated delta is something a client can dedupe, whereas one that was never sent is unrecoverable.

pod_orders_v2 is the exception, because it can resume inside a tick. A batch settles many orderbooks and is delivered as one frame each, so a close there also carries resume_since_book; pass both back and you receive exactly the frames you never got, with nothing replayed. That is the case the two-part cursor exists for — a close midway through a batch is precisely when you cannot tell where you got to, since frames the connection already accepted may not have reached your code yet.

On the delta channels (pod_orderbook, pod_orders, pod_orders_v2, pod_candles, pod_withdrawals, pod_transfers, pod_activity), eth_subscribe rejects a since older than the node's replay buffer, or any since while the buffer is empty, with -32602 since too old; backfill via REST then resubscribe. Backfill the gap over REST, then resubscribe with a recent since.

A close is the only signal that a delta stream lost data — the stream itself never has holes. Treat the absence of updates as an idle market only while the subscription is open.

Client support. Not every websocket client surfaces this frame, so check yours before relying on the reason being readable.

  • alloy (Rust) rejects a subscription notification carrying error and treats it as a connection error: it tears the websocket down and reconnects rather than surfacing the payload. You observe the close as a reconnect.

  • jsonrpsee (Rust) removes the subscription and discards the payload, so the stream ends with no reason attached.

  • The pod TypeScript SDK (@pod-network/trade-sdk) delivers it to subscribe's onError as a PodSubscriptionClosedError — code, resumable, missed and resumeSince mapped to properties, with the full payload on raw — and drops the subscription. On a resumable close it has already advanced that subscription's since to resume_since, so sub.resubscribe() is enough. It does not yet support pod_orders_v2, so resume_since_book reaches it only via raw.

With the Rust clients, read the raw websocket frame to act on the reason. The subscription is over regardless of whether your client reports it.

Transaction validation messages

The reason carried in data (for code -32000) or encoded inside the revert (for code 3) is one of the following. Messages with {…} placeholders are filled in with the offending values.

Protocol-level (returned with code -32000)

Message
When it occurs

Invalid chain ID: {chain_id}

The transaction's chain ID does not match the network.

Transaction is blacklisted

The transaction is on the blocklist.

Account has pending transaction

The account already has an unexecuted (pending) transaction.

Future nonce: tx nonce {tx_nonce}, expected {expected}

The nonce is higher than the account's next expected nonce.

Past nonce: tx nonce {tx_nonce}, expected {expected}

The nonce is lower than expected (already used).

Insufficient balance

The account cannot cover the transaction.

Underpriced tx: max_fee_per_gas {max_fee_per_gas} < base fee {base_fee}

maxFeePerGas is below the current base fee.

Recovery validation failed: {reason}

A recovery transaction failed validation.

Contract-level (returned with code 3, inside the revert)

Message
When it occurs

CLOB validation failed: {reason}

Order book transaction validation failed.

Bridge validation failed: {reason}

Bridge / cross-chain operation validation failed.

Mint validation failed: {reason}

Mint operation validation failed.

Standard JSON-RPC codes

Pod also returns the standard codes defined by the JSON-RPC 2.0 specification:

Code
Meaning
Typical cause

-32600

Invalid Request

Malformed request, or an unauthorized call to an admin-only method.

-32602

Invalid Params

Parameters could not be parsed (e.g. a malformed hash or raw transaction), or a subscription since older than the replay buffer.

-32603

Internal Error

An internal server error while servicing the request.

Last updated