Error Handling

Comprehensive guide to error handling in Shade402.

Error Classes

Shade402 defines specific error types for different scenarios:

X402Error

Base error class for all X402 errors:

class X402Error extends Error {
  code: string;
  details: ErrorDetails;
  message: string;
}

PaymentRequiredError

Thrown when payment is required:

class PaymentRequiredError extends X402Error {
  paymentRequest: any;
  code: 'PAYMENT_REQUIRED';
}

PaymentExpiredError

Thrown when payment request has expired:

class PaymentExpiredError extends X402Error {
  paymentRequest: any;
  code: 'PAYMENT_EXPIRED';
}

InsufficientFundsError

Thrown when wallet has insufficient balance:

class InsufficientFundsError extends X402Error {
  requiredAmount: string;
  availableAmount: string;
  code: 'INSUFFICIENT_FUNDS';
}

PaymentVerificationError

Thrown when payment verification fails:

class PaymentVerificationError extends X402Error {
  code: 'PAYMENT_VERIFICATION_FAILED';
}

TransactionBroadcastError

Thrown when transaction broadcast fails:

class TransactionBroadcastError extends X402Error {
  code: 'TRANSACTION_BROADCAST_FAILED';
}

InvalidPaymentRequestError

Thrown when payment request is invalid:

class InvalidPaymentRequestError extends X402Error {
  code: 'INVALID_PAYMENT_REQUEST';
}

Error Codes Reference

import { ERROR_CODES } from '@shade402/core';

console.log(ERROR_CODES.PAYMENT_REQUIRED);
// {
//   code: "PAYMENT_REQUIRED",
//   message: "Payment is required to access this resource",
//   retry: true,
//   user_action: "Ensure wallet has sufficient funds and retry"
// }

Client-side Error Handling

Explicit Client

import {
  PaymentRequiredError,
  PaymentExpiredError,
  InsufficientFundsError,
  PaymentVerificationError,
  TransactionBroadcastError,
} from '@shade402/core';
import { X402Client } from '@shade402/client';

const client = new X402Client(wallet);

try {
  let response = await client.get(url);

  if (client.paymentRequired(response)) {
    const paymentRequest = client.parsePaymentRequest(response);

    if (paymentRequest.isExpired()) {
      throw new PaymentExpiredError(paymentRequest);
    }

    try {
      const authorization = await client.createPayment(paymentRequest);
      response = await client.get(url, { payment: authorization });
    } catch (error) {
      if (error instanceof InsufficientFundsError) {
        console.error('Insufficient funds:', error.requiredAmount);
      } else if (error instanceof PaymentExpiredError) {
        console.error('Payment expired');
      } else if (error instanceof TransactionBroadcastError) {
        console.error('Transaction broadcast failed:', error.message);
      } else {
        throw error;
      }
    }
  }
} catch (error) {
  if (error instanceof X402Error) {
    console.error('X402 error:', error.code, error.message);
  } else {
    console.error('Unexpected error:', error);
  }
} finally {
  await client.close();
}

Automatic Client

import {
  PaymentRequiredError,
  InsufficientFundsError,
  PaymentExpiredError,
} from '@shade402/core';
import { X402AutoClient } from '@shade402/client';

const client = new X402AutoClient(wallet);

try {
  const response = await client.get(url);
} catch (error) {
  if (error instanceof PaymentRequiredError) {
    // Payment required but autoRetry is false
    console.log('Payment required:', error.paymentRequest);
  } else if (error instanceof InsufficientFundsError) {
    console.error('Insufficient funds:', error.requiredAmount);
    console.error('Available:', error.availableAmount);
  } else if (error instanceof PaymentExpiredError) {
    console.error('Payment expired');
  } else {
    console.error('Unexpected error:', error);
  }
} finally {
  await client.close();
}

Server-side Error Handling

Express Middleware

import { x402ErrorMiddleware } from '@shade402/express';

// Add error middleware after all routes
app.use(x402ErrorMiddleware({
  logErrors: true,
  includeStack: process.env.NODE_ENV === 'development',
  onError: (error, req, res) => {
    // Custom error handling
    console.error('Error:', error);
  },
}));

The error middleware automatically:

  • Returns 402 for PaymentRequiredError

  • Returns 410 for PaymentExpiredError

  • Returns 403 for PaymentVerificationError

  • Returns 502 for TransactionBroadcastError

  • Returns 400 for InvalidPaymentRequestError

  • Returns 500 for other errors

Manual Error Handling

import {
  PaymentVerificationError,
  PaymentExpiredError,
} from '@shade402/core';

app.get('/api/data',
  paymentRequired({ amount: '0.01' }),
  async (req, res) => {
    try {
      // Your logic
    } catch (error) {
      if (error instanceof PaymentVerificationError) {
        return res.status(403).json({
          error: 'Payment verification failed',
          code: error.code,
        });
      } else if (error instanceof PaymentExpiredError) {
        return res.status(410).json({
          error: 'Payment expired',
          code: error.code,
        });
      } else {
        return res.status(500).json({
          error: 'Internal server error',
        });
      }
    }
  }
);

Error Response Format

Standard error response format:

{
  "error": "Error message",
  "code": "ERROR_CODE",
  "details": {
    // Additional error details
  }
}

Example Responses

// 402 Payment Required
{
  "error": "Payment is required to access this resource",
  "code": "PAYMENT_REQUIRED",
  "payment_request": {
    "max_amount_required": "0.01",
    // ... payment request details
  }
}

// 403 Payment Verification Failed
{
  "error": "Payment verification failed: transaction not found",
  "code": "PAYMENT_VERIFICATION_FAILED"
}

// 410 Payment Expired
{
  "error": "Payment request has expired",
  "code": "PAYMENT_EXPIRED",
  "payment_request": {
    // ... payment request details
  }
}

Best Practices

  1. Handle all error types: Catch and handle specific error types

  2. Provide helpful messages: Return clear error messages to clients

  3. Log errors: Log errors for debugging and monitoring

  4. Don't expose sensitive info: Don't expose internal details in error messages

  5. Use error codes: Use error codes for programmatic error handling

  6. Retry appropriately: Retry transient errors, not permanent ones

  7. Monitor errors: Set up monitoring and alerts for errors

Error Monitoring

import { x402ErrorMiddleware } from '@shade402/express';

app.use(x402ErrorMiddleware({
  logErrors: true,
  onError: (error, req, res) => {
    // Send to monitoring service
    monitoringService.captureException(error, {
      tags: {
        type: 'x402_error',
        code: error.code,
      },
      extra: {
        path: req.path,
        method: req.method,
      },
    });
  },
}));

Next Steps

Last updated