Skip to main content
When a quote execution fails, it gets marked as REFUNDED and includes a failReason field that explains why the refund occurred. Understanding these reasons helps you build better error handling and improve user experience.
For a complete reference of all fail reason codes with detailed explanations, see Error Codes.

How Refund Reasons Work

When you check the status of a quote using the Get Execution Status endpoint, refunded quotes include additional context:
{
  "quoteId": "0xfa6094cd...",
  "status": "REFUNDED",
  "user": "0x9b747cC14...",
  "failReason": "SLIPPAGE",
  "originChainOperations": [],
  "destinationChainOperations": []
}

Implementation Patterns by Category

Balance and Allowance Issues

Handle insufficient funds and token approval problems:
function handleBalanceAndAllowanceErrors(failReason: string) {
  switch (failReason) {
    case 'TRANSFER_AMOUNT_EXCEEDS_BALANCE':
      // Check current balance before retrying
      return handleInsufficientBalance();
      
    case 'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE':
      // Request token approval
      return handleInsufficientAllowance();
      
    case 'INSUFFICIENT_NATIVE_TOKENS_SUPPLIED':
      // Add gas tokens
      return handleInsufficientGas();
      
    case 'TRANSFER_FROM_FAILED':
    case 'TRANSFER_FAILED':
      // Transfer operation failed
      return handleTransferFailure();
  }
}

async function handleInsufficientBalance() {
  const balance = await getBalance(userAddress, tokenAddress);
  if (balance.gte(requiredAmount)) {
    // Balance recovered, safe to retry
    return { action: 'retry', message: 'Balance sufficient, retrying...' };
  } else {
    // Show balance error to user
    return { action: 'error', message: 'Insufficient balance for this transaction' };
  }
}

async function handleInsufficientAllowance() {
  // Request token approval
  return { 
    action: 'approval_needed', 
    message: 'Token approval required',
    nextStep: 'approve_token'
  };
}

Price and Market Issues

Handle slippage, expired orders, and market conditions:
function handleMarketErrors(failReason: string, originalQuote: any) {
  switch (failReason) {
    case 'SLIPPAGE':
      // Retry with higher slippage tolerance
      return retryWithHigherSlippage(originalQuote);
      
    case 'ORDER_EXPIRED':
      // Generate fresh quote
      return generateFreshQuote(originalQuote);
      
    case 'ORDER_IS_CANCELLED':
      // Order was cancelled
      return { action: 'new_quote', message: 'Order cancelled, please create a new order' };
      
    case 'ORDER_ALREADY_FILLED':
      // Order already completed
      return { action: 'error', message: 'Order already filled' };
      
    case 'NO_QUOTES':
      // No liquidity available
      return handleNoLiquidity(originalQuote);
      
    case 'TOO_LITTLE_RECEIVED':
      // Output below minimum
      return handleLowOutput(originalQuote);
      
    case 'NO_INTERNAL_SWAP_ROUTES_FOUND':
      // No routing path available
      return { action: 'error', message: 'No swap route available for this pair' };
      
    case 'SWAP_USES_TOO_MUCH_GAS':
      // Gas cost too high
      return { action: 'retry', message: 'Gas cost too high, try smaller amount' };
  }
}

async function retryWithHigherSlippage(originalQuote: any) {
  const currentSlippage = originalQuote.slippageTolerance || 100;
  const newSlippage = Math.min(currentSlippage * 1.5, 500); // Cap at 5%
  
  return {
    action: 'retry',
    message: `Retrying with ${newSlippage / 100}% slippage tolerance`,
    newParams: {
      ...originalQuote,
      slippageTolerance: newSlippage
    }
  };
}

async function generateFreshQuote(originalQuote: any) {
  return {
    action: 'new_quote',
    message: 'Order expired, generating new quote with current prices',
    newParams: originalQuote
  };
}

Signature and Authentication Issues

