Skip to main content

Error Handling

Every SDK error is a NoxClientError with a machine-readable code and an optional cause.

import { NoxClientError, NoxClientErrorCode } from "@hisoka-io/nox-client";

try {
await client.rpcCall("eth_blockNumber", []);
} catch (err) {
if (err instanceof NoxClientError) {
console.error(`[${err.code}] ${err.message}`);
if (err.cause) console.error("Cause:", err.cause);
}
}

Error codes

CodeWhen it happens
TOPOLOGY_FETCH_FAILEDCan't reach any seed node, or the response is malformed
TOPOLOGY_VERIFICATION_FAILEDTopology fingerprint doesn't match the on-chain registry
NO_NODES_AVAILABLEThe topology has no nodes, or not enough nodes to build a 3-hop route
PACKET_BUILD_FAILEDSphinx packet encoding failed (usually a WASM issue)
TRANSPORT_FAILEDNetwork error sending/receiving, or the RPC returned an error
RESPONSE_TIMEOUTRequest exceeded timeoutMs without a complete response
DECRYPTION_FAILEDSURB response couldn't be decrypted or decoded
WASM_NOT_INITIALIZEDWASM module didn't load before a crypto operation was attempted
INVALID_CONFIGBad config parameter (e.g., to address isn't 20 bytes)

Common failure modes

Connection failures

try {
const client = await NoxClient.connect();
} catch (err) {
if (err instanceof NoxClientError) {
switch (err.code) {
case NoxClientErrorCode.TopologyFetchFailed:
// Seed nodes unreachable - network issue or all seeds down
break;
case NoxClientErrorCode.TopologyVerificationFailed:
// On-chain fingerprint mismatch - possible tampering
break;
case NoxClientErrorCode.NoNodesAvailable:
// Topology fetched but empty or insufficient nodes
break;
case NoxClientErrorCode.WasmNotInitialized:
// WASM failed to load - bundler issue or missing wasm file
break;
}
}
}

Request failures

try {
await client.rpcCall("eth_getBalance", [addr, "latest"]);
} catch (err) {
if (err instanceof NoxClientError) {
switch (err.code) {
case NoxClientErrorCode.ResponseTimeout:
// Request took too long - increase timeoutMs or check network
break;
case NoxClientErrorCode.TransportFailed:
// Entry node unreachable, or exit node returned an RPC error
// Check err.cause for the underlying error
break;
case NoxClientErrorCode.DecryptionFailed:
// Response fragments couldn't be reassembled
break;
}
}
}

RPC errors

When the exit node successfully contacts the RPC provider but the provider returns an error (invalid params, execution reverted), you get a TRANSPORT_FAILED error. The RPC error details are in err.cause:

try {
await client.rpcCall("eth_call", [{ to: "0xBadAddress", data: "0x" }, "latest"]);
} catch (err) {
if (err instanceof NoxClientError && err.code === NoxClientErrorCode.TransportFailed) {
console.error("RPC error:", err.cause);
}
}

Retry patterns

Some errors are transient and worth retrying. Others indicate a configuration or network problem.

Retryable errors

CodeRetry?Strategy
RESPONSE_TIMEOUTYesRetry with the same or increased timeout
TRANSPORT_FAILEDMaybeRetry if it's a network blip; don't retry RPC errors (reverts, invalid params)
TOPOLOGY_FETCH_FAILEDYesRetry after a delay - seed nodes may be temporarily down
DECRYPTION_FAILEDYesRare, but a different route may succeed

Non-retryable errors

CodeWhy
TOPOLOGY_VERIFICATION_FAILEDThe seed node and on-chain state disagree - investigate before retrying
NO_NODES_AVAILABLEThe network is empty - retrying won't help until nodes come online
INVALID_CONFIGFix the config and reconnect
WASM_NOT_INITIALIZEDFix the WASM loading issue (bundler config, missing file)
PACKET_BUILD_FAILEDUsually a bug - check payload size and WASM state

Simple retry helper

async function withRetry<T>(
fn: () => Promise<T>,
maxAttempts = 3,
delayMs = 1000
): Promise<T> {
for (let i = 0; i < maxAttempts; i++) {
try {
return await fn();
} catch (err) {
if (
err instanceof NoxClientError &&
(err.code === NoxClientErrorCode.ResponseTimeout ||
err.code === NoxClientErrorCode.DecryptionFailed)
) {
if (i < maxAttempts - 1) {
await new Promise((r) => setTimeout(r, delayMs * (i + 1)));
continue;
}
}
throw err;
}
}
throw new Error("unreachable");
}

const block = await withRetry(() => client.blockNumber());