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

# Cross-chain swap end-to-end

> Check chain support, quote a route, build and send the source transaction, and track the transfer until it lands.

A cross-chain swap is one transaction on the source chain. A cross-chain provider picked by Olympex then delivers the output token on the destination chain. This guide sends 10 USDT from Polygon and receives USDT on Ethereum: check both chains, quote, build the calldata, approve, send, and track the transfer with `POST /tx-status`.

<Info>
  You need API credentials and the signing helper from [Sign requests](/authentication/sign-requests), and a wallet on the source chain that holds the input token and native token for gas. [Execute a swap](/guides/execute-a-swap) explains the single-chain version of the wallet steps.
</Info>

## Prerequisites

* `OLYMPEX_API_KEY_ID`, `OLYMPEX_SECRET_KEY` and `OLYMPEX_PASSPHRASE` in your server's environment, and `sign-request.ts` (or `sign_request.py`) saved next to your code.
* Node.js 22.18 or later, which runs `.ts` files directly, in an ES module project (`npm pkg set type=module`), viem 2 (`npm install viem`) and an RPC URL for the source chain, as `POLYGON_RPC_URL` here.
* `wait-for-transfer.ts` from [Track a swap to finality](/guides/track-a-swap-to-finality), for the last step.
* A wallet funded on the source chain with the input token (10 USDT) and POL for gas. The script reads its private key from `WALLET_PRIVATE_KEY`.

## Steps

<Warning>
  The calldata from `POST /swap` is real mainnet calldata, and broadcasting it moves funds across chains. Olympex has no testnet environment, so test with a small amount from a dedicated wallet.
</Warning>

