> For the complete documentation index, see [llms.txt](https://docs.fly.trade/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.fly.trade/developers/api-reference/evm-swap-integration-guide.md).

# EVM Swap Integration Guide

## EVM Swap Integration Guide

This guide shows how to execute a swap on **EVM networks** (Ethereum, Polygon, BSC, Arbitrum, etc.) using Fly Trade's API in 3 steps:

1. **Get a quote** (pricing + route + constraints)
2. **Fetch transaction payload** with the quote ID
3. **Sign and send** the transaction to the network

### Prerequisites

* An EVM RPC connection (Infura, Alchemy, QuickNode, or self-hosted node)
* A wallet that can sign EVM transactions (MetaMask, WalletConnect, ethers.js Signer, or backend private key)
* Sufficient balance of the tokens you want to swap and their token contract addresses

## How the Flow Works

The high-level swap flow is:

* Call `/aggregator/quote` to receive a `quoteId`, estimated output amount, and swap details
* Call `/aggregator/transaction` with the `quoteId` to receive the transaction data (to, data, value, gas parameters)
* Sign the transaction with the user's wallet
* Send the signed transaction to the network
* Wait for confirmation

**Alternative:** Instead of calling quote and transaction separately, you can use the `/aggregator/quote/transaction` endpoint to get both in a single request. Note that this combined endpoint does not provide gas estimation.

### Step 1 — Get a Quote

**Endpoint:**

* Method: `GET`
* Path: `/aggregator/quote`

**Required Parameters:**

| Parameter          | Description                                                                                                                                                                                               |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `network`          | Network name (e.g., `ethereum`, `polygon`, `bsc`, `arbitrum`, `base`). See the [network dropdown on Swagger](https://api.fly.trade/swagger#/Quotes/%2Faggregator%2Fquote) for all supported network names |
| `fromTokenAddress` | Token contract address to swap from. Use `0x0000000000000000000000000000000000000000` for native token (ETH/MATIC/BNB)                                                                                    |
| `toTokenAddress`   | Token contract address to swap to. Use `0x0000000000000000000000000000000000000000` for native token                                                                                                      |
| `sellAmount`       | Amount in smallest unit (wei). For 1 ETH write `1000000000000000000`                                                                                                                                      |
| `slippage`         | Allowed slippage (e.g., `0.005` for 0.5%, `0.01` for 1%)                                                                                                                                                  |
| `fromAddress`      | Wallet address initiating the swap                                                                                                                                                                        |
| `toAddress`        | Wallet address receiving the swapped tokens                                                                                                                                                               |
| `gasless`          | Must always be set to `false`. Gasless transactions are not supported — setting this to `true` will cause the quote to fail                                                                               |

**Optional Parameters**

**Affiliate Fee:**

* `affiliateAddress`: Address to receive affiliate fees
* `affiliateFee`: The fee percentage. For example: 1% = 0.01

Affiliate fees are deducted from the `fromTokenAddress` amount and sent to the specified address.

**Example Request:**

```
GET /aggregator/quote?network=ethereum&fromTokenAddress=0x....&toTokenAddress=0x....&fromAddress=0x....&toAddress=0x....&sellAmount=1000000000000000&slippage=0.005&gasless=false
```

**Example Response:**

{% code expandable="true" %}

```json
{
    "id": "0d180427-53d7-479b-a91d-c4ec88de1522",
    "amountOut": "2334548",
    "targetAddress": "0x....",
    "fees": [
        {
            "type": "gas",
            "value": "1.3746"
        }
    ],
    "resourceEstimate": {
        "gasLimit": "209993"
    },
    "typedData": {
        "types": {
            "Swap": [
                { "name": "router", "type": "address" },
                { "name": "sender", "type": "address" },
                { "name": "recipient", "type": "address" },
                { "name": "fromAsset", "type": "address" },
                { "name": "toAsset", "type": "address" },
                { "name": "deadline", "type": "uint256" },
                { "name": "amountOutMin", "type": "uint256" },
                { "name": "expectedAmountOut", "type": "uint256" },
                { "name": "consumerId", "type": "bytes32" },
                { "name": "maxRetentionBps", "type": "uint256" },
                { "name": "transferFromRouter", "type": "bool" }
            ]
        },
        "domain": {
            "name": "Dex Aggregator",
            "version": "1",
            "chainId": "1",
            "verifyingContract": "0x...."
        },
        "message": {
            "router": "0x....",
            "sender": "0x....",
            "recipient": "0x....",
            "fromAsset": "0x....",
            "toAsset": "0x....",
            "deadline": "1778165284",
            "amountOutMin": "2322875",
            "expectedAmountOut": "2334548",
            "consumerId": "0x....",
            "maxRetentionBps": "500",
            "transferFromRouter": false
        }
    }
}
```

{% endcode %}

Save the `id` as `quoteId` for Step 2.

**Important Quote Constraints:**

**Quote Expiry:** Each quote has a **5-minute expiration window**. After 5 minutes, the `quoteId` becomes invalid and you will not be able to fetch the transaction payload. If your quote has expired, you must request a fresh quote before proceeding.

**Single-Use Quotes:** Each quote can only be used to fetch the transaction payload **once**. After successfully calling `/aggregator/transaction` with a `quoteId`, that quote is consumed and cannot be reused. If you need to execute another swap or retry, you must fetch a new quote.

### Step 2 — Get Transaction Payload

**Endpoint:**

* Method: `GET`
* Path: `/aggregator/transaction`

**Required Parameters:**

| Parameter | Description                         |
| --------- | ----------------------------------- |
| `quoteId` | The quote `id` returned from Step 1 |

**Optional Parameters:**

| Parameter     | Description                                                                                                                                                                                 | Default |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `estimateGas` | Whether to estimate gas when building the transaction. If `true`, ensures token approval and balance checks are performed. If gas estimation fails, returns error `"Couldn't estimate gas"` | `true`  |

**Important:** When `estimateGas=true`, you must have:

* Sufficient token approval for the router contract (for ERC-20 swaps)
* Enough token balance for the swap
* Otherwise, the endpoint will return an error and fail to generate the transaction

**Example Request:**

```
GET /aggregator/transaction?quoteId=8de05661-09d6-4b82-ab63-1e32ee1fa297
```

**Example Response:**

```json
{
    "from": "0x....",
    "to": "0x....",
    "data": "0x....",
    "chainId": 1,
    "type": 2,
    "gasLimit": "314086",
    "maxFeePerGas": "228922087",
    "maxPriorityFeePerGas": "190605439",
    "value": "10000000000000000"
}
```

### Alternative: Combined Quote + Transaction Endpoint

For faster integration, you can fetch both quote and transaction data in a single request.

**Endpoint:**

* Method: `GET`
* Path: `/aggregator/quote/transaction`

**Parameters:**

Accepts all parameters from the `/aggregator/quote` endpoint (network, fromTokenAddress, toTokenAddress, sellAmount, slippage, fromAddress, toAddress). Always set `gasless=false`.

**Note:** `fromAddress` and `toAddress` are **required** for this endpoint and it doesn't estimate gas.

**Example Request:**

```
GET /aggregator/quote/transaction?network=ethereum&fromTokenAddress=0x0000000000000000000000000000000000000000&toTokenAddress=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&sellAmount=1000000000000000&slippage=0.005&gasless=false&toAddress=0x6820587e343da884230b6611d1802b92359e5a72&fromAddress=0x6820587e343da884230b6611d1802b92359e5a72
```

**Example Response:**

Returns both quote data and transaction payload in one response:

{% code expandable="true" %}

```json
{
  "quote": {
    "id": "4181b2b4-4b36-4641-9136-f6ce0dce4345",
    "amountOut": "2334548",
    "targetAddress": "0x....",
    "fees": [
      {
        "type": "gas",
        "value": "0.8261"
      }
    ],
    "resourceEstimate": {
      "gasLimit": "209993"
    },
    "typedData": {
      "types": {
        "Swap": [
          { "name": "router", "type": "address" },
          { "name": "sender", "type": "address" },
          { "name": "recipient", "type": "address" },
          { "name": "fromAsset", "type": "address" },
          { "name": "toAsset", "type": "address" },
          { "name": "deadline", "type": "uint256" },
          { "name": "amountOutMin", "type": "uint256" },
          { "name": "expectedAmountOut", "type": "uint256" },
          { "name": "consumerId", "type": "bytes32" },
          { "name": "maxRetentionBps", "type": "uint256" },
          { "name": "transferFromRouter", "type": "bool" }
        ]
      },
      "domain": {
        "name": "Dex Aggregator",
        "version": "1",
        "chainId": "1",
        "verifyingContract": "0x...."
      },
      "message": {
        "router": "0x....",
        "sender": "0x....",
        "recipient": "0x....",
        "fromAsset": "0x....",
        "toAsset": "0x....",
        "deadline": "1778166079",
        "amountOutMin": "2322875",
        "expectedAmountOut": "2334548",
        "consumerId": "0x....",
        "maxRetentionBps": "500",
        "transferFromRouter": false
      }
    }
  },
  "transaction": {
    "from": "0x....",
    "to": "0x....",
    "data": "0x....",
    "chainId": 1,
    "type": 2,
    "gasLimit": "0",
    "maxFeePerGas": "2542178902",
    "maxPriorityFeePerGas": "845661810",
    "value": "1000000000000000"
  }
}
```

{% endcode %}

### Updating Amount In (Optional)

After fetching transaction data from `/aggregator/transaction` or `/aggregator/quote/transaction`, you can dynamically override the `amountIn` before executing the transaction. This is useful when your integration manages token balances or allowances at execution time rather than at quote time.

**Two Modes:**

* **Specify an exact amount** — Pass any non-zero `uint256` value to set a precise `amountIn`.
* **Use balance or allowance (whichever is lower)** — Pass `0` as the `amountIn`. The contract will automatically use `min(balance, allowance)` at execution time.

**How It Works:**

The `DexAggregator` contract exposes a pure helper function `updateAmountIn`. You pass it the raw transaction `data` bytes returned by the API along with your desired `amountIn`, and it returns adjusted calldata ready to execute.

```solidity
function updateAmountIn(bytes memory data, uint256 amountIn) external pure returns (bytes memory) {
    assembly {
        mstore(add(data, 164), amountIn) // 32 + 132
    }
    return data;
}
```

**TypeScript Example:**

{% code expandable="true" %}

```typescript
import { ethers } from 'ethers';

async function executeSwapWithCustomAmount() {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const wallet = new ethers.Wallet(PRIVATE_KEY, provider);

  // Step 1: Fetch transaction data from the API
  const quoteUrl = new URL('https://api.fly.trade/aggregator/quote/transaction');
  quoteUrl.searchParams.set('network', 'ethereum');
  quoteUrl.searchParams.set('fromTokenAddress', '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'); // USDC
  quoteUrl.searchParams.set('toTokenAddress', '0x0000000000000000000000000000000000000000'); // ETH
  quoteUrl.searchParams.set('sellAmount', '1000000'); // 1 USDC
  quoteUrl.searchParams.set('slippage', '0.005');
  quoteUrl.searchParams.set('fromAddress', wallet.address);
  quoteUrl.searchParams.set('toAddress', wallet.address);
  quoteUrl.searchParams.set('gasless', 'false');

  const response = await fetch(quoteUrl.toString());
  const { transaction } = await response.json();

  // Step 2: Use updateAmountIn to override the amountIn
  const dexAggregator = new ethers.Contract(
    DEX_AGGREGATOR_ADDRESS,
    ['function updateAmountIn(bytes memory data, uint256 amountIn) external pure returns (bytes memory)'],
    wallet
  );

  // Option A: Set a specific amountIn (e.g. 500000 = 0.5 USDC)
  const adjustedData = await dexAggregator.updateAmountIn(transaction.data, '500000');

  // Option B: Use min(balance, allowance) automatically
  // const adjustedData = await dexAggregator.updateAmountIn(transaction.data, 0);

  // Step 3: Execute the swap with the adjusted calldata
  const tx = await wallet.sendTransaction({
    to: transaction.to,
    data: adjustedData,
    value: transaction.value,
    gasLimit: transaction.gasLimit,
  });

  const receipt = await tx.wait();
  console.log('Swap executed with custom amountIn:', receipt.transactionHash);
}
```

{% endcode %}

### Step 3 — Execute the Swap Transaction

Once you have the transaction payload (from Step 2 or the combined endpoint), you can execute it using your preferred web3 library.

**Important:** Depending on the blockchain, the transaction response may use EIP-1559 format with `maxFeePerGas` and `maxPriorityFeePerGas` (if the chain supports EIP-1559) or legacy format with `gasPrice`. Make sure your wallet/library supports both transaction types.

#### Integration Examples

**Example 1: Frontend Integration (React + Ethers.js)**

Example for integrating swaps in a React frontend application:

{% code expandable="true" %}

```typescript
import { useState } from 'react';
import { ethers } from 'ethers';

function SwapComponent() {
  const [loading, setLoading] = useState(false);
  const [status, setStatus] = useState('');

  async function handleSwap() {
    if (!window.ethereum) {
      alert('Please install MetaMask');
      return;
    }

    setLoading(true);
    setStatus('Connecting wallet...');

    try {
      // Connect wallet
      const provider = new ethers.providers.Web3Provider(window.ethereum);
      await provider.send('eth_requestAccounts', []);
      const signer = provider.getSigner();
      const userAddress = await signer.getAddress();

      // Swap parameters
      const fromToken = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; // USDC
      const toToken = '0x0000000000000000000000000000000000000000'; // ETH
      const amount = '1000000'; // 1 USDC (6 decimals)

      // Check and approve if needed (ERC-20 only)
      if (fromToken !== '0x0000000000000000000000000000000000000000') {
        setStatus('Checking token approval...');
        
        // Get router address from a sample quote
        const sampleQuoteUrl = `https://api.fly.trade/aggregator/quote?network=ethereum&fromTokenAddress=${fromToken}&toTokenAddress=${toToken}&sellAmount=${amount}&slippage=0.005&fromAddress=${userAddress}&toAddress=${userAddress}&gasless=false`;
        const sampleQuoteRes = await fetch(sampleQuoteUrl);
        const sampleQuote = await sampleQuoteRes.json();
        const routerAddress = sampleQuote.targetAddress;

        // Check allowance
        const tokenContract = new ethers.Contract(
          fromToken,
          ['function allowance(address,address) view returns (uint256)', 'function approve(address,uint256) returns (bool)'],
          signer
        );
        const allowance = await tokenContract.allowance(userAddress, routerAddress);

        if (allowance.lt(amount)) {
          setStatus('Approving tokens...');
          const approveTx = await tokenContract.approve(routerAddress, ethers.constants.MaxUint256);
          await approveTx.wait();
          setStatus('Approval confirmed');
        }
      }

      // Get quote and transaction in one call
      setStatus('Fetching quote...');
      const quoteUrl = new URL('https://api.fly.trade/aggregator/quote/transaction');
      quoteUrl.searchParams.set('network', 'ethereum');
      quoteUrl.searchParams.set('fromTokenAddress', fromToken);
      quoteUrl.searchParams.set('toTokenAddress', toToken);
      quoteUrl.searchParams.set('sellAmount', amount);
      quoteUrl.searchParams.set('slippage', '0.005');
      quoteUrl.searchParams.set('fromAddress', userAddress);
      quoteUrl.searchParams.set('toAddress', userAddress);
      quoteUrl.searchParams.set('gasless', 'false');

      const response = await fetch(quoteUrl.toString());
      if (!response.ok) {
        const error = await response.text();
        throw new Error(error);
      }

      const { quote, transaction } = await response.json();
      
      setStatus(`Swapping for ${ethers.utils.formatUnits(quote.amountOut, 18)} ETH...`);

      // Execute swap
      const tx = await signer.sendTransaction({
        to: transaction.to,
        data: transaction.data,
        value: transaction.value,
        gasLimit: transaction.gasLimit,
      });

      setStatus('Transaction sent. Waiting for confirmation...');
      const receipt = await tx.wait();

      setStatus(`✅ Swap completed! Tx: ${receipt.transactionHash}`);
      
    } catch (error) {
      console.error('Swap failed:', error);
      setStatus(`❌ Error: ${error.message}`);
    } finally {
      setLoading(false);
    }
  }

  return (
    <div>
      <button onClick={handleSwap} disabled={loading}>
        {loading ? 'Processing...' : 'Swap 1 USDC → ETH'}
      </button>
      {status && <p>{status}</p>}
    </div>
  );
}

