Error Model
Every failure surfaces as a typed error. There are three the SDK defines, plus one native browser error, and they partition cleanly by layer: transport, contract, and cancellation.
HTTPError
Section titled “HTTPError”Thrown when the server returns a non-2xx response. Network failures (offline,
DNS, CORS) also surface as HTTPError, with status: 0.
class HTTPError extends Error { name = 'HTTPError'; status: number; // HTTP status (0 for network errors) statusText: string; // status text ('Network Error' when status is 0) url: string; // the request URL that failed headers: Headers; // response headers bodyText?: string; // raw response body if available (capped ~64KB) toJSON(): { status; statusText; url; headers; bodyText? };}Common statuses: 401 (not authenticated — the SDK retries via refresh first),
403 (insufficient permissions), 404 (not found), 422 (validation).
AuthRevokedError
Section titled “AuthRevokedError”A subclass of HTTPError — terminal auth failure. Thrown when the node reports
that the refresh token was reused or revoked, meaning refresh can’t
recover. Because it extends HTTPError, instanceof HTTPError still matches;
check it first if you want to distinguish it.
class AuthRevokedError extends HTTPError { name = 'AuthRevokedError'; reason: string; // 'token_reuse' | 'token_revoked'}When this is thrown the SDK has already cleared its tokens (onAuthRevoked →
clearToken). Prompt the user to sign in again.
RpcError
Section titled “RpcError”Thrown by sdk.rpc.execute() when the WASM contract returns a JSON-RPC error.
class RpcError extends Error { name = 'RpcError'; code: number; // JSON-RPC error code type?: string; // optional error type from the contract data?: unknown; // optional structured error data}The inherited message holds the human-readable description from the contract.
Cancellation and timeouts
Section titled “Cancellation and timeouts”A request that exceeds timeoutMs, or one cancelled via an AbortSignal, does
not surface as a native AbortError. Every high-level client routes through
the HTTP client, which catches the underlying DOMException
(AbortError/TimeoutError) and re-throws it as an HTTPError with
status: 0 — the same shape as any other network failure. Match it as an
HTTPError whose status is 0:
try { await sdk.admin.getContexts(); // subject to the client's global timeoutMs} catch (err) { if (err instanceof HTTPError && err.status === 0) { // network failure, timeout, or cancellation }}status: 0 alone doesn’t distinguish a deliberate cancellation from a genuine
network failure — track the AbortController you called .abort() on if you
need to tell them apart. Per-call signal/timeoutMs overrides are a feature of
the low-level HttpClient;
the sdk.admin / sdk.auth / sdk.rpc facades use the single global
timeoutMs you pass to the constructor.
Full handling pattern
Section titled “Full handling pattern”import { HTTPError, AuthRevokedError, RpcError } from '@calimero-network/mero-js';
try { const result = await sdk.rpc.execute({ contextId: 'ctx-id', method: 'get_value', argsJson: { key: 'foo' }, }); return result;} catch (err) { if (err instanceof RpcError) { // Contract-level error — the WASM returned an error object console.error(`Contract error ${err.code}: ${err.message}`, err.data); } else if (err instanceof AuthRevokedError) { // Refresh token reused/revoked — session is over promptSignIn(); } else if (err instanceof HTTPError) { // Transport-level error — HTTP 4xx/5xx, or status 0 for a network // failure, timeout, or cancellation if (err.status === 0) console.warn('Network failure, timeout, or cancelled'); else if (err.status === 403) console.error('Permission denied'); else console.error(`HTTP ${err.status}: ${err.statusText}`); } else { throw err; // something unexpected }}Note on 401s
Section titled “Note on 401s”The SDK handles 401 Unauthorized internally: it triggers a token refresh
and transparently retries the original request (see
the token lifecycle).
Your code only sees a 401 HTTPError if the refresh itself fails (e.g. the
refresh token is also expired). In that case, call sdk.clearToken() and prompt
the user to re-authenticate. If the token was reused or revoked, you get an
AuthRevokedError instead.