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

# Swap tokens and bridge assets

> Get quotes and ready-to-sign transactions for same-chain swaps and cross-chain bridges across EVM chains and Solana with Zerion API swap endpoints.

**What you'll build:**

* Get swap quotes from multiple liquidity sources in a single call
* Receive ready-to-sign approve and swap transactions without separate quote/execute steps
* Bridge assets across chains (EVM ↔ EVM, EVM ↔ Solana)

```
Swap 0.05 ETH → DAI on Ethereum

  Quote 1 (Relay):
    Output:           128.45 DAI
    Min after slip:   125.88 DAI
    Net of fees:      $128.10
    ✅ Transaction ready to sign

  Quote 2 (LI.FI):
    Output:           128.12 DAI
    Min after slip:   125.56 DAI
    Net of fees:      $127.78
    ✅ Transaction ready to sign
```

**Time:** \~15 minutes

## Prerequisites

* A Zerion API key ([get one here](https://dashboard.zerion.io))
* A wallet address with tokens to swap

## How it works

Unlike most swap APIs that require separate quote and execute endpoints, Zerion API combines both into a **single call**:

1. Call `GET /v1/swap/quotes/` with the sender, recipient, input asset, output asset, and amount
2. Receive multiple quotes. Each includes the expected output, fees, and (when executable) ready-to-sign `transaction_approve` and `transaction_swap` payloads
3. Pick a quote and sign the transactions with your wallet

The API returns quotes best-first, sorted by the fiat value of `output_amount_after_fees`.

<Steps>
  <Step title="Get swap quotes">
    Use the [swap and bridge quotes](/api-reference/swap/get-swap-and-bridge-quotes) endpoint.

    <CodeGroup>
      ```javascript JavaScript theme={null}
      const API_KEY = process.env.ZERION_API_KEY;
      const headers = {
        accept: "application/json",
        authorization: `Basic ${btoa(API_KEY + ":")}`,
      };

      // Swap 0.05 ETH → DAI on Ethereum
      const wallet = "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B";
      const params = new URLSearchParams({
        from: wallet,
        to: wallet,
        "input[chain_id]": "ethereum",
        "input[fungible_id]": "eth",
        "input[amount]": "0.05", // human-readable decimal, not wei
        "output[fungible_id]": "0x6B175474E89094C44Da98b954EedeAC495271d0F", // DAI
      });

      const response = await fetch(
        `https://api.zerion.io/v1/swap/quotes/?${params}`,
        { headers }
      );
      const { data } = await response.json();

      for (const quote of data) {
        const {
          liquidity_source,
          output_amount,
          minimum_output_amount,
          output_amount_after_fees,
          network_fee,
          error,
        } = quote.attributes;
        console.log(`\nQuote from ${liquidity_source.name}:`);
        console.log(`  Output:         ${output_amount.quantity}`);
        console.log(`  Min after slip: ${minimum_output_amount.quantity}`);
        console.log(`  Net of fees:    ${output_amount_after_fees.value} ${output_amount_after_fees.currency ?? ""}`);
        console.log(`  Network fee:    ${network_fee?.amount?.value ?? "n/a"}`);
        if (error) console.log(`  ⚠️  ${error.code}: ${error.message ?? error.hint}`);
      }
      ```

      ```python Python theme={null}
      import os, requests

      api_key = os.environ["ZERION_API_KEY"]
      wallet = "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"

      # Swap 0.05 ETH → DAI on Ethereum
      response = requests.get(
          "https://api.zerion.io/v1/swap/quotes/",
          params={
              "from": wallet,
              "to": wallet,
              "input[chain_id]": "ethereum",
              "input[fungible_id]": "eth",
              "input[amount]": "0.05",  # human-readable decimal, not wei
              "output[fungible_id]": "0x6B175474E89094C44Da98b954EedeAC495271d0F",  # DAI
          },
          auth=(api_key, ""),
      )

      for quote in response.json()["data"]:
          attrs = quote["attributes"]
          print(f"\nQuote from {attrs['liquidity_source']['name']}:")
          print(f"  Output:         {attrs['output_amount']['quantity']}")
          print(f"  Min after slip: {attrs['minimum_output_amount']['quantity']}")
          after_fees = attrs["output_amount_after_fees"]
          print(f"  Net of fees:    {after_fees.get('value')} {after_fees.get('currency', '')}")
          if "error" in attrs:
              err = attrs["error"]
              print(f"  Note: {err['code']} - {err.get('message') or err['hint']}")
      ```

      ```bash cURL theme={null}
      curl -g -u "YOUR_API_KEY:" \
        "https://api.zerion.io/v1/swap/quotes/?from=0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B&to=0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B&input[chain_id]=ethereum&input[fungible_id]=eth&input[amount]=0.05&output[fungible_id]=0x6B175474E89094C44Da98b954EedeAC495271d0F"
      ```
    </CodeGroup>

    Each quote includes:

    * `liquidity_source` - provider id, display name, and icon
    * `input_amount` / `output_amount` - what you're sending and the estimated amount you'll receive before slippage (`quantity` is the human-readable decimal; `value` / `usd_value` are fiat conversions when available)
    * `minimum_output_amount` - guaranteed output after slippage
    * `output_amount_after_fees` - net output once protocol, bridge, and network fees are subtracted; this is the field quotes are ranked by
    * `slippage_percent` - actual slippage applied (the `slippage_percent` query parameter when provided, otherwise auto-chosen)
    * `protocol_fee` / `bridge_fee` / `network_fee` - fee breakdown; each carries `amount` and an `included_in_rate` flag (whether already deducted from the rate)
    * `transaction_approve` - ERC-20 approve transaction to sign first. Absent when not needed (Solana, EVM native-asset input, or sufficient allowance already granted)
    * `transaction_swap` - swap transaction to sign. Absent when the quote is informational only (e.g., `error` is set)
    * `error` - present when the quote can't be executed as-is (e.g., `not_enough_input_asset_balance`)
    * `estimated_time_seconds` - typically present on bridge routes
  </Step>

  <Step title="Pick a quote and check for errors">
    The API returns quotes best-first. Before signing, check whether the chosen quote has an `error` and a `transaction_swap`.

    ```javascript theme={null}
    const quote = data[0]; // best by output_amount_after_fees
    const { error, transaction_approve, transaction_swap } = quote.attributes;

    if (error) {
      console.log(`Cannot execute: ${error.code} (hint: ${error.hint})`);
      process.exit(1);
    }
    if (!transaction_swap) {
      console.log("No transaction payload - informational quote only");
      process.exit(1);
    }
    ```

    <Note>
      `error.hint` is a machine-readable next step (`topup`, `increase_input_amount`, `unspecified`) you can surface in your UI to guide the user.
    </Note>
  </Step>

  <Step title="Sign the approve transaction (if present)">
    If `transaction_approve` is present, sign and confirm it before submitting the swap. It's a complete EVM transaction, so you don't need a manual `approve(spender, amount)` call.

    ```javascript theme={null}
    import { ethers } from "ethers";

    const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
    const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

    async function sendEVM(payload) {
      // EVM numeric fields are hex strings - ethers accepts them directly
      const tx = await signer.sendTransaction({
        type: parseInt(payload.type, 16),
        to: payload.to,
        data: payload.data,
        value: payload.value,
        gasLimit: payload.gas,
        nonce: parseInt(payload.nonce, 16),
        chainId: parseInt(payload.chain_id, 16),
        ...(payload.max_fee
          ? { maxFeePerGas: payload.max_fee, maxPriorityFeePerGas: payload.max_priority_fee }
          : { gasPrice: payload.gas_price }),
      });
      return tx.wait();
    }

    if (transaction_approve?.evm) {
      console.log("Sending approval...");
      await sendEVM(transaction_approve.evm);
      console.log("Approval confirmed");
    }
    ```

    <Note>
      `transaction_approve` is omitted for Solana, for EVM swaps where the input is the chain's native asset (ETH, MATIC, etc.), and when the wallet already has sufficient allowance.
    </Note>
  </Step>

  <Step title="Sign and send the swap transaction">
    ```javascript theme={null}
    const receipt = await sendEVM(transaction_swap.evm);
    console.log(`Confirmed in block ${receipt.blockNumber}`);
    ```

    For Solana quotes, `transaction_swap.solana.raw` is a base64-encoded raw transaction that you decode and sign with the Solana wallet of your choice (e.g., `@solana/web3.js`).
  </Step>

  <Step title="Bridge assets across chains">
    To bridge tokens between chains, set a different `output[chain_id]`. The `to` parameter is the recipient on the destination chain. Set it to your wallet on that chain (use a base58 Solana address when bridging to Solana).

    ```bash theme={null}
    # Bridge 1 USDC from Ethereum to Polygon
    curl -g -u "YOUR_API_KEY:" \
      "https://api.zerion.io/v1/swap/quotes/?from=YOUR_WALLET&to=YOUR_WALLET&input[chain_id]=ethereum&input[fungible_id]=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&input[amount]=1&output[chain_id]=polygon&output[fungible_id]=0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
    ```

    <Tip>
      Use the [List fungibles available for bridging](/api-reference/swap/list-fungibles-available-for-bridging) endpoint to discover which tokens can be bridged between two specific chains before requesting quotes.
    </Tip>
  </Step>
</Steps>

## Key parameters

| Parameter | Description |
| - | - |
| `from` | Wallet performing the swap. Must match the chain type of `input[chain_id]` (EVM hex or Solana base58). Required. |
| `to` | Recipient of the output asset. Must match the chain type of `output[chain_id]`. For a same-chain swap to the same wallet, set equal to `from`. Required. |
| `input[chain_id]` | Source chain (e.g., `ethereum`, `polygon`, `solana`). Required. |
| `input[fungible_id]` | Input asset's fungible ID - either a Zerion fungible ID (e.g., `eth`) or the token contract address. Required. |
| `input[amount]` | Human-readable input amount as a decimal string (e.g., `"0.05"`), not the smallest unit. Required. |
| `output[chain_id]` | Destination chain. Defaults to `input[chain_id]` (same-chain swap). |
| `output[fungible_id]` | Output asset's fungible ID or token contract address. Required. |
| `slippage_percent` | Maximum acceptable slippage in percent. Auto-chosen when omitted. |
| `currency` | Currency for fiat values in the response (e.g., `usd`). Defaults to `usd`. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.