Skip to main content
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.
You need API credentials and the signing helper from Sign requests, and a wallet on the source chain that holds the input token and native token for gas. Execute a swap explains the single-chain version of the wallet steps.

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, 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

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

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 returns data: true when Olympex supports a chain. Send chainId as an integer, as on every endpoint; a string returns 400 VALIDATION_ERROR.
Response
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. Chain support changes rarely, so cache the result and refresh it periodically instead of checking before every transfer.
2

Quote the route

Cross-chain POST /quotes 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.
A response looks like this:
Before the user confirms, show the bridge name, minimumReceived in the destination token’s decimals and, when present, estimatedTime. Cross-chain mechanics explains each amount.
3

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.
A response looks like this, with the calldata shortened:
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.
4

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.
For native-token input (0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE), skip this step: value carries the amount.
5

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

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:
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 has the full poller and the status values.

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

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.
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.
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.
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.
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.
Using gasLimit as the gas limit. On cross-chain swaps it can derive from a wei amount. Estimate gas on the source chain yourself.

What’s next

Track a swap to finality

The full poller and every status value.

Cross-chain mechanics

How providers, bridges and middleware swaps fit together.

Build a swap

Every field of the cross-chain POST /swap.

Get cross-chain transfer status

Every field of POST /tx-status.