> ## Documentation Index
> Fetch the complete documentation index at: https://docs.li.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# Partial Completion

> How to detect and recover from partial transfers

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:

```javascript theme={"system"}
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

```json theme={"system"}
{
  "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": "stargateV2"
}
```

***

## 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

```javascript theme={"system"}
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:

```javascript theme={"system"}
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

```javascript theme={"system"}
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

```javascript theme={"system"}
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

| Cause             | Explanation                                       |
| ----------------- | ------------------------------------------------- |
| Slippage exceeded | Price moved beyond tolerance during transfer time |
| Liquidity changed | DEX pool liquidity depleted                       |
| Token delist      | Destination DEX no longer supports the pair       |
| Contract issue    | Destination DEX had temporary issue               |

***

## Prevention Strategies

### 1. Use Higher Slippage for Volatile Tokens

```javascript theme={"system"}
// For volatile tokens, increase slippage
const slippage = isVolatileToken(toToken) ? 0.02 : 0.005; // 2% vs 0.5%
```

### 2. Prefer Direct Bridge Routes

```javascript theme={"system"}
// 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:

```javascript theme={"system"}
// 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
```

***

## Related Pages

* [Status & Recovery](/agents/workflows/status-recovery) - Polling and status handling
* [Decision Tables](/agents/quick-start/decision-tables) - Route selection
* [Error Playbooks](/agents/reference/error-playbooks) - Handle failures