Handle wallet and signing problems:
function handleSignatureErrors(failReason: string) {
  switch (failReason) {
    case 'INVALID_SIGNATURE':
      return { 
        action: 'resign', 
        message: 'Invalid signature, please sign again' 
      };
      
    case 'SIGNATURE_EXPIRED':
      return { 
        action: 'resign', 
        message: 'Signature expired, please sign the new transaction' 
      };
      
    case 'INVALID_SENDER':
      return { 
        action: 'reconnect', 
        message: 'Please reconnect your wallet' 
      };
      
    case 'ACCOUNT_ABSTRACTION_INVALID_NONCE':
    case 'ACCOUNT_ABSTRACTION_SIGNATURE_ERROR':
      return { 
        action: 'sync_wallet', 
        message: 'Smart wallet sync required, please retry' 
      };
  }
}

System and Protocol Issues

Handle system capacity and protocol-specific errors:
function handleSystemErrors(failReason: string) {
  switch (failReason) {
    case 'SOLVER_CAPACITY_EXCEEDED':
      return {
        action: 'retry',
        message: 'System at capacity, retrying in a moment',
        delay: 2000
      };
      
    case 'EXECUTION_REVERTED':
    case 'TRANSACTION_REVERTED':
      return {
        action: 'error',
        message: 'Transaction reverted, please check parameters'
      };
      
    case 'GENERATE_SWAP_FAILED':
    case 'REVERSE_SWAP_FAILED':
      return {
        action: 'retry',
        message: 'Swap generation failed, retrying'
      };
      
    case 'MISSING_REVERT_DATA':
      return {
        action: 'contact_support',
        message: 'Transaction failed without details, contact support'
      };
  }
}

Deposit and Validation Issues

Handle deposit-related errors:
function handleDepositErrors(failReason: string) {
  switch (failReason) {
    case 'DEPOSIT_ADDRESS_MISMATCH':
    case 'DEPOSIT_CHAIN_MISMATCH':
    case 'INCORRECT_DEPOSIT_CURRENCY':
      return {
        action: 'error',
        message: 'Deposit details incorrect, verify and retry'
      };
      
    case 'AMOUNT_TOO_LOW_TO_REFUND':
    case 'DEPOSITED_AMOUNT_TOO_LOW_TO_FILL':
      return {
        action: 'error',
        message: 'Amount too low, increase amount'
      };
      
    case 'NEGATIVE_NEW_AMOUNT_AFTER_FEES':
      return {
        action: 'error',
        message: 'Fees exceed amount, increase amount'
      };
      
    case 'ORIGIN_CURRENCY_MISMATCH':
      return {
        action: 'error',
        message: 'Source currency mismatch, check token'
      };
  }
}

Token and Protocol Specific Issues

Handle token-specific and protocol errors:
function handleTokenErrors(failReason: string) {
  switch (failReason) {
    case 'TOKEN_NOT_TRANSFERABLE':
      return {
        action: 'error',
        message: 'Token has transfer restrictions'
      };
      
    case 'ZERO_SELL_AMOUNT':
      return {
        action: 'error',
        message: 'Invalid amount, must be greater than zero'
      };
      
    case 'SEAPORT_INEXACT_FRACTION':
    case 'SEAPORT_INVALID_FULFILLER':
      return {
        action: 'error',
        message: 'NFT order validation failed'
      };
      
    case 'MINT_NOT_ACTIVE':
      return {
        action: 'wait',
        message: 'Minting not active, try later'
      };
      
    case 'ERC_1155_TOO_MANY_REQUESTED':
      return {
        action: 'error',
        message: 'Reduce number of tokens requested'
      };
      
    case 'INCORRECT_PAYMENT':
      return {
        action: 'error',
        message: 'Payment amount incorrect'
      };
      
    case 'INVALID_GAS_PRICE':
      return {
        action: 'retry',
        message: 'Invalid gas price, retrying with updated price'
      };
      
    case 'FLUID_DEX_ERROR':
      return {
        action: 'retry',
        message: 'DEX protocol error, retrying'
      };
      
    case 'NEW_CALLDATA_INCLUDES_HIGHER_RENT_FEE':
      return {
        action: 'confirm',
        message: 'Higher fees required, confirm to proceed'
      };
  }
}

