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

# Transaction Execution

> How to sign, send, and confirm LI.FI transactions

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:

```json theme={"system"}
{
  "transactionRequest": {
    "to": "0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE",
    "data": "0x4630a0d8...",
    "value": "0x0",
    "gasLimit": "0x55730",
    "gasPrice": "0x5d21dba00",
    "chainId": 1
  }
}
```

| Field      | Description                         |
| ---------- | ----------------------------------- |
| `to`       | LI.FI contract address              |
| `data`     | Encoded function call               |
| `value`    | Native token amount (hex, in wei)   |
| `gasLimit` | Estimated gas limit (hex)           |
| `gasPrice` | Suggested gas price (hex, optional) |
| `chainId`  | Chain 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)

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

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

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

<Tip>
  The quote's `gasLimit` is usually accurate. Only re-estimate if you need precise gas costs or are optimizing for gas efficiency.
</Tip>

***

## Step 3: Build the Transaction

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

### JavaScript (viem)

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

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

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

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

```javascript theme={"system"}
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., "stargateV2")
};

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

***

## Complete Execution Flow

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

| Error                     | Cause                          | Solution                          |
| ------------------------- | ------------------------------ | --------------------------------- |
| `insufficient funds`      | Not enough ETH for gas + value | Check balance before sending      |
| `nonce too low`           | Pending transaction exists     | Wait or speed up pending tx       |
| `gas too low`             | Gas limit insufficient         | Increase gas limit                |
| `execution reverted`      | Contract rejected transaction  | Quote may be stale, get new quote |
| `replacement fee too low` | Trying to replace pending tx   | Increase gas price                |

### Retry Logic

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

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

***

## Related Pages

* [Token Approvals](/agents/workflows/approvals) - Handle ERC20 approvals
* [Status & Recovery](/agents/workflows/status-recovery) - Poll transfer status
* [Five-Call Recipe](/agents/quick-start/five-call-recipe) - Complete workflow

<script type="application/ld+json">
  {`{
    "@context": "https://schema.org",
    "@type": "FAQPage",
    "mainEntity": [
      {
        "@type": "Question",
        "name": "What is the transactionRequest object used for?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "The transactionRequest in the quote response is a ready-to-use transaction payload that includes destination contract, calldata, value, gas fields, and chainId for execution."
        }
      },
      {
        "@type": "Question",
        "name": "Do I need to verify the chain before sending the transaction?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Yes. You should verify that the wallet is connected to transactionRequest.chainId before signing and sending."
        }
      },
      {
        "@type": "Question",
        "name": "What should I do after sending the transaction?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Save the source transaction hash and then poll the status endpoint to track cross-chain completion and final outcome."
        }
      }
    ]
    }`}
</script>
