Rail Failures
Diagnosing rail failures
All rail errors surface as PQSafeError with a rail field indicating which adapter failed:
import { PQSafeError } from '@pqsafe/agent-pay'
try { await executeAgentPayment(signed, request)} catch (err) { if (err instanceof PQSafeError) { console.log('Code:', err.code) console.log('Rail:', err.rail) // 'airwallex' | 'wise' | 'stripe' | 'usdc-base' | 'x402' console.log('Details:', err.details) // Raw rail API error }}Rail-specific failure modes
Airwallex
| Error | Cause | Fix |
|---|---|---|
RAIL_TIMEOUT | Airwallex API slow or down | Retry — Airwallex SLA is 99.9% |
INSUFFICIENT_BALANCE | Source account balance low | Top up via Airwallex Dashboard |
INVALID_BENEFICIARY | Bank account details rejected | Verify IBAN/account number format |
FX_RATE_EXPIRED | FX quote expired (>30s) | Retry — SDK will re-fetch rate |
COMPLIANCE_HOLD | Transaction flagged for review | Contact Airwallex compliance team |
Wise
| Error | Cause | Fix |
|---|---|---|
RECIPIENT_REJECTED | Wise rejected the recipient account | Verify bank details; use Wise’s validation API |
TRANSFER_LIMIT_EXCEEDED | Over daily/monthly limit | Break into smaller transfers or contact Wise |
PROFILE_NOT_VERIFIED | Wise profile verification incomplete | Complete KYB in Wise Dashboard |
Stripe
| Error | Cause | Fix |
|---|---|---|
CARD_DECLINED | Card network declined the charge | Check card expiry, limit, 3DS requirements |
PAYMENT_METHOD_NOT_FOUND | pm_... ID invalid | Re-confirm payment method ID from Stripe Dashboard |
STRIPE_WEBHOOK_FAILED | Webhook secret mismatch | Verify STRIPE_WEBHOOK_SECRET env var |
USDC on Base
| Error | Cause | Fix |
|---|---|---|
TRANSACTION_REVERTED | USDC transfer reverted on-chain | Check USDC balance and token allowance |
GAS_ESTIMATION_FAILED | Base RPC returned error | Check BASE_RPC_URL is reachable |
NONCE_TOO_LOW | Transaction nonce conflict | Wait for pending transactions to clear |
RECIPIENT_REJECTED | Invalid 0x address | Verify address is checksummed and on Base |
x402
| Error | Cause | Fix |
|---|---|---|
X402_REQUIREMENTS_CHANGED | Server changed payment requirements mid-flow | Re-probe with probeX402Endpoint() |
FACILITATOR_UNREACHABLE | x402.org/facilitator down | Check X402_FACILITATOR_URL or use self-hosted facilitator |
Global retry strategy
import { executeAgentPayment, PQSafeError } from '@pqsafe/agent-pay'
const RETRYABLE_CODES = new Set([ 'RAIL_TIMEOUT', 'FX_RATE_EXPIRED', 'NONCE_TOO_LOW',])
async function executeWithRetry(signed: any, request: any, maxAttempts = 3) { for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await executeAgentPayment(signed, request) } catch (err) { if (err instanceof PQSafeError && RETRYABLE_CODES.has(err.code) && attempt < maxAttempts) { const delayMs = Math.pow(2, attempt - 1) * 1000 // 1s, 2s, 4s await new Promise(r => setTimeout(r, delayMs)) continue } throw err } }}