Docs/WDK/Error Codes

>_ WDK / REFERENCE

ERROR
CODES.

Stable, machine-readable error codes for every WDK + USDT0 failure mode. Each code tells you exactly what happened and what to do about it.

⌘KCommand-palette first navJump:QuickstartAPIErrorsMigrationSecurity

Error Payload Format

All error responses use this JSON structure. The code field is machine-readable and stable across API versions. The message is human-readable and may change.

json — error response envelope
HTTP/1.1 410 Gone
Content-Type: application/json

{
  "error": {
    "code": "P402_QUOTE_EXPIRED",
    "message": "The quote q_123 expired at 2026-04-16T12:01:00.000Z.",
    "details": {
      "quoteId": "q_123",
      "expiredAt": "2026-04-16T12:01:00.000Z"
    }
  }
}

HTTP Status Mapping

HTTP StatusMeaningRetry?
400 Bad RequestMalformed payload. Your code is wrong.No — fix the request first.
401 UnauthorizedSignature or authentication failure.No — re-sign with correct parameters.
402 Payment RequiredInsufficient balance.No — top up the wallet first.
409 ConflictReplay detected — nonce already used.No — generate a new nonce.
410 GoneQuote expired.Yes — re-quote and resubmit.
422 UnprocessablePolicy denied the route.Yes — with a different route.
503 Service UnavailableNo route available or receipt delayed.Yes — with backoff.
504 Gateway TimeoutSettlement not finalized in SLA.Poll receipt endpoint; do not retry settle.

Full Error Reference

P402_QUOTE_EXPIREDHTTP 410
Trigger

The quote TTL (60 seconds) elapsed before the settlement was submitted.

Recovery

Re-request the quote (/api/v1/liquidity/quote) and prompt the user to sign within the TTL. Do not retry the settle call with the expired quoteId.

P402_ROUTE_UNAVAILABLEHTTP 503
Trigger

No viable route exists for the requested asset and constraints at this time.

Recovery

Relax constraints (increase maxFeeBps or maxLatencyMs), or change the sourceAssets order. Retry with exponential backoff. If persistent, fall back to USDC.

P402_POLICY_BLOCKED_ROUTEHTTP 422
Trigger

The policy engine denied the selected route (e.g. route is on a blocked jurisdiction).

Recovery

Select a different route from the quote options that is compliant with your account policy. Do not retry the same routeId.

P402_SIGNATURE_REJECTEDHTTP 401
Trigger

The EIP-712 signature failed verification. Common causes: wrong chainId, wrong contract address, wrong nonce, or malformed signature bytes.

Recovery

Rebuild the authorization object from scratch. Verify chainId matches the token's deployment chain. Re-sign with the WDK adapter and resubmit.

P402_AUTH_INVALIDHTTP 400
Trigger

The authorization payload is structurally malformed (missing fields, wrong types, invalid address format).

Recovery

Check all required fields: from, to, value, validAfter, validBefore, nonce. Ensure nonce is a 0x-prefixed 32-byte hex string.

P402_INSUFFICIENT_BALANCEHTTP 402
Trigger

The wallet does not hold enough of the source asset to cover amount + fees.

Recovery

Check the wallet balance before submitting. Offer a fallback route with a cheaper asset (e.g. try USDC if USDT0 balance is insufficient).

P402_SETTLEMENT_TIMEOUTHTTP 504
Trigger

The on-chain transaction was submitted but not finalized within the SLA window (30 seconds on Base).

Recovery

Poll GET /api/v1/receipts/{receipt_id} with exponential backoff. The transaction may still finalize. If receipt shows settled: true, the payment succeeded.

P402_RECEIPT_UNAVAILABLEHTTP 503
Trigger

Receipt generation is delayed — the transaction settled on-chain but the receipt record is not yet written.

Recovery

Retry GET /api/v1/receipts/{receipt_id} with 2-second intervals for up to 60 seconds. Keep the txHash visible in your UI so users can verify on a block explorer.

P402_REPLAY_DETECTEDHTTP 409
Trigger

The nonce in the authorization has already been used in a prior settlement for this wallet.

Recovery

Generate a new nonce (32 random bytes) and re-sign. Never reuse nonces. Use the receipt scheme instead if you need idempotent retries.

Recommended Retry Strategy

typescript — retry with exponential backoff
const RETRYABLE_CODES = new Set([
  'P402_QUOTE_EXPIRED',
  'P402_ROUTE_UNAVAILABLE',
  'P402_POLICY_BLOCKED_ROUTE',
  'P402_SETTLEMENT_TIMEOUT',
  'P402_RECEIPT_UNAVAILABLE',
]);

async function settleWithRetry(payload: SettlePayload, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const response = await fetch('/api/v1/router/settle', {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });

    if (response.ok) return response.json();

    const error = await response.json();
    const code = error.error?.code;

    if (!RETRYABLE_CODES.has(code) || attempt === maxAttempts) {
      throw new Error(`Settlement failed: ${code}`);
    }

    // For expired quotes: re-quote before retrying
    if (code === 'P402_QUOTE_EXPIRED') {
      const newQuote = await requestQuote(payload);
      payload = { ...payload, quoteId: newQuote.quoteId, routeId: newQuote.routes[0].routeId };
    }

    // Exponential backoff: 1s, 2s, 4s
    await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt - 1)));
  }
}