<Steps>
  <Step title="Check both chains">
    The TypeScript snippets in these steps form one script, `cross-chain-swap.ts`. Run it with `node cross-chain-swap.ts`.

    [`POST /support-chain`](/api-reference/chains/check-chain-support) returns `data: true` when Olympex supports a chain. Send `chainId` as an integer, as on every endpoint; a string returns `400 VALIDATION_ERROR`.

    <CodeGroup>
      ```bash cURL theme={null}
      # Check that Olympex supports Polygon (137). Repeat with 1 for Ethereum.
      # Needs openssl, curl and OLYMPEX_API_KEY_ID, OLYMPEX_SECRET_KEY, OLYMPEX_PASSPHRASE.
      METHOD=POST
      ENDPOINT=/support-chain
      QUERY=''
      BODY='{"chainId":137}'
      TS="${OLYMPEX_TIMESTAMP:-$(date +%s)}"
      NONCE="${OLYMPEX_NONCE:-$(openssl rand -hex 12)}"
      BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')"
      VALUE_INFO="$(printf '%s\n%s\n%s' "$TS" "$NONCE" "$BODY_HASH")"
      MESSAGE="$(printf 'OLPX-HMAC-SHA256-V2\n%s\n%s\n%s\n%s\n%s\n%s' "$TS" "$NONCE" "$METHOD" "/api/v1$ENDPOINT" "$QUERY" "$BODY_HASH")"
      curl -sS -X "$METHOD" "https://api-rest.olympex.io/api/v1$ENDPOINT${QUERY:+?$QUERY}" \
        -H "content-type: application/json" \
        -H "x-api-key-id: $OLYMPEX_API_KEY_ID" \
        -H "x-value-info: $(printf '%s' "$VALUE_INFO" | openssl base64 -A)" \
        -H "x-passphrase: $OLYMPEX_PASSPHRASE" \
        -H "x-signature: $(printf '%s' "$MESSAGE" | openssl dgst -sha256 -hmac "$OLYMPEX_SECRET_KEY" -binary | od -An -v -tx1 | tr -d ' \n')" \
        --data-raw "$BODY"
      ```

      ```ts TypeScript theme={null}
      // cross-chain-swap.ts. Run: node cross-chain-swap.ts
      import { olympexRequest } from "./sign-request.ts"; // /authentication/sign-requests

      const [sourceOk, destinationOk] = await Promise.all([
        olympexRequest<boolean>("POST", "/support-chain", { chainId: 137 }), // Polygon
        olympexRequest<boolean>("POST", "/support-chain", { chainId: 1 }), // Ethereum
      ]);
      if (!sourceOk || !destinationOk) throw new Error("Olympex doesn't support one of these chains");
      ```

      ```python Python theme={null}
      from sign_request import olympex_request  # /authentication/sign-requests

      for chain_id in (137, 1):  # Polygon, Ethereum
          if not olympex_request("POST", "/support-chain", {"chainId": chain_id}):
              raise RuntimeError(f"Olympex doesn't support chain {chain_id}")
      ```
    </CodeGroup>

    ```json Response theme={null}
    {
      "success": true,
      "data": true,
      "meta": {
        "requestId": "ENUlxjCsIAMEMUA=",
        "version": "v1",
        "accountType": "integrator",
        "apiKeyId": "00000000-0000-4000-8000-000000000000"
      }
    }
    ```

    A supported chain isn't always a valid source. Cross-chain transfers can start on Ethereum (`1`), Optimism (`10`), BNB Chain (`56`), Polygon (`137`), Arbitrum (`42161`) and Avalanche (`43114`), and whether a specific pair has a route depends on the providers at the time: the quote in the next step confirms it. See [Supported chains](/concepts/supported-chains). Chain support changes rarely, so cache the result and refresh it periodically instead of checking before every transfer.
  </Step>

  <Step title="Quote the route">
    Cross-chain [`POST /quotes`](/api-reference/quotes/get-quote) bodies take `fromChainId`, `toChainId`, `inTokenAddress` and `outTokenAddress`, and no `gasPrice`. Unknown top-level keys are rejected with `400`. If you charge an integrator fee, add the same top-level `fees` object here and on the swap; for cross-chain routes, the amounts in `integratorFeeBreakdown` are in base units of the source token.

    <CodeGroup>
      ```bash cURL theme={null}
      # Cross-chain quote: 10 USDT on Polygon to USDT on Ethereum.
      # Needs openssl, curl and OLYMPEX_API_KEY_ID, OLYMPEX_SECRET_KEY, OLYMPEX_PASSPHRASE.
      METHOD=POST
      ENDPOINT=/quotes
      QUERY=''
      BODY='{"mode":"cross-chain","params":{"amount":"10","fromChainId":137,"inTokenAddress":"0xc2132d05d31c914a87c6611c10748aeb04b58e8f","outTokenAddress":"0xdac17f958d2ee523a2206206994597c13d831ec7","slippage":"1","toChainId":1}}'
      TS="${OLYMPEX_TIMESTAMP:-$(date +%s)}"
      NONCE="${OLYMPEX_NONCE:-$(openssl rand -hex 12)}"
      BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')"
      VALUE_INFO="$(printf '%s\n%s\n%s' "$TS" "$NONCE" "$BODY_HASH")"
      MESSAGE="$(printf 'OLPX-HMAC-SHA256-V2\n%s\n%s\n%s\n%s\n%s\n%s' "$TS" "$NONCE" "$METHOD" "/api/v1$ENDPOINT" "$QUERY" "$BODY_HASH")"
      curl -sS -X "$METHOD" "https://api-rest.olympex.io/api/v1$ENDPOINT${QUERY:+?$QUERY}" \
        -H "content-type: application/json" \
        -H "x-api-key-id: $OLYMPEX_API_KEY_ID" \
        -H "x-value-info: $(printf '%s' "$VALUE_INFO" | openssl base64 -A)" \
        -H "x-passphrase: $OLYMPEX_PASSPHRASE" \
        -H "x-signature: $(printf '%s' "$MESSAGE" | openssl dgst -sha256 -hmac "$OLYMPEX_SECRET_KEY" -binary | od -An -v -tx1 | tr -d ' \n')" \
        --data-raw "$BODY"
      ```

      ```ts TypeScript theme={null}
      type CrossChainQuote = {
        aggregatorId: string; // "okx" or "rango" for signed API accounts; "rango" in the example below
        toTokenAmount: string; // base units of the destination token
        minimumReceived: string; // base units of the destination token
        estimateCostInUSD: string;
        estimatedTime?: string | null;
        bridgeInfo?: { displayName: string; icon: string };
      };

      const transfer = {
        fromChainId: 137, // Polygon
        toChainId: 1, // Ethereum
        tokenIn: "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", // USDT on Polygon
        tokenOut: "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT on Ethereum
        amount: "10", // human-readable units of the input token
        slippage: "1", // percent
      } as const;

      const { quote } = await olympexRequest<{ mode: "cross-chain"; quote: CrossChainQuote }>("POST", "/quotes", {
        mode: "cross-chain",
        params: {
          fromChainId: transfer.fromChainId,
          toChainId: transfer.toChainId,
          inTokenAddress: transfer.tokenIn,
          outTokenAddress: transfer.tokenOut,
          amount: transfer.amount,
          slippage: transfer.slippage,
        },
      });
      console.log(`${quote.aggregatorId} via ${quote.bridgeInfo?.displayName}: at least ${quote.minimumReceived} base units`);
      ```

      ```python Python theme={null}
      data = olympex_request("POST", "/quotes", {
          "mode": "cross-chain",
          "params": {
              "fromChainId": 137,  # Polygon
              "toChainId": 1,  # Ethereum
              "inTokenAddress": "0xc2132d05d31c914a87c6611c10748aeb04b58e8f",  # USDT on Polygon
              "outTokenAddress": "0xdac17f958d2ee523a2206206994597c13d831ec7",  # USDT on Ethereum
              "amount": "10",
              "slippage": "1",
          },
      })
      quote = data["quote"]
      print(quote["aggregatorId"], quote["bridgeInfo"]["displayName"], quote["minimumReceived"])
      ```
    </CodeGroup>

    A response looks like this:

    <Accordion title="Response">
      ```json theme={null}
      {
        "success": true,
        "data": {
          "mode": "cross-chain",
          "quote": {
            "aggregatorId": "rango",
            "estimatedGas": "162807827210550000",
            "estimateCostInUSD": "0.020004848960669123",
            "fromTokenAmount": "10",
            "toTokenAmount": "9985000",
            "minimumReceived": "9885149",
            "bridgeInfo": {
              "icon": "https://raw.githubusercontent.com/rango-exchange/assets/main/swappers/BOB/icon.svg",
              "displayName": "BOB Gateway"
            },
            "middlewareRoute": {
              "chainFrom": [],
              "chainTo": [
                {
                  "fromAsset": {
                    "address": "0xc2132d05d31c914a87c6611c10748aeb04b58e8f",
                    "decimals": 6,
                    "symbol": "USDT"
                  },
                  "toAsset": {
                    "address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
                    "decimals": 6,
                    "symbol": "USDT"
                  }
                }
              ]
            },
            "integratorFeeBreakdown": {
              "protocolFeeBps": 15,
              "integratorMarginBps": 0,
              "protocolFeeAmount": "15000",
              "integratorMarginAmount": "0"
            }
          }
        },
        "meta": {
          "requestId": "EduMjjALIAMEP4Q=",
          "version": "v1",
          "accountType": "integrator",
          "apiKeyId": "00000000-0000-4000-8000-000000000000"
        }
      }
      ```
    </Accordion>

    | Field | Meaning |
    | - | - |
    | `aggregatorId` | The cross-chain provider. Signed API accounts receive `okx` or `rango`. Pass it to `POST /swap`. |
    | `toTokenAmount`, `minimumReceived` | Expected and minimum amount on the destination chain, in **base units** of the destination token. `"9885149"` of USDT is 9.885149 USDT. |
    | `fromTokenAmount` | Your input amount, echoed in human-readable units. |
    | `estimateCostInUSD` | The provider's estimate of the transfer's cost in USD. What it includes varies by provider. |
    | `estimatedTime` | The provider's time estimate, when it reports one. |
    | `bridgeInfo` | The bridge's `displayName` and `icon`. The icon can be empty. |
    | `middlewareRoute` | Swaps on the source (`chainFrom`) and destination (`chainTo`) chains around the bridge. A native token can appear here as `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` or as the zero address, depending on the provider: treat both as native, and compare addresses case-insensitively. |
    | `estimatedGas` | The provider's estimate. Its unit depends on the provider, so use it for display only. |
    | `integratorFeeBreakdown` | The Olympex protocol fee and your integrator fee, in base units of the source token. `protocolFeeBps` is in basis points: `15` is 0.15%, and `"15000"` of USDT is 0.015 USDT. |

    Before the user confirms, show the bridge name, `minimumReceived` in the destination token's decimals and, when present, `estimatedTime`. [Cross-chain mechanics](/concepts/cross-chain-mechanics#amounts-costs-and-timing) explains each amount.
  </Step>

  <Step title="Build the transaction">
    The cross-chain `POST /swap` body repeats the quote's `params` and adds `account`, the wallet that sends on the source chain and receives on the destination chain, and the quote's `aggregatorId`. Don't send `dryRun`: cross-chain requests reject it with `400`.

    <CodeGroup>
      ```bash cURL theme={null}
      # Build a cross-chain swap: 10 USDT on Polygon to USDT on Ethereum, with the quote's aggregatorId. Returns calldata; nothing is broadcast.
      # Needs openssl, curl and OLYMPEX_API_KEY_ID, OLYMPEX_SECRET_KEY, OLYMPEX_PASSPHRASE.
      METHOD=POST
      ENDPOINT=/swap
      QUERY=''
      BODY='{"mode":"cross-chain","params":{"account":"0x1E67cb01969D79B2B895179e4A07D24a839dBb52","aggregatorId":"rango","amount":"10","fromChainId":137,"inTokenAddress":"0xc2132d05d31c914a87c6611c10748aeb04b58e8f","outTokenAddress":"0xdac17f958d2ee523a2206206994597c13d831ec7","slippage":"1","toChainId":1}}'
      TS="${OLYMPEX_TIMESTAMP:-$(date +%s)}"
      NONCE="${OLYMPEX_NONCE:-$(openssl rand -hex 12)}"
      BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')"
      VALUE_INFO="$(printf '%s\n%s\n%s' "$TS" "$NONCE" "$BODY_HASH")"
      MESSAGE="$(printf 'OLPX-HMAC-SHA256-V2\n%s\n%s\n%s\n%s\n%s\n%s' "$TS" "$NONCE" "$METHOD" "/api/v1$ENDPOINT" "$QUERY" "$BODY_HASH")"
      curl -sS -X "$METHOD" "https://api-rest.olympex.io/api/v1$ENDPOINT${QUERY:+?$QUERY}" \
        -H "content-type: application/json" \
        -H "x-api-key-id: $OLYMPEX_API_KEY_ID" \
        -H "x-value-info: $(printf '%s' "$VALUE_INFO" | openssl base64 -A)" \
        -H "x-passphrase: $OLYMPEX_PASSPHRASE" \
        -H "x-signature: $(printf '%s' "$MESSAGE" | openssl dgst -sha256 -hmac "$OLYMPEX_SECRET_KEY" -binary | od -An -v -tx1 | tr -d ' \n')" \
        --data-raw "$BODY"
      ```

      ```ts TypeScript theme={null}
      import { createPublicClient, createWalletClient, erc20Abi, http, parseUnits, type Address, type Hex } from "viem";
      import { privateKeyToAccount } from "viem/accounts";
      import { polygon } from "viem/chains";

      type CrossChainSwap = {
        to: Address;
        calldata: Hex; // named `calldata`, not `data`
        value: string; // wei
        contractToApprove: Address;
        dexHash: Hex; // identifies the provider for POST /tx-status
        gasLimit: string;
      };

      const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as Hex);

      const buildSwap = async () =>
        (
          await olympexRequest<{ mode: "cross-chain"; swap: CrossChainSwap }>("POST", "/swap", {
            mode: "cross-chain",
            params: {
              account: account.address,
              aggregatorId: quote.aggregatorId,
              fromChainId: transfer.fromChainId,
              toChainId: transfer.toChainId,
              inTokenAddress: transfer.tokenIn,
              outTokenAddress: transfer.tokenOut,
              amount: transfer.amount,
              slippage: transfer.slippage,
            },
          })
        ).swap;

      let swap = await buildSwap();
      ```

      ```python Python theme={null}
      data = olympex_request("POST", "/swap", {
          "mode": "cross-chain",
          "params": {
              "account": "0x1E67cb01969D79B2B895179e4A07D24a839dBb52",  # sends on Polygon, receives on Ethereum
              "aggregatorId": quote["aggregatorId"],  # "rango" in the example quote
              "fromChainId": 137,
              "toChainId": 1,
              "inTokenAddress": "0xc2132d05d31c914a87c6611c10748aeb04b58e8f",  # USDT on Polygon
              "outTokenAddress": "0xdac17f958d2ee523a2206206994597c13d831ec7",  # USDT on Ethereum
              "amount": "10",
              "slippage": "1",
          },
      })
      swap = data["swap"]
      print(swap["to"], swap["contractToApprove"], swap["dexHash"])
      ```
    </CodeGroup>

    A response looks like this, with the calldata shortened:

    <Accordion title="Response">
      ```json theme={null}
      {
        "success": true,
        "data": {
          "mode": "cross-chain",
          "swap": {
            "to": "0xA91e13e8BfEbbC57ea008bfb2dE94AdD3F484E68",
            "gasLimit": "323438069024099968",
            "calldata": "0x0044fcef0000000000000000000000000000000000000000000000000000000000000020…0000000000000000",
            "value": "0",
            "estimatedGas": "161719034512049984",
            "contractToApprove": "0x9f770bC130f57C2D1C7Bf9bb4965Ab7c79AdE22E",
            "dexHash": "0x2676e2ee99986d45baa8242aea50a63a90b996df49a22e8975ba756b3706279f"
          }
        },
        "meta": {
          "requestId": "EduNEjxWIAMEZew=",
          "version": "v1",
          "accountType": "integrator",
          "apiKeyId": "00000000-0000-4000-8000-000000000000"
        }
      }
      ```
    </Accordion>

    The transaction goes to `to`, the Olympex aggregator contract on the source chain, with `calldata` as its data and `value` in wei. Keep `dexHash`: `POST /tx-status` needs it to find the transfer.
  </Step>

  <Step title="Approve the exact input amount">
    For ERC-20 input, approve `contractToApprove` for exactly the input amount on the source chain, in base units. If you sent an approval, build the swap again once it confirms, so the calldata's expiry window starts after the approval.

    ```ts theme={null}
    const publicClient = createPublicClient({ chain: polygon, transport: http(process.env.POLYGON_RPC_URL) });
    const walletClient = createWalletClient({ account, chain: polygon, transport: http(process.env.POLYGON_RPC_URL) });

    /** Approves exactly `amount` for `spender`. Returns true if it sent a transaction. */
    async function ensureAllowance(token: Address, spender: Address, amount: bigint): Promise<boolean> {
      const allowance = await publicClient.readContract({ address: token, abi: erc20Abi, functionName: "allowance", args: [account.address, spender] });
      if (allowance >= amount) return false;
      const approve = async (value: bigint) => {
        const hash = await walletClient.writeContract({ address: token, abi: erc20Abi, functionName: "approve", args: [spender, value] });
        const receipt = await publicClient.waitForTransactionReceipt({ hash });
        if (receipt.status !== "success") throw new Error(`approve(${value}) reverted: ${hash}`);
      };
      if (allowance > 0n) await approve(0n); // USDT-style tokens need a reset to 0 first
      await approve(amount);
      return true;
    }

    const decimals = await publicClient.readContract({ address: transfer.tokenIn, abi: erc20Abi, functionName: "decimals" });
    const amountIn = parseUnits(transfer.amount, decimals); // "10" USDT is 10000000 base units
    const balance = await publicClient.readContract({ address: transfer.tokenIn, abi: erc20Abi, functionName: "balanceOf", args: [account.address] });
    if (balance < amountIn) throw new Error(`The wallet holds ${balance} base units of USDT, less than ${amountIn}`);
    // Build again after each approval, and check the allowance against the spender in the new response.
    while (await ensureAllowance(transfer.tokenIn, swap.contractToApprove, amountIn)) {
      swap = await buildSwap();
    }
    ```

    For native-token input (`0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`), skip this step: `value` carries the amount.
  </Step>

  <Step title="Estimate gas and send the source transaction">
    Always run `eth_estimateGas` on the source chain before you send, add a buffer, and send right away. A `200` from `POST /swap` doesn't guarantee that the calldata executes: a provider can return a route that reverts. The calldata also carries an on-chain expiry (5 minutes on most routes), a quote inside the route can expire within seconds of the build, and the calldata is bound to `account`.

    ```ts theme={null}
    const request = { account, to: swap.to, data: swap.calldata, value: BigInt(swap.value) };
    let gas: bigint;
    try {
      gas = ((await publicClient.estimateGas(request)) * 120n) / 100n; // +20% buffer
    } catch (error) {
      // The balance and the allowance are in place, so this route can't execute as built. Don't send it.
      throw new Error(`The ${quote.aggregatorId} calldata fails eth_estimateGas: request a new quote and build from it`, { cause: error });
    }
    const hash = await walletClient.sendTransaction({ ...request, gas });

    // Store these before anything else can fail: POST /tx-status needs all three.
    const tracking = { hash, chainId: transfer.fromChainId, dexHash: swap.dexHash.toLowerCase() };
    console.log(JSON.stringify(tracking));

    const receipt = await publicClient.waitForTransactionReceipt({ hash });
    if (receipt.status !== "success") throw new Error(`Source transaction reverted: ${hash}. Nothing was bridged.`);
    ```

    This guide doesn't fall back to `gasLimit`. On cross-chain swaps, `estimatedGas` and the `gasLimit` derived from it can be a wei amount rather than gas units: in the response above, `gasLimit` is `"323438069024099968"`.

    If the estimate reverts, the transaction would revert too, so never send it. Cross-chain quotes have no `aggregatorOrder` to fall back on. Request a new cross-chain quote once. If it returns the same `aggregatorId` and `bridgeInfo.displayName`, building it again gives a route that reverts the same way: stop, and tell the user that this route isn't available right now. Otherwise, show the user the new `minimumReceived` and build the swap from it.
  </Step>

  <Step title="Track the transfer to the destination chain">
    A successful source receipt means the source leg ran, not that the funds arrived. Poll `POST /tx-status` with the source transaction hash, the source chain ID and the lowercase `dexHash`, every 15 to 30 seconds:

    ```ts theme={null}
    import { waitForTransfer } from "./wait-for-transfer.ts"; // /guides/track-a-swap-to-finality

    const outcome = await waitForTransfer(tracking, {
      onUpdate: (status) => console.log(status.status, status.detailStatus),
    });

    if (outcome.state === "success") console.log(`Delivered on Ethereum: ${outcome.transfer.toTxHash}`);
    else if (outcome.state === "failure") console.log(`Transfer failed: ${outcome.transfer.status} ${outcome.transfer.detailStatus}`);
    else console.log(`No final status yet. Check the explorers, then contact support with requestId ${outcome.requestId}`);
    ```

    A `500 TX_STATUS_ERROR` right after the broadcast is normal: the provider has no status for it yet, so keep polling. The same error also comes back on every poll when the hash, chain ID or `dexHash` is wrong, so if it never clears, check those three values. [Track a swap to finality](/guides/track-a-swap-to-finality) has the full poller and the status values.
  </Step>
