Cross-chain transfers take time to complete. This page explains how to poll the status endpoint, interpret status values, and handle each outcome correctly.
Status Endpoint
GET https://li.quest/v1/status
Parameters
| Parameter | Required | Description | Source |
|---|
txHash | Yes | Transaction hash from execution | From sendTransaction result |
fromChain | No | Source chain ID (recommended) | From quote.action.fromChainId |
toChain | No | Destination chain ID | From quote.action.toChainId |
bridge | No | Bridge/tool identifier | From quote.tool |
Only txHash is required. Providing fromChain speeds up the response significantly.
Example Request
curl "https://li.quest/v1/status?txHash=0xabc123..."
Or with optional parameters for faster response:
curl "https://li.quest/v1/status?txHash=0xabc123...&fromChain=1&toChain=42161&bridge=stargate"
Status Response
{
"transactionId": "0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"sending": {
"txHash": "0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"txLink": "https://etherscan.io/tx/0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"chainId": 1,
"amount": "10000000",
"token": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"chainId": 1,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606"
},
"amountUSD": "9.9961",
"gasPrice": "65729222",
"gasUsed": "220266",
"gasToken": {
"address": "0x0000000000000000000000000000000000000000",
"chainId": 1,
"symbol": "ETH",
"decimals": 18,
"name": "ETH",
"coinKey": "ETH",
"priceUSD": "2923.27"
},
"gasAmount": "14477912813052",
"gasAmountUSD": "0.0423",
"timestamp": 1737625200
},
"receiving": {
"txHash": "0xdef456789abc...",
"txLink": "https://arbiscan.io/tx/0xdef456789abc...",
"chainId": 42161,
"amount": "9965731",
"token": {
"address": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"chainId": 42161,
"symbol": "USDC",
"decimals": 6,
"name": "USD Coin",
"coinKey": "USDC",
"logoURI": "https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png",
"priceUSD": "0.999606"
},
"amountUSD": "9.9618",
"timestamp": 1737625204
},
"lifiExplorerLink": "https://explorer.li.fi/tx/0x1695bded7c1634dce4a200bdec72be0ed6cc5f7153ec2dc832b271d790eb00c8",
"fromAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"toAddress": "0x552008c0f6870c2f77e5cC1d2eb9bdff03e30Ea0",
"tool": "across",
"status": "DONE",
"substatus": "COMPLETED"
}
Status Values
Primary Status
| Status | Meaning | Action |
|---|
NOT_FOUND | Transaction not indexed yet | Continue polling |
PENDING | Transfer in progress | Continue polling |
DONE | Transfer finished | Check substatus for outcome |
FAILED | Transfer failed | Handle error |
Substatus (when status = DONE)
| Substatus | Meaning | User Outcome |
|---|
COMPLETED | Success | Received exact requested token |
PARTIAL | Partial success | Received different token (full value) |
REFUNDED | Failed with refund | Tokens returned to sender |
Decision Matrix
IF status === "NOT_FOUND"
→ Wait and poll again (tx may not be indexed yet)
IF status === "PENDING"
→ Wait and poll again (transfer in progress)
IF status === "DONE"
IF substatus === "COMPLETED"
→ Success! User received toToken on toChain
IF substatus === "PARTIAL"
→ Partial success - see Partial Completion guide
IF substatus === "REFUNDED"
→ Failed - tokens returned to user on fromChain
IF status === "FAILED"
→ Permanent failure - inform user, may need manual recovery
Polling Implementation
Recommended Strategy
| Attempt | Wait Time | Total Time |
|---|
| 1-6 | 10 seconds | 1 minute |
| 7-12 | 30 seconds | 4 minutes |
| 13-24 | 60 seconds | 16 minutes |
| 25+ | 120 seconds | Until timeout |
JavaScript Implementation
async function pollTransferStatus(params, options = {}) {
const { txHash, bridge, fromChain, toChain } = params;
const {
maxDuration = 30 * 60 * 1000, // 30 minutes
onStatusUpdate = () => {}
} = options;
const startTime = Date.now();
let attempt = 0;
while (Date.now() - startTime < maxDuration) {
attempt++;
try {
const response = await fetch(
`https://li.quest/v1/status?txHash=${txHash}&bridge=${bridge}&fromChain=${fromChain}&toChain=${toChain}`
);
if (!response.ok) {
throw new Error(`Status API error: ${response.status}`);
}
const status = await response.json();
// Notify caller of status update
onStatusUpdate(status, attempt);
// Check for terminal states
if (status.status === 'DONE') {
return {
success: status.substatus === 'COMPLETED',
partial: status.substatus === 'PARTIAL',
refunded: status.substatus === 'REFUNDED',
status
};
}
if (status.status === 'FAILED') {
return {
success: false,
failed: true,
status
};
}
} catch (error) {
console.error(`Polling error (attempt ${attempt}):`, error.message);
// Continue polling despite errors
}
// Calculate wait time with backoff
const waitTime = getWaitTime(attempt);
await sleep(waitTime);
}
throw new Error('Status polling timeout');
}
function getWaitTime(attempt) {
if (attempt <= 6) return 10000; // 10 seconds
if (attempt <= 12) return 30000; // 30 seconds
if (attempt <= 24) return 60000; // 60 seconds
return 120000; // 2 minutes
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Python Implementation
import time
import requests
def poll_transfer_status(tx_hash, bridge, from_chain, to_chain,
max_duration=1800, on_update=None):
"""
Poll transfer status until completion or timeout.
Args:
tx_hash: Transaction hash
bridge: Bridge tool name
from_chain: Source chain ID
to_chain: Destination chain ID
max_duration: Maximum polling duration in seconds (default 30 min)
on_update: Optional callback for status updates
Returns:
dict with success, partial, refunded, failed flags and status
"""
start_time = time.time()
attempt = 0
while time.time() - start_time < max_duration:
attempt += 1
try:
url = (
f"https://li.quest/v1/status"
f"?txHash={tx_hash}"
f"&bridge={bridge}"
f"&fromChain={from_chain}"
f"&toChain={to_chain}"
)
response = requests.get(url)
response.raise_for_status()
status = response.json()
# Notify callback
if on_update:
on_update(status, attempt)
# Check terminal states
if status.get('status') == 'DONE':
substatus = status.get('substatus')
return {
'success': substatus == 'COMPLETED',
'partial': substatus == 'PARTIAL',
'refunded': substatus == 'REFUNDED',
'status': status
}
if status.get('status') == 'FAILED':
return {
'success': False,
'failed': True,
'status': status
}
except Exception as e:
print(f"Polling error (attempt {attempt}): {e}")
# Wait with backoff
wait_time = get_wait_time(attempt)
time.sleep(wait_time)
raise Exception('Status polling timeout')
def get_wait_time(attempt):
if attempt <= 6:
return 10
if attempt <= 12:
return 30
if attempt <= 24:
return 60
return 120
Handling Each Outcome
COMPLETED - Success
if (result.success) {
const { receiving } = result.status;
console.log('Transfer successful!');
console.log(`Received: ${receiving.amount} ${receiving.token.symbol}`);
console.log(`On chain: ${receiving.chainId}`);
console.log(`Tx: ${receiving.txHash}`);
// No further action needed
}
PARTIAL - Different Token Received
if (result.partial) {
const { receiving } = result.status;
console.log('Transfer completed with different token');
console.log(`Received: ${receiving.amount} ${receiving.token.symbol}`);
// User may want to swap to their intended token
// See: Partial Completion guide
}
REFUNDED - Tokens Returned
if (result.refunded) {
const { sending } = result.status;
console.log('Transfer failed - tokens refunded');
console.log(`Refunded: ${sending.amount} ${sending.token.symbol}`);
console.log(`On chain: ${sending.chainId}`);
// User's tokens are back on the source chain
// May retry with different parameters
}
FAILED - Permanent Failure
if (result.failed) {
console.error('Transfer failed permanently');
console.error('Error:', result.status.error);
// May need manual intervention
// Contact support with transaction details
}
Estimated Transfer Times
The quote response includes estimate.executionDuration (in seconds) which gives you the expected transfer time for that specific route. Always use this value rather than general estimates.
Transfer times vary significantly based on the bridge used and network conditions. The executionDuration field in the quote response provides the most accurate estimate for each specific route.
Timeout Handling
If polling times out:
- Don’t assume failure - The transfer may still complete
- Save the transaction details - txHash, bridge, fromChain, toChain
- Provide manual check - User can check status later
- Link to explorer - Provide transaction explorer URLs
function getExplorerUrl(chainId, txHash) {
const explorers = {
1: 'https://etherscan.io/tx/',
42161: 'https://arbiscan.io/tx/',
10: 'https://optimistic.etherscan.io/tx/',
137: 'https://polygonscan.com/tx/',
8453: 'https://basescan.org/tx/'
};
return `${explorers[chainId] || 'https://blockscan.com/tx/'}${txHash}`;
}
// On timeout
console.log('Status check timed out. The transfer may still be processing.');
console.log(`Check source tx: ${getExplorerUrl(fromChain, txHash)}`);
console.log('You can also check status manually at: https://li.fi/');
Related Pages