export default SwapComponent;
```

{% endcode %}

**Example 2: Smart Contract Integration**

If you're building a smart contract that needs to execute swaps, here's how to integrate:

**Solidity Contract Example:**

{% code expandable="true" %}

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC20 {
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

contract MySwapContract {
    address public flyRouter = 0xa6e941eab67569ca4522f70d343714ff51d571c4;
    
    // Execute swap by calling Fly Trade router with transaction data
    function executeSwap(
        address fromToken,
        uint256 amountIn,
        bytes calldata swapCallData
    ) external payable returns (bool) {
        // If swapping ERC-20 token, transfer it from user and approve router
        if (fromToken != address(0)) {
            require(
                IERC20(fromToken).transferFrom(msg.sender, address(this), amountIn),
                "Transfer failed"
            );
            require(
                IERC20(fromToken).approve(flyRouter, amountIn),
                "Approval failed"
            );
        }
        
        // Execute swap by calling the router with the provided calldata
        (bool success, ) = flyRouter.call{value: msg.value}(swapCallData);
        require(success, "Swap failed");
        
        return true;
    }
    
    // Allow contract to receive ETH
    receive() external payable {}
}
```

{% endcode %}

**Off-chain Script to Call Contract:**

{% code expandable="true" %}

```typescript
import { ethers } from 'ethers';

async function executeContractSwap() {
  const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
  const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
  
  // Your contract instance
  const mySwapContract = new ethers.Contract(
    CONTRACT_ADDRESS,
    ['function executeSwap(address,uint256,bytes) payable returns (bool)'],
    wallet
  );
  
  // Step 1: Get quote and transaction data from Fly Trade API
  const quoteUrl = new URL('https://api.fly.trade/aggregator/quote/transaction');
  quoteUrl.searchParams.set('network', 'ethereum');
  quoteUrl.searchParams.set('fromTokenAddress', '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'); // USDC
  quoteUrl.searchParams.set('toTokenAddress', '0x0000000000000000000000000000000000000000'); // ETH
  quoteUrl.searchParams.set('sellAmount', '1000000'); // 1 USDC
  quoteUrl.searchParams.set('slippage', '0.005');
  quoteUrl.searchParams.set('fromAddress', CONTRACT_ADDRESS); // Your contract address
  quoteUrl.searchParams.set('toAddress', wallet.address); // User receives tokens
  quoteUrl.searchParams.set('gasless', 'false');
  quoteUrl.searchParams.set('estimateGas', 'false'); // Disable gas estimation for contracts
  
  const response = await fetch(quoteUrl.toString());
  const { quote, transaction } = await response.json();
  
  // Step 2: Approve USDC for your contract (if user hasn't already)
  const usdcContract = new ethers.Contract(
    '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
    ['function approve(address,uint256) returns (bool)'],
    wallet
  );
  
  const approveTx = await usdcContract.approve(CONTRACT_ADDRESS, '1000000');
  await approveTx.wait();
  
  // Step 3: Call your contract's executeSwap function with the transaction calldata
  const swapTx = await mySwapContract.executeSwap(
    '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // fromToken (USDC)
    '1000000', // amountIn
    transaction.data, // calldata from Fly Trade
    { value: 0 } // msg.value (0 for ERC-20 → ETH swap)
  );
  
  const receipt = await swapTx.wait();
  console.log('Swap executed:', receipt.transactionHash);
}
```

{% endcode %}

**Important Notes for Contract Integration:**

* Set `estimateGas=false` when getting transaction data for contract calls
* The contract must approve the Fly router before swapping ERC-20 tokens
* Pass the transaction data from Fly Trade API directly to the router as calldata

***

### Network Support

Fly Trade supports swaps on multiple EVM networks. You must use the **network name** (not chain ID) in API requests:

For the complete and up-to-date list of supported network names, refer to the network dropdown in our [Swagger documentation](https://api.fly.trade/swagger#/Quotes/%2Faggregator%2Fquote) for `aggregator/quote` endpoint or get it from `/token-manager/networks` endpoint.

***

## Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.fly.trade/developers/api-reference/evm-swap-integration-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language. The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.fly.trade/developers/api-reference/evm-swap-integration-guide.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
