Skip to content

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';
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.

FieldValue
code"WALLET_NOT_FOUND"
userIdThe 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.

FieldValue
code"POLICY_VIOLATION"
reasonHuman-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 USDC
  • Recipient not on allow-list
  • Daily 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.

FieldValue
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-numeric value).
  • 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.

FieldValue
code"NETWORK_ERROR"
statusCodeHTTP 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.

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.