Skip to main content
A PARTIAL completion occurs when a cross-chain transfer succeeds but the user receives a different token than requested. This typically happens when the destination swap fails but the bridge succeeds. This page explains how to detect, interpret, and recover from partial completions.

What is a Partial Completion?

In a cross-chain transfer, there are often multiple steps:
Source Chain                          Destination Chain
[Token A] → [Swap to B] → [Bridge] → [Receive B] → [Swap to C]
If the final swap on the destination chain fails (e.g., due to slippage), the user receives token B instead of token C. The value is preserved, but the token is different.

Example Scenario

  • Requested: 10 USDC on Ethereum → ETH on Arbitrum
  • Actual Result: 10 USDC on Ethereum → 10 USDC on Arbitrum (swap failed)
  • Status: DONE with substatus PARTIAL

Detecting Partial Completion

Check the status response:
const result = await pollTransferStatus(params);

if (result.partial) {
  // User received a different token
  const received = result.status.receiving;
  const requested = quote.action.toToken;
  
  console.log(`Requested: ${requested.symbol}`);
  console.log(`Received: ${received.token.symbol}`);
}

Status Response for PARTIAL

{
  "status": "DONE",
  "substatus": "PARTIAL",
  "sending": {
    "chainId": 1,
    "amount": "10000000",
    "token": {
      "symbol": "USDC",
      "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
    }
  },
  "receiving": {
    "chainId": 42161,
    "amount": "10000000",
    "token": {
      "symbol": "USDC",
      "address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
    }
  },
  "tool": "stargate"
}

Recovery Flow

When a partial completion occurs, offer the user a same-chain swap to get their intended token.

Step 1: Extract Received Token Details

function extractReceivedToken(status) {
  const { receiving } = status;
  
  return {
    chainId: receiving.chainId,
    address: receiving.token.address,
    symbol: receiving.token.symbol,
    decimals: receiving.token.decimals,
    amount: receiving.amount
  };
}

Step 2: Get a Same-Chain Swap Quote

Request a quote to swap the received token to the intended token:
async function getRecoveryQuote(status, originalQuote, userAddress) {
  const received = extractReceivedToken(status);
  const intended = originalQuote.action.toToken;
  
  // Same-chain swap on the destination chain
  const params = {
    fromChain: received.chainId,
    toChain: received.chainId,  // Same chain
    fromToken: received.address,
    toToken: intended.address,
    fromAmount: received.amount,
    fromAddress: userAddress
  };
  
  const response = await fetch(
    `https://li.quest/v1/quote?` + new URLSearchParams(params)
  );
  
  return await response.json();
}

Step 3: Present Options to User

async function handlePartialCompletion(status, originalQuote, userAddress) {
  const received = extractReceivedToken(status);
  const intended = originalQuote.action.toToken;
  
  console.log('\n=== Partial Transfer Detected ===');
  console.log(`You received: ${formatAmount(received.amount, received.decimals)} ${received.symbol}`);
  console.log(`You requested: ${intended.symbol}`);
  
  // Get recovery quote
  try {
    const recoveryQuote = await getRecoveryQuote(status, originalQuote, userAddress);
    
    const expectedOutput = formatAmount(
      recoveryQuote.estimate.toAmount,
      intended.decimals
    );
    
    console.log(`\nRecovery option available:`);
    console.log(`Swap ${received.symbol}${intended.symbol}`);
    console.log(`Expected output: ${expectedOutput} ${intended.symbol}`);
    
    return {
      hasRecoveryOption: true,
      received,
      recoveryQuote
    };
    
  } catch (error) {
    console.log(`\nNo direct swap available for ${received.symbol}${intended.symbol}`);
    
    return {
      hasRecoveryOption: false,
      received,
      error: error.message
    };
  }
}

function formatAmount(amount, decimals) {
  return (Number(amount) / Math.pow(10, decimals)).toFixed(4);
}

Complete Recovery Implementation