Solana-Specific Issues

Handle Solana blockchain specific problems:
function handleSolanaErrors(failReason: string) {
  switch (failReason) {
    case 'INSUFFICIENT_FUNDS_FOR_RENT':
      return {
        action: 'add_sol',
        message: 'Insufficient SOL for rent. Add SOL to your wallet.',
        minAmount: '0.002 SOL'
      };
      
    case 'JUPITER_INVALID_TOKEN_ACCOUNT':
      return {
        action: 'init_account',
        message: 'Token account needs initialization. This will be handled automatically.',
        autoRetry: true
      };
  }
}

Automatic Retry Logic

Implement smart retry mechanisms for recoverable errors:
const RETRYABLE_REASONS = [
  'SLIPPAGE',
  'ORDER_EXPIRED',
  'SOLVER_CAPACITY_EXCEEDED',
  'SWAP_USES_TOO_MUCH_GAS',
  'EXECUTION_REVERTED',
  'TRANSACTION_REVERTED',
  'GENERATE_SWAP_FAILED',
  'REVERSE_SWAP_FAILED',
  'TOO_LITTLE_RECEIVED',
  'INVALID_GAS_PRICE',
  'FLUID_DEX_ERROR'
];

const USER_ACTION_REQUIRED = [
  'TRANSFER_AMOUNT_EXCEEDS_BALANCE',
  'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE',
  'INSUFFICIENT_NATIVE_TOKENS_SUPPLIED',
  'INVALID_SIGNATURE',
  'SIGNATURE_EXPIRED',
  'INVALID_SENDER',
  'ACCOUNT_ABSTRACTION_INVALID_NONCE',
  'ACCOUNT_ABSTRACTION_SIGNATURE_ERROR',
  'TRANSFER_FROM_FAILED',
  'TRANSFER_FAILED',
  'INSUFFICIENT_FUNDS_FOR_RENT',
  'JUPITER_INVALID_TOKEN_ACCOUNT'
];

const NON_RETRYABLE_ERRORS = [
  'UNKNOWN',
  'DOUBLE_SPEND',
  'AMOUNT_TOO_LOW_TO_REFUND',
  'DEPOSIT_ADDRESS_MISMATCH',
  'DEPOSIT_CHAIN_MISMATCH',
  'INCORRECT_DEPOSIT_CURRENCY',
  'DEPOSITED_AMOUNT_TOO_LOW_TO_FILL',
  'NEGATIVE_NEW_AMOUNT_AFTER_FEES',
  'NO_QUOTES',
  'NO_INTERNAL_SWAP_ROUTES_FOUND',
  'ORDER_IS_CANCELLED',
  'ORDER_ALREADY_FILLED',
  'TOKEN_NOT_TRANSFERABLE',
  'ZERO_SELL_AMOUNT',
  'SEAPORT_INEXACT_FRACTION',
  'SEAPORT_INVALID_FULFILLER',
  'MINT_NOT_ACTIVE',
  'ERC_1155_TOO_MANY_REQUESTED',
  'INCORRECT_PAYMENT',
  'ORIGIN_CURRENCY_MISMATCH',
  'MISSING_REVERT_DATA'
];

