Skip to main content
After getting a quote and handling any required approvals, you need to execute the actual transfer transaction. This page covers chain verification, gas handling, signing, and broadcasting.

The Transaction Request

The quote response includes a ready-to-use transactionRequest object:
{
  "transactionRequest": {
    "to": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE",
    "data": "0x4630a0d8...",
    "value": "0x0",
    "gasLimit": "0x55730",
    "gasPrice": "0x5d21dba00",
    "chainId": 1
  }
}
FieldDescription
toLI.FI contract address
dataEncoded function call
valueNative token amount (hex, in wei)
gasLimitEstimated gas limit (hex)
gasPriceSuggested gas price (hex, optional)
chainIdChain ID for the transaction

Pre-Execution Checklist

Before sending the transaction:
  • Wallet is connected to the correct chain (transactionRequest.chainId)
  • User has sufficient native token for gas
  • User has sufficient fromToken balance
  • Token approval is confirmed (if ERC20)
  • Quote is still valid (not expired)

Step 1: Verify Chain

Ensure the wallet is on the correct chain before sending:

JavaScript (viem)

import { createWalletClient, http } from 'viem';
import { mainnet, arbitrum, optimism, polygon, base } from 'viem/chains';

const CHAIN_MAP = {
  1: mainnet,
  42161: arbitrum,
  10: optimism,
  137: polygon,
  8453: base
};

async function ensureCorrectChain(walletClient, targetChainId) {
  const currentChainId = await walletClient.getChainId();
  
  if (currentChainId !== targetChainId) {
    console.log(`Switching from chain ${currentChainId} to ${targetChainId}`);
    
    await walletClient.switchChain({ id: targetChainId });
    
    // Verify switch succeeded
    const newChainId = await walletClient.getChainId();
    if (newChainId !== targetChainId) {
      throw new Error(`Failed to switch to chain ${targetChainId}`);
    }
  }
  
  return true;
}

Python (web3.py)

def ensure_correct_chain(web3, target_chain_id):
    current_chain = web3.eth.chain_id
    
    if current_chain != target_chain_id:
        raise Exception(
            f"Wrong chain. Connected to {current_chain}, need {target_chain_id}"
        )
    
    return True

Step 2: Estimate Gas (Optional)

The quote includes a gasLimit, but you can re-estimate for accuracy:
async function estimateGas(publicClient, txRequest, fromAddress) {
  try {
    const gasEstimate = await publicClient.estimateGas({
      account: fromAddress,
      to: txRequest.to,
      data: txRequest.data,
      value: BigInt(txRequest.value || '0x0')
    });
    
    // Add 20% buffer
    return (gasEstimate * 120n) / 100n;
  } catch (error) {
    // Fall back to quote's gasLimit
    console.log('Using quote gasLimit:', txRequest.gasLimit);
    return BigInt(txRequest.gasLimit);
  }
}
The quote’s gasLimit is usually accurate. Only re-estimate if you need precise gas costs or are optimizing for gas efficiency.

Step 3: Build the Transaction

Convert the quote’s transaction request into a sendable transaction:

JavaScript (viem)

function buildTransaction(txRequest, gasLimit) {
  return {
    to: txRequest.to,
    data: txRequest.data,
    value: BigInt(txRequest.value || '0x0'),
    gas: gasLimit || BigInt(txRequest.gasLimit),
    chainId: txRequest.chainId
  };
}

Gas Price Options

You can use the quote’s suggested gas price or fetch current prices:
// Option 1: Use quote's gas price
const tx = {
  ...buildTransaction(txRequest),
  gasPrice: BigInt(txRequest.gasPrice)
};

// Option 2: Let wallet determine gas price (recommended)
const tx = buildTransaction(txRequest);
// Don't set gasPrice - wallet will use current market rate

// Option 3: Use EIP-1559 (for supported chains)
const tx = {
  ...buildTransaction(txRequest),
  maxFeePerGas: parseGwei('50'),
  maxPriorityFeePerGas: parseGwei('2')
};

Step 4: Sign and Send

JavaScript (viem)

async function executeTransaction(walletClient, publicClient, txRequest) {
  // Ensure on correct chain
  await ensureCorrectChain(walletClient, txRequest.chainId);
  
  // Build transaction
  const tx = {
    to: txRequest.to,
    data: txRequest.data,
    value: BigInt(txRequest.value || '0x0'),
    gas: BigInt(txRequest.gasLimit)
  };
  
  // Send transaction
  console.log('Sending transaction...');
  const hash = await walletClient.sendTransaction(tx);
  console.log(`Transaction sent: ${hash}`);
  
  // Wait for confirmation
  console.log('Waiting for confirmation...');
  const receipt = await publicClient.waitForTransactionReceipt({ 
    hash,
    confirmations: 1
  });
  
  if (receipt.status !== 'success') {
    throw new Error(`Transaction failed: ${hash}`);
  }
  
  console.log(`Transaction confirmed in block ${receipt.blockNumber}`);
  
  return {
    hash,
    receipt
  };
}

