Errors
Every error thrown by OxGasServerWallet is a subclass of OxGasError. The class tells you the failure category; the fields on the instance tell you how to recover.
import { OxGasError, WalletNotFoundError, PolicyViolationError, ServerSigningError, NetworkError,} from '@0xgasless/core';The base class
Section titled “The base class”class OxGasError extends Error { readonly code: string; // stable machine-readable code readonly name: string; // class name — used by `instanceof`}Every subclass sets a unique code and name. Match on instanceof in your code, and log the code field for analytics.
WalletNotFoundError
Section titled “WalletNotFoundError”| Field | Value |
|---|---|
code | "WALLET_NOT_FOUND" |
userId | The userId you tried to sign for |
Thrown when you call signTransaction(userId, ...) for a user whose wallet was never created.
Why it happens: you skipped the getOrCreateWallet / getAddress call. The platform never creates wallets implicitly on signing — explicit setup is required.
Recovery:
try { await wallet.signTransaction(userId, params);} catch (err) { if (err instanceof WalletNotFoundError) { await wallet.getOrCreateWallet(err.userId); // retry the sign return wallet.signTransaction(userId, params); } throw err;}In practice you avoid this by always calling getOrCreateWallet or getAddress on every interaction, since both are idempotent and cheap.
PolicyViolationError
Section titled “PolicyViolationError”| Field | Value |
|---|---|
code | "POLICY_VIOLATION" |
reason | Human-readable reason returned by the policy engine |
Thrown when the dashboard’s policy engine blocks the transaction. The reason string is safe to show to your end user — it’s written for that purpose.
Common reasons:
Per-transaction cap exceeded: requested 5 USDC, limit 1 USDCRecipient not on allow-listDaily spend cap exhausted (10 USDC used / 10 USDC limit)Transactions outside allowed time window (09:00–17:00 UTC)
Recovery: there’s nothing the SDK can do — the platform refused to sign. Show err.reason to the user, log the violation, and surface a way to adjust the policy.
try { const signed = await wallet.signTransaction(userId, params); // ...} catch (err) { if (err instanceof PolicyViolationError) { await bot.reply(`Blocked: ${err.reason}`); return; } throw err;}For bots and trading systems, a policy violation isn’t a “retry-with-backoff” condition. Treat it as a hard stop and pause the workflow until the user (or you) raises the cap or updates the allow-list in the dashboard.
ServerSigningError
Section titled “ServerSigningError”| Field | Value |
|---|---|
code | "SERVER_SIGNING_ERROR" |
Thrown when signing fails for a reason other than a policy violation. Usually one of:
- Malformed transaction parameters (bad
to, non-numericvalue). - Transient KMS error inside the platform.
- The wallet exists but is in an unexpected internal state.
Recovery: validate input client-side, then retry once. If it still fails, log and bail — repeated ServerSigningErrors indicate something unrecoverable.
async function trySign(userId: string, params: SignTransactionParams) { try { return await wallet.signTransaction(userId, params); } catch (err) { if (err instanceof ServerSigningError) { // single retry — KMS can be briefly flaky return await wallet.signTransaction(userId, params); } throw err; }}Don’t retry more than once. If the second attempt fails, the cause is almost certainly in the request itself, and looping just wastes API calls.
NetworkError
Section titled “NetworkError”| Field | Value |
|---|---|
code | "NETWORK_ERROR" |
statusCode | HTTP status code, if available |
Thrown when:
- The platform API is unreachable (DNS, TCP, TLS failure).
- The request timed out (default
timeout: 30000ms). - The platform returned a non-2xx status that doesn’t map to one of the more specific errors above.
- During
broadcastTransaction: the RPC endpoint returned an error.
Recovery: exponential backoff is the standard pattern. Most transient network failures resolve within a few seconds.
async function withRetry<T>(fn: () => Promise<T>, max = 3): Promise<T> { for (let i = 0; i < max; i++) { try { return await fn(); } catch (err) { if (!(err instanceof NetworkError) || i === max - 1) throw err; await new Promise((r) => setTimeout(r, 2 ** i * 500)); } } throw new Error('unreachable');}
const result = await withRetry(() => wallet.signTransaction(userId, params),);The 5xx statusCodes mean “the platform had a problem” — safe to retry. 4xx codes other than 403/404 (which already map to specific errors) are bugs in your code or your auth — don’t retry.
A complete error handler
Section titled “A complete error handler”This pattern covers every error class and works for any SDK call.
import { WalletNotFoundError, PolicyViolationError, ServerSigningError, NetworkError,} from '@0xgasless/core';
async function safeSign( userId: string, params: SignTransactionParams,) { try { return await wallet.signTransaction(userId, params); } catch (err) { if (err instanceof WalletNotFoundError) { await wallet.getOrCreateWallet(userId); return wallet.signTransaction(userId, params); } if (err instanceof PolicyViolationError) { throw new BlockedTransaction(err.reason); // your own typed error } if (err instanceof ServerSigningError) { return wallet.signTransaction(userId, params); // one retry } if (err instanceof NetworkError) { if (err.statusCode && err.statusCode < 500) throw err; // 4xx — don't retry return wallet.signTransaction(userId, params); // 5xx or timeout — retry once } throw err; // unknown — bubble up }}Each branch reflects a deliberate choice: implicit wallet creation, user-facing policy block, single retry on a transient signing error, and conservative retry on transient network errors. Adjust the policy on each branch to your reliability needs, but keep the instanceof discipline — every error class has a different recovery story, and string-matching error messages is fragile.
What’s next
Section titled “What’s next”- Security model — what the platform sees, what it doesn’t.