Skip to main content

Error Handling & Troubleshooting

Comprehensive guide to handling errors gracefully and debugging common issues with AnySpend to provide the best user experience.

šŸ“Š Order Status Lifecycle

Understanding order states is crucial for proper error handling and user experience.

Order Status Types

Order Status Enum
enum OrderStatus {
  // Initial States
  SCANNING_DEPOSIT_TRANSACTION = "scanning_deposit_transaction",
  WAITING_STRIPE_PAYMENT = "waiting_stripe_payment",
  EXPIRED = "expired",

  // Processing States
  SENDING_TOKEN_FROM_VAULT = "sending_token_from_vault",
  RELAY = "relay",

  // Success States
  EXECUTED = "executed",

  // Failure States
  REFUNDING = "refunding",
  REFUNDED = "refunded",
  FAILURE = "failure",
}

Status Descriptions

StatusDescriptionUser Action Required
scanning_deposit_transactionWaiting for payment confirmationNone - wait for blockchain confirmation
waiting_stripe_paymentProcessing credit card paymentMay need to complete 3D Secure
sending_token_from_vaultSending tokens for swapNone - automatic process
relayCross-chain transaction in progressNone - wait for completion
executedTransaction completed successfullyNone - success!
expiredOrder expired before completionCreate new order
refundingAutomatic refund in progressNone - wait for refund
refundedRefund completedCheck wallet for refunded tokens
failureTransaction failedReview error details, retry

āš ļø Common Error Codes

Payment Errors

Description: User doesn’t have enough tokens for the transactionSolution: Request user to add funds to their wallet or choose a different payment tokenExample:
if (error.message === "INSUFFICIENT_BALANCE") {
  toast.error("Insufficient balance. Please add funds to your wallet.");
  // Optionally redirect to fiat onramp
  openFiatOnramp();
}
Description: Token contract not supported on the target chainSolution: Verify token is supported and provide alternative optionsExample:
if (error.message === "INVALID_TOKEN_ADDRESS") {
  toast.error("This token is not supported. Please choose another.");
  showSupportedTokens();
}
Description: Transaction amount is below the minimum thresholdSolution: Increase transaction amount or inform user of minimum requirementsExample:
if (error.message === "MINIMUM_AMOUNT_NOT_MET") {
  toast.error(`Minimum amount is $${minimumAmount}. Please increase your amount.`);
}
Description: Transaction amount exceeds maximum limitSolution: Reduce amount or split into multiple transactionsExample:
if (error.message === "MAXIMUM_AMOUNT_EXCEEDED") {
  toast.error(`Maximum amount is $${maximumAmount}. Please reduce your amount.`);
}

Network Errors

Description: Price moved beyond acceptable tolerance during executionSolution: Retry with higher slippage tolerance or wait for price stabilityExample:
if (error.message === "SLIPPAGE") {
  toast.warning("Price moved unfavorably. Retrying with adjusted settings...");
  retryWithHigherSlippage();
}
Description: RPC connection issues or blockchain congestionSolution: Retry after delay or switch to alternative RPCExample:
if (error.message === "NETWORK_ERROR") {
  toast.error("Network issue detected. Please check connection and try again.");
  scheduleRetry();
}
Description: Price quote is no longer validSolution: Get fresh quote and retry transactionExample:
if (error.message === "QUOTE_EXPIRED") {
  toast.info("Price quote expired. Getting fresh quote...");
  refreshQuoteAndRetry();
}
Description: Requested blockchain is not supportedSolution: Use supported chains or implement fallbackExample:
if (error.message === "CHAIN_NOT_SUPPORTED") {
  toast.error("This blockchain is not supported. Please choose another.");
  showSupportedChains();
}

Contract Errors

Description: Smart contract execution failedSolution: Check contract parameters and stateExample:
if (error.message === "CONTRACT_CALL_FAILED") {
  toast.error("Contract interaction failed. Please verify parameters.");
  logContractError(error);
}
Description: Gas limit set too low for transactionSolution: Increase gas limit or suggest gas optimizationExample:
if (error.message === "INSUFFICIENT_GAS") {
  toast.error("Transaction requires more gas. Increasing gas limit...");
  retryWithHigherGas();
}
Description: Transaction nonce conflictSolution: Wait for pending transactions to completeExample:
if (error.message === "NONCE_TOO_LOW") {
  toast.info("Please wait for pending transactions to complete.");
  waitAndRetry();
}
Description: Contract reverted the transactionSolution: Check contract state and parametersExample:
if (error.message === "TRANSACTION_REVERTED") {
  toast.error("Transaction was rejected by the contract. Please check requirements.");
  showTransactionDetails();
}