Python (web3.py)

def execute_transaction(web3, tx_request, private_key):
    account = web3.eth.account.from_key(private_key)
    
    # Build transaction
    tx = {
        'to': tx_request['to'],
        'data': tx_request['data'],
        'value': int(tx_request.get('value', '0x0'), 16),
        'gas': int(tx_request['gasLimit'], 16),
        'nonce': web3.eth.get_transaction_count(account.address),
        'chainId': tx_request['chainId']
    }
    
    # Get gas price
    tx['gasPrice'] = web3.eth.gas_price
    
    # Sign transaction
    signed = web3.eth.account.sign_transaction(tx, private_key)
    
    # Send transaction
    print('Sending transaction...')
    tx_hash = web3.eth.send_raw_transaction(signed.rawTransaction)
    print(f'Transaction sent: {tx_hash.hex()}')
    
    # Wait for confirmation
    print('Waiting for confirmation...')
    receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
    
    if receipt['status'] != 1:
        raise Exception(f'Transaction failed: {tx_hash.hex()}')
    
    print(f'Confirmed in block {receipt["blockNumber"]}')
    
    return {
        'hash': tx_hash.hex(),
        'receipt': receipt
    }

Step 5: Handle the Result

After execution, save the transaction hash for status polling:
const { hash: txHash } = await executeTransaction(walletClient, publicClient, quote.transactionRequest);

// Save for status polling (only txHash is required, others speed up response)
const statusParams = {
  txHash,                                    // Required
  fromChain: quote.action.fromChainId,       // Recommended for faster response
  toChain: quote.action.toChainId,           // Optional
  bridge: quote.tool                         // Optional (e.g., "stargate")
};

// Now poll for cross-chain status
// See: Status & Recovery page

Complete Execution Flow

async function executeQuote(quote, walletClient, publicClient) {
  const { transactionRequest, action, tool, estimate } = quote;
  
  // 1. Verify chain
  await ensureCorrectChain(walletClient, transactionRequest.chainId);
  
  // 2. Handle approval if needed
  await ensureApproval(quote, walletClient, publicClient);
  
  // 3. Execute main transaction
  const tx = {
    to: transactionRequest.to,
    data: transactionRequest.data,
    value: BigInt(transactionRequest.value || '0x0'),
    gas: BigInt(transactionRequest.gasLimit)
  };
  
  const hash = await walletClient.sendTransaction(tx);
  
  // 4. Wait for source chain confirmation
  const receipt = await publicClient.waitForTransactionReceipt({ 
    hash,
    confirmations: 1 
  });
  
  if (receipt.status !== 'success') {
    throw new Error('Transaction reverted');
  }
  
  // 5. Return data for status polling
  return {
    txHash: hash,
    bridge: tool,
    fromChain: action.fromChainId,
    toChain: action.toChainId,
    receipt
  };
}

Error Handling

Common Execution Errors

ErrorCauseSolution
insufficient fundsNot enough ETH for gas + valueCheck balance before sending
nonce too lowPending transaction existsWait or speed up pending tx
gas too lowGas limit insufficientIncrease gas limit
execution revertedContract rejected transactionQuote may be stale, get new quote
replacement fee too lowTrying to replace pending txIncrease gas price

Retry Logic

async function executeWithRetry(quote, walletClient, publicClient, maxRetries = 2) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await executeQuote(quote, walletClient, publicClient);
    } catch (error) {
      const message = error.message.toLowerCase();
      
      // Don't retry these errors
      if (message.includes('insufficient funds') ||
          message.includes('user rejected') ||
          message.includes('denied')) {
        throw error;
      }
      
      // Quote might be stale - get new quote and retry
      if (message.includes('reverted') && attempt < maxRetries - 1) {
        console.log('Transaction reverted, fetching new quote...');
        quote = await getQuote(originalParams);
        continue;
      }
      
      throw error;
    }
  }
}

Gas Optimization Tips

  1. Use EIP-1559 where supported - More predictable gas costs
  2. Don’t over-estimate gas - Quote’s gasLimit is usually accurate
  3. Check gas prices - High gas times may make transfers expensive
  4. Consider L2s - Arbitrum, Optimism, Base have much lower gas costs
// Check if gas cost is reasonable
const gasCostWei = BigInt(txRequest.gasLimit) * BigInt(txRequest.gasPrice);
const gasCostEth = Number(gasCostWei) / 1e18;

if (gasCostEth > 0.01) { // More than 0.01 ETH
  console.log(`Warning: High gas cost: ${gasCostEth} ETH`);
}