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();
  }
}

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 'NO_QUOTES':
      // No liquidity available
      return handleNoLiquidity(originalQuote);
      
    case 'TOO_LITTLE_RECEIVED':
      // Output below minimum
      return handleLowOutput(originalQuote);
  }
}

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' 
      };
  }
}

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'
];

const USER_ACTION_REQUIRED = [
  'TRANSFER_AMOUNT_EXCEEDS_BALANCE',
  'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE',
  'INSUFFICIENT_NATIVE_TOKENS_SUPPLIED',
  'INVALID_SIGNATURE',
  'SIGNATURE_EXPIRED'
];

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) {
  if (['TRANSFER_AMOUNT_EXCEEDS_BALANCE', 'TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE', 'INSUFFICIENT_NATIVE_TOKENS_SUPPLIED'].includes(failReason)) {
    return handleBalanceAndAllowanceErrors;
  }
  
  if (['SLIPPAGE', 'ORDER_EXPIRED', 'NO_QUOTES', 'TOO_LITTLE_RECEIVED'].includes(failReason)) {
    return handleMarketErrors;
  }
  
  if (['INVALID_SIGNATURE', 'SIGNATURE_EXPIRED', 'INVALID_SENDER'].includes(failReason)) {
    return handleSignatureErrors;
  }
  
  if (['INSUFFICIENT_FUNDS_FOR_RENT', 'JUPITER_INVALID_TOKEN_ACCOUNT'].includes(failReason)) {
    return handleSolanaErrors;
  }
  
  // Default handler
  return () => ({ action: 'manual', message: '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 = {
    SLIPPAGE: 'Price changed during execution',
    TRANSFER_AMOUNT_EXCEEDS_BALANCE: 'Insufficient token balance',
    TRANSFER_AMOUNT_EXCEEDS_ALLOWANCE: 'Token approval needed',
    ORDER_EXPIRED: 'Quote expired',
    NO_QUOTES: 'No liquidity available',
    EXECUTION_REVERTED: 'Transaction failed',
    INSUFFICIENT_FUNDS_FOR_RENT: 'Insufficient SOL for fees',
    JUPITER_INVALID_TOKEN_ACCOUNT: 'Solana account issue'
  };
  
  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
I