šŸ› ļø Error Handling Patterns

Component-Level Error Handling

Payment Component with Error Handling
import { useAnyspendCreateOrder } from "@b3dotfun/sdk/anyspend";

function PaymentComponent() {
  const [error, setError] = useState<string | null>(null);
  const [retryCount, setRetryCount] = useState(0);

  const { createOrder, isCreatingOrder } = useAnyspendCreateOrder({
    onError: error => {
      console.error("Payment failed:", error);

      // Handle specific errors
      switch (error.message) {
        case "INSUFFICIENT_BALANCE":
          setError("Insufficient balance. Please add funds to your wallet.");
          break;

        case "SLIPPAGE":
          if (retryCount < 3) {
            setError("Price moved unfavorably. Retrying...");
            setTimeout(() => {
              setRetryCount(prev => prev + 1);
              retryPayment();
            }, 2000);
          } else {
            setError("Price too volatile. Please try again later.");
          }
          break;

        case "NETWORK_ERROR":
          setError("Network issue. Please check your connection and try again.");
          break;

        case "QUOTE_EXPIRED":
          setError("Price quote expired. Getting fresh quote...");
          refreshQuote();
          break;

        default:
          setError("Payment failed. Please try again or contact support.");
      }

      // Track errors for monitoring
      analytics.track("payment_error", {
        error: error.message,
        retryCount,
        timestamp: new Date().toISOString(),
      });
    },

    onSuccess: () => {
      setError(null);
      setRetryCount(0);
    },
  });

  return (
    <div className="payment-component">
      {error && (
        <div className="error-banner">
          <span className="error-icon">āš ļø</span>
          <span>{error}</span>
          <button onClick={() => setError(null)}>Dismiss</button>
        </div>
      )}

      <button onClick={handlePayment} disabled={isCreatingOrder}>
        {isCreatingOrder ? "Processing..." : "Pay Now"}
      </button>
    </div>
  );
}

Order Status Monitoring

Order Status Monitor
import { useAnyspendOrderAndTransactions } from "@b3dotfun/sdk/anyspend";

function OrderStatusMonitor({ orderId }: { orderId: string }) {
  const { orderAndTransactions, getOrderAndTransactionsError } = useAnyspendOrderAndTransactions(orderId);

  if (getOrderAndTransactionsError) {
    return (
      <div className="error-state">
        <h3>Unable to load order status</h3>
        <p>Please check your connection and try again.</p>
        <button onClick={() => window.location.reload()}>Retry</button>
      </div>
    );
  }

  if (!orderAndTransactions) {
    return <div>Loading order status...</div>;
  }

  const { order, depositTxs, executeTx, refundTxs } = orderAndTransactions.data;

  const renderStatusMessage = () => {
    switch (order.status) {
      case "scanning_deposit_transaction":
        return (
          <div className="status-pending">
            <div className="spinner" />
            <div>
              <h3>ā³ Waiting for payment confirmation</h3>
              <p>This usually takes 1-2 minutes. Please don't close this window.</p>
              {depositTxs.length > 0 && (
                <a
                  href={getExplorerUrl(depositTxs[0].txHash, depositTxs[0].chainId)}
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  View payment transaction
                </a>
              )}
            </div>
          </div>
        );

      case "relay":
        return (
          <div className="status-processing">
            <div className="spinner" />
            <div>
              <h3>šŸ”„ Processing cross-chain transaction</h3>
              <p>Your payment is being processed. This may take a few minutes.</p>
            </div>
          </div>
        );

      case "executed":
        return (
          <div className="status-success">
            <div className="success-icon">āœ…</div>
            <div>
              <h3>Transaction completed successfully!</h3>
              <p>Your order has been processed.</p>
              {executeTx && (
                <a href={getExplorerUrl(executeTx.txHash, executeTx.chainId)} target="_blank" rel="noopener noreferrer">
                  View transaction
                </a>
              )}
            </div>
          </div>
        );

      case "failure":
      case "obtain_failed":
        return (
          <div className="status-error">
            <div className="error-icon">āŒ</div>
            <div>
              <h3>Transaction failed</h3>
              <p>{order.errorDetails || "An error occurred while processing your order."}</p>
              <div className="error-actions">
                <button onClick={() => createNewOrder()}>Try Again</button>
                <button onClick={() => contactSupport(orderId)}>Contact Support</button>
              </div>
            </div>
          </div>
        );

      case "refunded":
        return (
          <div className="status-refunded">
            <div className="refund-icon">ā†©ļø</div>
            <div>
              <h3>Refund processed</h3>
              <p>Your payment has been refunded automatically.</p>
              {refundTxs.length > 0 && (
                <a
                  href={getExplorerUrl(refundTxs[0].txHash, refundTxs[0].chainId)}
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  View refund transaction
                </a>
              )}
            </div>
          </div>
        );

      case "expired":
        return (
          <div className="status-expired">
            <div className="expired-icon">ā°</div>
            <div>
              <h3>Order expired</h3>
              <p>This order expired before payment was received.</p>
              <button onClick={() => createNewOrder()}>Create New Order</button>
            </div>
          </div>
        );

      default:
        return (
          <div className="status-unknown">
            <div className="spinner" />
            <div>
              <h3>Processing...</h3>
              <p>Order status: {order.status}</p>
            </div>
          </div>
        );
    }
  };

  return (
    <div className="order-status-monitor">
      <div className="order-header">
        <h2>Order #{orderId.slice(0, 8)}</h2>
        <div className="order-meta">
          <span>Created: {new Date(order.createdAt).toLocaleString()}</span>
          <span>Status: {order.status}</span>
        </div>
      </div>

      {renderStatusMessage()}

      {/* Debug information in development */}
      {process.env.NODE_ENV === "development" && (
        <details className="debug-info">
          <summary>Debug Information</summary>
          <pre>{JSON.stringify(order, null, 2)}</pre>
        </details>
      )}
    </div>
  );
}