async function executePartialRecovery(
  status,
  originalQuote,
  walletClient,
  publicClient,
  userAddress
) {
  // 1. Extract what was received
  const received = extractReceivedToken(status);
  const intended = originalQuote.action.toToken;
  
  console.log('Partial completion detected');
  console.log(`Received: ${received.amount} ${received.symbol}`);
  console.log(`Intended: ${intended.symbol}`);
  
  // 2. Check if we need to recover (did user already get intended token?)
  if (received.address.toLowerCase() === intended.address.toLowerCase()) {
    console.log('Already received intended token - no recovery needed');
    return { success: true, recovered: false };
  }
  
  // 3. Get recovery quote
  console.log('Getting recovery swap quote...');
  const recoveryQuote = await getRecoveryQuote(status, originalQuote, userAddress);
  
  // 4. Check if recovery is worthwhile (accounting for gas)
  const gasCostEstimate = estimateGasCost(recoveryQuote);
  const swapValue = estimateSwapValue(recoveryQuote);
  
  if (gasCostEstimate > swapValue * 0.1) {
    console.log('Gas cost too high relative to swap value');
    console.log('Recommend keeping received token');
    return { 
      success: true, 
      recovered: false,
      reason: 'Gas cost exceeds 10% of swap value'
    };
  }
  
  // 5. Execute recovery swap
  console.log('Executing recovery swap...');
  
  // Handle approval if needed
  await ensureApproval(recoveryQuote, walletClient, publicClient);
  
  // Execute swap
  const { hash } = await executeTransaction(
    walletClient,
    publicClient,
    recoveryQuote.transactionRequest
  );
  
  console.log(`Recovery swap sent: ${hash}`);
  
  // 6. Poll recovery status (same-chain is usually fast)
  const recoveryResult = await pollTransferStatus({
    txHash: hash,
    bridge: recoveryQuote.tool,
    fromChain: received.chainId,
    toChain: received.chainId
  }, {
    maxDuration: 5 * 60 * 1000  // 5 minutes for same-chain
  });
  
  return {
    success: recoveryResult.success,
    recovered: true,
    recoveryTxHash: hash,
    finalStatus: recoveryResult.status
  };
}

Why Partial Completions Happen

CauseExplanation
Slippage exceededPrice moved beyond tolerance during transfer time
Liquidity changedDEX pool liquidity depleted
Token delistDestination DEX no longer supports the pair
Contract issueDestination DEX had temporary issue

Prevention Strategies

1. Use Higher Slippage for Volatile Tokens

// For volatile tokens, increase slippage
const slippage = isVolatileToken(toToken) ? 0.02 : 0.005; // 2% vs 0.5%

2. Prefer Direct Bridge Routes

// Request routes without destination swap when possible
const quote = await getQuote({
  ...params,
  toToken: 'USDC',  // Bridge token directly instead of swapping
});

3. Check Route Feasibility Before Transfer

For large amounts, you can verify the destination swap is feasible by requesting a quote for just that leg:
// Get quote for just the destination swap
const destSwapQuote = await getQuote({
  fromChain: toChain,
  toChain: toChain,
  fromToken: bridgeToken,
  toToken: intendedToken,
  fromAmount: amount
});

// If quote fails with NO_POSSIBLE_ROUTE, the destination swap may fail
// Consider using the bridge token as the final destination

User Communication Templates

Partial Completion Detected

Your transfer has partially completed.

✓ Bridged successfully
✗ Destination swap failed due to slippage

You received: 10.00 USDC on Arbitrum
You requested: ETH on Arbitrum

Would you like to swap your USDC to ETH now?
Estimated output: 0.0029 ETH

Recovery Not Available

Your transfer has partially completed.

You received: 10.00 TOKEN on Arbitrum
You requested: ETH on Arbitrum

Unfortunately, there's no direct swap route for TOKEN → ETH.
Your TOKEN balance is available in your wallet on Arbitrum.

Options:
1. Keep the TOKEN
2. Manually swap on a DEX that supports this token

Decision Tree

PARTIAL status received


Is received token = intended token?

    ┌───┴───┐
    │       │
   Yes      No
    │       │
    ▼       ▼
 Done    Get recovery quote


      Quote available?

        ┌───┴───┐
        │       │
       Yes      No
        │       │
        ▼       ▼
 Gas cost < 10%   Inform user,
  of value?       suggest manual

    ┌───┴───┐
    │       │
   Yes      No
    │       │
    ▼       ▼
 Execute    Recommend keeping
 recovery   received token