async function executeWithSmartRetry(quoteParams: QuoteParams, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const quote = await getQuote(quoteParams);
      const result = await executeQuote(quote);
      
      if (result.status === 'COMPLETED') {
        return { success: true, result };
      }
      
      // Handle refunded quotes
      if (result.status === 'REFUNDED' && result.failReason) {
        const errorHandler = getErrorHandler(result.failReason);
        const handlerResult = errorHandler(result.failReason, quoteParams);
        
        if (handlerResult.action === 'retry' && attempt < maxRetries) {
          // Automatic retry with adjusted parameters
          quoteParams = { ...quoteParams, ...handlerResult.newParams };
          await delay(1000 * attempt); // Exponential backoff
          continue;
        }
        
        // Return error for user action or final attempt
        return { 
          success: false, 
          error: result.failReason,
          action: handlerResult.action,
          message: handlerResult.message,
          retryable: RETRYABLE_REASONS.includes(result.failReason)
        };
      }
      
    } catch (error) {
      if (attempt === maxRetries) throw error;
      await delay(1000 * attempt);
    }
  }
}

function getErrorHandler(failReason: string) {
  // Balance and Allowance Issues
  if ([
    'TRANSFER_AMOUNT_EXCEEDS_BALANCE',
    'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE',
    'INSUFFICIENT_NATIVE_TOKENS_SUPPLIED',
    'TRANSFER_FROM_FAILED',
    'TRANSFER_FAILED'
  ].includes(failReason)) {
    return handleBalanceAndAllowanceErrors;
  }
  
  // Price and Market Issues
  if ([
    'SLIPPAGE',
    'ORDER_EXPIRED',
    'ORDER_IS_CANCELLED',
    'ORDER_ALREADY_FILLED',
    'NO_QUOTES',
    'TOO_LITTLE_RECEIVED',
    'NO_INTERNAL_SWAP_ROUTES_FOUND',
    'SWAP_USES_TOO_MUCH_GAS'
  ].includes(failReason)) {
    return handleMarketErrors;
  }
  
  // Signature and Authentication Issues
  if ([
    'INVALID_SIGNATURE',
    'SIGNATURE_EXPIRED',
    'INVALID_SENDER',
    'ACCOUNT_ABSTRACTION_INVALID_NONCE',
    'ACCOUNT_ABSTRACTION_SIGNATURE_ERROR'
  ].includes(failReason)) {
    return handleSignatureErrors;
  }
  
  // System and Protocol Issues
  if ([
    'SOLVER_CAPACITY_EXCEEDED',
    'EXECUTION_REVERTED',
    'TRANSACTION_REVERTED',
    'GENERATE_SWAP_FAILED',
    'REVERSE_SWAP_FAILED',
    'MISSING_REVERT_DATA'
  ].includes(failReason)) {
    return handleSystemErrors;
  }
  
  // Deposit and Validation Issues
  if ([
    'DEPOSIT_ADDRESS_MISMATCH',
    'DEPOSIT_CHAIN_MISMATCH',
    'INCORRECT_DEPOSIT_CURRENCY',
    'AMOUNT_TOO_LOW_TO_REFUND',
    'DEPOSITED_AMOUNT_TOO_LOW_TO_FILL',
    'NEGATIVE_NEW_AMOUNT_AFTER_FEES',
    'ORIGIN_CURRENCY_MISMATCH'
  ].includes(failReason)) {
    return handleDepositErrors;
  }
  
  // Token and Protocol Specific Issues
  if ([
    'TOKEN_NOT_TRANSFERABLE',
    'ZERO_SELL_AMOUNT',
    'SEAPORT_INEXACT_FRACTION',
    'SEAPORT_INVALID_FULFILLER',
    'MINT_NOT_ACTIVE',
    'ERC_1155_TOO_MANY_REQUESTED',
    'INCORRECT_PAYMENT',
    'INVALID_GAS_PRICE',
    'FLUID_DEX_ERROR',
    'NEW_CALLDATA_INCLUDES_HIGHER_RENT_FEE'
  ].includes(failReason)) {
    return handleTokenErrors;
  }
  
  // Solana-Specific Issues
  if ([
    'INSUFFICIENT_FUNDS_FOR_RENT',
    'JUPITER_INVALID_TOKEN_ACCOUNT'
  ].includes(failReason)) {
    return handleSolanaErrors;
  }
  
  // Default handler for unknown or unhandled errors
  return () => ({ 
    action: 'manual', 
    message: 'Unexpected error, please try again or contact support' 
  });
}