Global Error Boundary

Error Boundary Component
import React, { Component, ErrorInfo } from "react";

interface Props {
  children: React.ReactNode;
  fallback?: React.ComponentType<{ error: Error; resetError: () => void }>;
}

interface State {
  hasError: boolean;
  error?: Error;
}

class AnySpendErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error("AnySpend Error Boundary caught an error:", error, errorInfo);

    // Report to error tracking service
    if (typeof window !== "undefined") {
      // Example: Sentry.captureException(error, { contexts: { errorInfo } });
    }
  }

  resetError = () => {
    this.setState({ hasError: false, error: undefined });
  };

  render() {
    if (this.state.hasError) {
      const FallbackComponent = this.props.fallback || DefaultErrorFallback;
      return <FallbackComponent error={this.state.error!} resetError={this.resetError} />;
    }

    return this.props.children;
  }
}

function DefaultErrorFallback({ error, resetError }: { error: Error; resetError: () => void }) {
  return (
    <div className="error-fallback">
      <h2>Something went wrong</h2>
      <p>An unexpected error occurred in the payment component.</p>
      <details className="error-details">
        <summary>Error details</summary>
        <pre>{error.message}</pre>
      </details>
      <div className="error-actions">
        <button onClick={resetError}>Try Again</button>
        <button onClick={() => window.location.reload()}>Reload Page</button>
      </div>
    </div>
  );
}

// Usage
function App() {
  return (
    <AnySpendErrorBoundary>
      <AnySpendProvider>
        <YourApp />
      </AnySpendProvider>
    </AnySpendErrorBoundary>
  );
}

šŸ“Š Error Monitoring & Analytics

Error Tracking Setup

Error Analytics
// Track errors for monitoring and improvement
function trackPaymentError(error: Error, context: any) {
  // Analytics tracking
  analytics.track("payment_error", {
    error_message: error.message,
    error_stack: error.stack,
    user_agent: navigator.userAgent,
    timestamp: new Date().toISOString(),
    context,
  });

  // Error reporting service (e.g., Sentry)
  if (window.Sentry) {
    window.Sentry.captureException(error, {
      tags: {
        component: "anyspend",
        error_type: "payment_error",
      },
      extra: context,
    });
  }

  // Custom error logging
  console.error("AnySpend Payment Error:", {
    message: error.message,
    context,
    timestamp: new Date().toISOString(),
  });
}

User-Friendly Error Messages

