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