</Steps>

## Verify

* The source transaction succeeded on the source chain's explorer (Polygonscan here).
* `POST /tx-status` returns a success status (`SUCCESS`, `DONE`, `success` or `Success`) with a `toTxHash` that you can open on the destination chain's explorer (Etherscan here).
* The wallet's balance of the destination token on Ethereum rose by at least the quote's `minimumReceived`, in base units. `toAmount` in the status comes from the provider without conversion, so check its unit before you compare it.

## Common pitfalls

<Warning>
  **`calldata`, not `data`.** The cross-chain swap returns `calldata` instead of `data`. Sending the transaction with an undefined `data` field sends no calldata at all.
</Warning>

<Warning>
  **Treating the source receipt as delivery.** The source leg can succeed while the bridge fails or refunds. Show the swap as complete only when `POST /tx-status` reports a success status.
</Warning>

<Warning>
  **Losing `dexHash`.** Without the `dexHash` from `POST /swap` you can't query the transfer status. Store it with the transaction hash and source chain ID as soon as the wallet returns the hash.
</Warning>

<Warning>
  **Stale calldata.** The calldata carries an on-chain expiry (5 minutes on most routes), a quote inside the route can expire within seconds, and it is bound to `account`: another Olympex swap from the same account can invalidate it. Build it right before you send.
</Warning>

<Warning>
  **Sending calldata without an estimate.** A `200` from `POST /swap` doesn't mean the source transaction executes. Run `eth_estimateGas` before the wallet signs, and if it reverts, request a new quote instead of sending.
</Warning>

<Warning>
  **Using `gasLimit` as the gas limit.** On cross-chain swaps it can derive from a wei amount. Estimate gas on the source chain yourself.
</Warning>

## What's next

<CardGroup cols={2}>
  <Card title="Track a swap to finality" icon="clock" href="/guides/track-a-swap-to-finality">
    The full poller and every status value.
  </Card>

  <Card title="Cross-chain mechanics" icon="route" href="/concepts/cross-chain-mechanics">
    How providers, bridges and middleware swaps fit together.
  </Card>

  <Card title="Build a swap" icon="code" href="/api-reference/swap/build-swap">
    Every field of the cross-chain `POST /swap`.
  </Card>

  <Card title="Get cross-chain transfer status" icon="list-check" href="/api-reference/transactions/get-transaction-status">
    Every field of `POST /tx-status`.
  </Card>
</CardGroup>


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