Error Message Helper
function getErrorMessage(error: Error): { title: string; message: string; action?: string } {
  switch (error.message) {
    case "INSUFFICIENT_BALANCE":
      return {
        title: "Insufficient Balance",
        message: "You don't have enough funds for this transaction.",
        action: "Add funds to your wallet or choose a different payment method.",
      };

    case "SLIPPAGE":
      return {
        title: "Price Changed",
        message: "The price moved while processing your transaction.",
        action: "We'll retry automatically with updated pricing.",
      };

    case "NETWORK_ERROR":
      return {
        title: "Connection Issue",
        message: "Unable to connect to the blockchain network.",
        action: "Please check your internet connection and try again.",
      };

    case "QUOTE_EXPIRED":
      return {
        title: "Quote Expired",
        message: "The price quote is no longer valid.",
        action: "Getting a fresh quote automatically...",
      };

    default:
      return {
        title: "Transaction Failed",
        message: "An unexpected error occurred.",
        action: "Please try again or contact support if the problem persists.",
      };
  }
}

// Usage in component
function ErrorDisplay({ error }: { error: Error }) {
  const errorInfo = getErrorMessage(error);

  return (
    <div className="error-display">
      <h3>{errorInfo.title}</h3>
      <p>{errorInfo.message}</p>
      {errorInfo.action && <p className="error-action">{errorInfo.action}</p>}
    </div>
  );
}

šŸ”§ Debugging Tools

Debug Mode

Debug Helper
// Enable debug mode for detailed logging
function useAnyspendDebug() {
  const isDebug = process.env.NODE_ENV === "development" || localStorage.getItem("anyspend_debug") === "true";

  const log = (message: string, data?: any) => {
    if (isDebug) {
      console.log(`[AnySpend Debug] ${message}`, data);
    }
  };

  const error = (message: string, error?: Error) => {
    if (isDebug) {
      console.error(`[AnySpend Debug] ${message}`, error);
    }
  };

  return { log, error, isDebug };
}

// Usage
function PaymentComponent() {
  const debug = useAnyspendDebug();

  const { createOrder } = useAnyspendCreateOrder({
    onSuccess: data => {
      debug.log("Order created successfully", data);
    },
    onError: error => {
      debug.error("Order creation failed", error);
    },
  });

  // ... rest of component
}

Network Status Checker

Network Status
function useNetworkStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);
  const [networkSpeed, setNetworkSpeed] = useState<"slow" | "fast" | "unknown">("unknown");

  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);

    window.addEventListener("online", handleOnline);
    window.addEventListener("offline", handleOffline);

    // Check network speed
    const connection = (navigator as any).connection;
    if (connection) {
      const speed = connection.effectiveType;
      setNetworkSpeed(speed === "4g" ? "fast" : "slow");
    }

    return () => {
      window.removeEventListener("online", handleOnline);
      window.removeEventListener("offline", handleOffline);
    };
  }, []);

  return { isOnline, networkSpeed };
}

// Usage in component
function PaymentComponent() {
  const { isOnline, networkSpeed } = useNetworkStatus();

  if (!isOnline) {
    return (
      <div className="network-error">
        <h3>No Internet Connection</h3>
        <p>Please check your connection and try again.</p>
      </div>
    );
  }

  if (networkSpeed === "slow") {
    return (
      <div className="network-warning">
        <p>āš ļø Slow network detected. Transactions may take longer than usual.</p>
      </div>
    );
  }

  // ... rest of component
}

šŸ“‹ Best Practices

Keep users informed about what’s happening, especially during longer operations like cross-chain transactions.
// Good: Clear status messages
<div className="status">
  <Spinner />
  <p>Processing your payment... This may take 2-3 minutes.</p>
</div>

// Bad: No feedback during long operations
<div>{isLoading && <Spinner />}</div>
Many errors are transient and can be resolved by retrying the operation.
// Good: Automatic retry with exponential backoff
const retryWithBackoff = (attempt: number) => {
  const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
  setTimeout(() => retry(), delay);
};

// Bad: No retry mechanism
if (error) {
  throw error;
}
Comprehensive error logging helps identify and fix issues quickly.
// Good: Detailed error logging
console.error("Payment failed", {
  error: error.message,
  orderData,
  userAddress,
  timestamp: Date.now(),
});

// Bad: Generic error logging
console.error("Error occurred");
Provide alternative options when primary functionality fails.
// Good: Fallback options
if (crossChainFailed) {
  return <SameChainSwapOption />;
}

// Bad: Complete failure with no alternatives
if (crossChainFailed) {
  throw new Error("Transaction failed");
}

šŸ†˜ Getting Help

When reporting issues, please include: - Error messages and codes - Steps to reproduce the issue - Browser and device information - Network conditions (if relevant) - Order IDs (if applicable)
Remember: Good error handling is about more than just catching errors—it’s about providing a smooth, understandable experience for your users even when things go wrong.