User Experience Patterns

Progress Indicators

Show users what’s happening during retry flows:
interface RetryState {
  attempt: number;
  maxAttempts: number;
  currentAction: string;
  failReason?: string;
}

function ProgressIndicator({ retryState }: { retryState: RetryState }) {
  const progress = (retryState.attempt / retryState.maxAttempts) * 100;
  
  return (
    <div className="retry-progress">
      <div className="progress-bar" style={{ width: `${progress}%` }} />
      <p>
        Attempt {retryState.attempt} of {retryState.maxAttempts}: {retryState.currentAction}
      </p>
      {retryState.failReason && (
        <p className="error-reason">
          Previous attempt failed: {getUserFriendlyMessage(retryState.failReason)}
        </p>
      )}
    </div>
  );
}

function getUserFriendlyMessage(failReason: string): string {
  const messages = {
    // Balance and Allowance
    TRANSFER_AMOUNT_EXCEEDS_BALANCE: 'Insufficient token balance',
    TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE: 'Token approval needed',
    INSUFFICIENT_NATIVE_TOKENS_SUPPLIED: 'Insufficient gas tokens',
    TRANSFER_FROM_FAILED: 'Token transfer failed',
    TRANSFER_FAILED: 'Token transfer failed',
    
    // Price and Market
    SLIPPAGE: 'Price changed during execution',
    ORDER_EXPIRED: 'Quote expired',
    ORDER_IS_CANCELLED: 'Order was cancelled',
    ORDER_ALREADY_FILLED: 'Order already completed',
    NO_QUOTES: 'No liquidity available',
    TOO_LITTLE_RECEIVED: 'Output amount too low',
    NO_INTERNAL_SWAP_ROUTES_FOUND: 'No swap route found',
    SWAP_USES_TOO_MUCH_GAS: 'Gas cost too high',
    
    // Signature and Authentication
    INVALID_SIGNATURE: 'Invalid signature',
    SIGNATURE_EXPIRED: 'Signature expired',
    INVALID_SENDER: 'Invalid sender',
    ACCOUNT_ABSTRACTION_INVALID_NONCE: 'Wallet sync needed',
    ACCOUNT_ABSTRACTION_SIGNATURE_ERROR: 'Wallet signature error',
    
    // System and Protocol
    SOLVER_CAPACITY_EXCEEDED: 'System at capacity',
    EXECUTION_REVERTED: 'Transaction reverted',
    TRANSACTION_REVERTED: 'Transaction reverted',
    GENERATE_SWAP_FAILED: 'Swap generation failed',
    REVERSE_SWAP_FAILED: 'Reverse swap failed',
    MISSING_REVERT_DATA: 'Transaction failed',
    
    // Deposit and Validation
    DEPOSIT_ADDRESS_MISMATCH: 'Wrong deposit address',
    DEPOSIT_CHAIN_MISMATCH: 'Wrong blockchain',
    INCORRECT_DEPOSIT_CURRENCY: 'Wrong token deposited',
    AMOUNT_TOO_LOW_TO_REFUND: 'Amount too low',
    DEPOSITED_AMOUNT_TOO_LOW_TO_FILL: 'Amount too low',
    NEGATIVE_NEW_AMOUNT_AFTER_FEES: 'Fees exceed amount',
    ORIGIN_CURRENCY_MISMATCH: 'Source token mismatch',
    DOUBLE_SPEND: 'Duplicate transaction',
    
    // Token and Protocol Specific
    TOKEN_NOT_TRANSFERABLE: 'Token transfer restricted',
    ZERO_SELL_AMOUNT: 'Invalid amount',
    SEAPORT_INEXACT_FRACTION: 'NFT order error',
    SEAPORT_INVALID_FULFILLER: 'NFT fulfiller error',
    MINT_NOT_ACTIVE: 'Minting not active',
    ERC_1155_TOO_MANY_REQUESTED: 'Too many tokens requested',
    INCORRECT_PAYMENT: 'Payment amount incorrect',
    INVALID_GAS_PRICE: 'Invalid gas price',
    FLUID_DEX_ERROR: 'DEX protocol error',
    NEW_CALLDATA_INCLUDES_HIGHER_RENT_FEE: 'Higher fees required',
    
    // Solana-Specific
    INSUFFICIENT_FUNDS_FOR_RENT: 'Insufficient SOL for rent',
    JUPITER_INVALID_TOKEN_ACCOUNT: 'Solana account issue',
    
    // Unknown
    UNKNOWN: 'Unknown error'
  };
  
  return messages[failReason] || 'Transaction failed';
}

