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

> How a cross-chain transfer is quoted, built, sent and tracked: providers, bridges, swaps around the bridge, and status.

A cross-chain transfer is one transaction on the source chain and one outcome on the destination chain. You quote it, build its calldata, and send a single transaction from your wallet on the source chain. A cross-chain provider runs the rest of the route: the bridge, plus any swaps it needs on either side. You follow the result with `POST /tx-status` until the provider reports success or failure.

## The lifecycle

<Steps>
  <Step title="Confirm the source chain">
    Transfers can start on Ethereum, Optimism, BNB Chain, Polygon, Arbitrum and Avalanche. See [Where a transfer can start](#where-a-transfer-can-start).
  </Step>

  <Step title="Quote the transfer">
    [`POST /quotes`](/api-reference/quotes/get-quote) with `mode: "cross-chain"` and `fromChainId`, `toChainId` (integers), `inTokenAddress`, `outTokenAddress`, `amount` and `slippage`. The quote names the provider in `aggregatorId`, the bridge in `bridgeInfo`, and the expected and minimum amounts on the destination chain. Unknown top-level keys are rejected with `400`.
  </Step>

  <Step title="Build the transaction">
    [`POST /swap`](/api-reference/swap/build-swap) with `mode: "cross-chain"`, the `aggregatorId` from the quote, the `account` that sends on the source chain and receives on the destination chain, and the same chains, tokens, amount and slippage. Don't send `dryRun`: cross-chain requests reject it with `400`. The response returns `to`, `calldata`, `value`, `contractToApprove` and `dexHash`.
  </Step>

  <Step title="Send on the source chain">
    For ERC-20 input, approve `contractToApprove` for the exact input amount. Estimate gas yourself, then send `{to, data: calldata, value, gas}` from `account` on the source chain, right after `/swap`. A `200` from `/swap` doesn't guarantee that the calldata executes: if `eth_estimateGas` reverts while the allowance and the balance are in order, don't send it. Request a new quote and build again.
  </Step>

  <Step title="Track to completion">
    [`POST /tx-status`](/api-reference/transactions/get-transaction-status) with the source transaction `hash`, the source `chainId` and the `dexHash` from `/swap`. Poll with backoff until `status` is a success or failure value.
  </Step>
</Steps>

## Field names across endpoints

The quote, the swap and the status call use these fields:

| Meaning | `POST /quotes` (cross-chain) | `POST /swap` (cross-chain) | `POST /tx-status` |
| - | - | - | - |
| Source chain | `fromChainId` (integer) | `fromChainId` (integer) | `chainId` (integer) |
| Destination chain | `toChainId` (integer) | `toChainId` (integer) | Not sent |
| Token you send | `inTokenAddress` | `inTokenAddress` | Not sent |
| Token you receive | `outTokenAddress` | `outTokenAddress` | Not sent |
| Sending wallet | Not sent | `account` | Not sent |
| Provider | Returned as `aggregatorId` | `aggregatorId` | Identified by `dexHash` |
| Calldata | Not returned | Returned as `calldata` | Not sent |

<Note>
  Single-chain swaps return the calldata as `data`; cross-chain swaps return it as `calldata`. Map it to the `data` field of the transaction you send.
</Note>

## Providers and bridges

The quote's `aggregatorId` names the cross-chain provider. Signed requests from API accounts are quoted and built with `okx` or `rango`. Pass the value to `/swap` unchanged.

The provider picks the bridge. `bridgeInfo.displayName` names it, and `bridgeInfo.icon` is a logo URL that can be empty. Show the bridge name to your users: it tells them which protocol carries their funds between chains.

`middlewareRoute` lists the swaps around the bridge: `chainFrom` on the source chain before bridging, and `chainTo` on the destination chain after it. Each entry names its `fromAsset` and `toAsset` with `address`, `decimals` and `symbol`. In this quote for 10 USDT on Polygon to USDT on Ethereum, the provider is `rango` and the bridge is BOB Gateway. `chainFrom` is empty, and `chainTo` holds one entry, from USDT on Polygon to USDT on Ethereum. `integratorFeeBreakdown` reports the Olympex protocol fee in base units of the source token:

```json Quote theme={null}
{
  "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"
  }
}
```

In `middlewareRoute`, a native token can appear as `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` or as the zero address, `0x0000000000000000000000000000000000000000`, depending on the provider. Treat both as native, and compare addresses case-insensitively.

## Amounts, costs and timing

| Field | Meaning |
| - | - |
| `fromTokenAmount` | The input amount, echoed in human-readable units. |
| `toTokenAmount` | Expected amount on the destination chain, in base units of the destination token. |
| `minimumReceived` | Minimum amount on the destination chain after slippage, in base units. |
| `estimateCostInUSD` | The provider's estimate of the transfer's cost, in USD. What it includes varies by provider. |
| `estimatedGas` | The provider's estimate. Its unit depends on the provider: gas units or a native-token amount in wei. Display only. |
| `estimatedTime` | The provider's estimate of the transfer time. Present only when the provider reports one. |
| `integratorFeeBreakdown` | The Olympex protocol fee and your integrator fee, on every quote. `protocolFeeBps` is in basis points (`15` is 0.15%) and applies even when you send no `fees`; `integratorMarginBps` is then `0`. For cross-chain, amounts are in base units of the source token. See [Gas and fees](/concepts/gas-and-fees#the-fee-breakdown). |

In the quote above, 10 USDT on Polygon returns `toTokenAmount` `"9985000"` and `minimumReceived` `"9885149"`. USDT on Ethereum has 6 decimals, so the user expects 9.985 USDT on Ethereum and receives at least 9.885149. The protocol fee, `protocolFeeAmount` `"15000"`, is in the source token: 0.015 USDT on Polygon, 0.15% of the input.

<Warning>
  Don't use a cross-chain `estimatedGas` or `gasLimit` as the gas limit of your transaction. The unit varies by provider. Estimate gas with `eth_estimateGas` on the source chain and add a buffer, as described in [Gas and fees](/concepts/gas-and-fees).
</Warning>

## Where a transfer can start

A transfer can start on Ethereum (`1`), Optimism (`10`), BNB Chain (`56`), Polygon (`137`), Arbitrum (`42161`) and Avalanche (`43114`). Base (`8453`) and Linea (`59144`) support single-chain swaps but can't start a transfer. Whether a specific pair has a route depends on the providers at the time you ask, so request a quote to confirm it. [Supported chains](/concepts/supported-chains) has the full matrix.

## Tracking a transfer

Store three values as soon as you broadcast: the source transaction hash, the source chain ID and the `dexHash` from `/swap`. They are all [`POST /tx-status`](/api-reference/transactions/get-transaction-status) needs:

```json theme={null}
{
  "chainId": 137,
  "dexHash": "0x6c2142a4113d1a2ef94717109dd5ac71f69439465f95a170fb9cf0f56d76ad7c",
  "hash": "0x5f2b0a3c3e8f9d1b7a6c4e2d0f8b6a4c2e0d8f6b4a2c0e8d6f4b2a0c8e6d4f2b"
}
```

`chainId` is an integer, and `dexHash` goes in lowercase. The response always carries `status`, `detailStatus`, `fromChainId` and `toChainId`, and adds `fromTxHash`, `toTxHash`, `fromAmount`, `toAmount`, `fromTokenAddress`, `toTokenAddress` and `bridgeHash` when the provider reports them. `fromAmount` and `toAmount` are as the provider reports them, and their unit isn't normalized: check it before you do arithmetic. `errorMsg` is always `null` in a `200` response: provider errors surface as `500` with `TX_STATUS_ERROR` instead. Show `detailStatus` to users, not `errorMsg`.

`status` is the provider's own value, so match it against both lists exactly:

| Outcome | `status` values |
| - | - |
| Success | `SUCCESS`, `DONE`, `success`, `Success` |
| Failure | `FAILURE`, `FAILED`, `failed`, `REFUND`, `Reverted`, `FROM_FAILURE`, `INVALID` |
| In progress | Any other value |

`detailStatus` is the provider's sub-status or message; show it, but don't branch on it.

`POST /tx-status` returns `500` with `TX_STATUS_ERROR` when the provider has no status for the transfer yet, which is common right after broadcast, and for transfers the provider marks as failed. Treat it as "status unknown", not as a final answer. Keep polling with backoff, for example every 15 to 30 seconds. If the status stays unknown, check the source transaction on a block explorer, then contact [partners@olympex.io](mailto:partners@olympex.io) with the `meta.requestId` of your last call and the source transaction hash.

For a single-chain swap, or the source transaction of a transfer, the transaction receipt is the result: read it from your RPC or poll [`GET /transactions/{hash}`](/api-reference/transactions/get-transaction). That endpoint reads the receipt on one chain, not the bridge.

## What this means for your integration

* Send `/swap` the quote's `params` unchanged, plus `account` and the quote's `aggregatorId`.
* Persist the source hash, source chain ID and lowercase `dexHash` the moment you broadcast. Without them you can't track the transfer.
* Show the bridge name, `minimumReceived` in destination-token units and `estimatedTime` when the provider reports it.
* Poll until a success or failure value, and treat `TX_STATUS_ERROR` as "not known yet".

## Related

<CardGroup cols={2}>
  <Card title="Cross-chain swap end-to-end" icon="link" href="/guides/cross-chain-swap-end-to-end">
    The full walkthrough, from quote to destination.
  </Card>

  <Card title="Track a swap to finality" icon="clock" href="/guides/track-a-swap-to-finality">
    Polling, backoff and final states.
  </Card>

  <Card title="Get cross-chain transfer status" icon="code" href="/api-reference/transactions/get-transaction-status">
    The `POST /tx-status` reference.
  </Card>

  <Card title="Supported chains" icon="list-check" href="/concepts/supported-chains">
    Chains, chain IDs and token addresses.
  </Card>
</CardGroup>


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