Error Recovery UI

Provide clear next steps for users:
function ErrorRecoveryUI({ error, onRetry, onCancel }) {
  const getActionButton = (action: string) => {
    switch (action) {
      case 'retry':
        return <button onClick={onRetry}>Try Again</button>;
      case 'approval_needed':
        return <button onClick={handleApproval}>Approve Token</button>;
      case 'add_sol':
        return <button onClick={handleAddSol}>Add SOL</button>;
      case 'reconnect':
        return <button onClick={handleReconnect}>Reconnect Wallet</button>;
      default:
        return <button onClick={onCancel}>Cancel</button>;
    }
  };

  return (
    <div className="error-recovery">
      <h3>Transaction Failed</h3>
      <p>{error.message}</p>
      
      <div className="actions">
        {getActionButton(error.action)}
        <button onClick={onCancel} className="secondary">Cancel</button>
      </div>
      
      {error.retryable && (
        <p className="retry-hint">
          This error can be automatically retried. Click "Try Again" to retry with adjusted parameters.
        </p>
      )}
    </div>
  );
}

Analytics and Monitoring

Track fail reason patterns to optimize your integration:
interface FailReasonAnalytics {
  failReason: string;
  tokenPair: string;
  amount: string;
  attempt: number;
  userAgent: string;
  timestamp: string;
}

function trackFailReason(data: FailReasonAnalytics) {
  // Send to your analytics service
  analytics.track('quote_refunded', {
    fail_reason: data.failReason,
    token_pair: data.tokenPair,
    amount_usd: data.amount,
    retry_attempt: data.attempt,
    user_agent: data.userAgent,
    timestamp: data.timestamp
  });
}

function analyzeFailReasonTrends(timeRange: string) {
  // Query your analytics to identify patterns
  return {
    mostCommonReasons: ['SLIPPAGE', 'TRANSFER_AMOUNT_EXCEEDS_BALANCE'],
    successRateByReason: {
      SLIPPAGE: 0.85, // 85% success after retry
      ORDER_EXPIRED: 0.92,
      NO_QUOTES: 0.45
    },
    recommendations: [
      'Increase default slippage tolerance to 1.5%',
      'Implement balance checking before quote generation',
      'Add liquidity warnings for low-liquidity pairs'
    ]
  };
}

Best Practices Summary

  1. Categorize Error Handling: Group similar fail reasons and handle them with consistent patterns
  2. Implement Smart Retries: Automatically retry recoverable errors with adjusted parameters
  3. Provide Clear UI: Show users exactly what went wrong and what they need to do
  4. Monitor Patterns: Track fail reasons to identify optimization opportunities
  5. Progressive Enhancement: Start with basic error handling and add sophistication over time

Error Codes Reference

Complete reference of all fail reason codes with detailed explanations

Transaction Lifecycle

Understand the complete journey of a quote from creation to completion

Quote Examples

Working examples of quote generation and execution with proper error handling

Slippage Guide

Deep dive into slippage tolerance and price impact management
Last modified on November 3, 2025