Get started

View as Markdown

The 0x Cross-Chain API is in private beta. Request access to start building.

Quick link with examples: 0x-examples

About 0x Cross Chain API

The 0x Cross Chain API is a fast, flexible API that lets developers programmatically access tokens across various blockchains, enabling cross-chain swap functionality in any app or wallet.

Whether you’re building a new application, or expanding the offering of your existing product, this API helps you integrate cross-chain token swaps with minimal overhead and maximum efficiency.

Steps to Cross Chain Swap Tokens

This guide will walk you through using the /quotes and /status endpoints on 0x’s Cross Chain API to:

  1. Fetch a quote
  2. Set allowance
  3. Sign and send a transaction
  4. Monitor the status of the cross chain swap

In our example, we will be swapping WETH on Base to USDC on Arbitrum.

0. Prerequisites

Make sure you have:

  • A funded Base wallet
  • 0x API key
  • An RPC Connection (see details below)

Base provides a default public RPC endpoint, but for production use, it’s strongly recommended to run your own or use a third-party provider like QuickNode or Alchemy.

To interact with that RPC, we will use a popular TS library, viem.

1import { createWalletClient, http } from "viem";
2import { base } from "viem/chains";
3import { privateKeyToAccount } from "viem/accounts";
4
5const PRIVATE_KEY = 'your_private_key'
6
7const account = privateKeyToAccount(PRIVATE_KEY as `0x${string}`);
8const walletClient = createWalletClient(
9 {
10 account,
11 chain: base,
12 transport: http(configuration.rpcUrls.base),
13 }
14)

1. Fetch a Quote

Start by sending a GET request to the 0x /quotes endpoint to get a quote for a specific tokens and chains pair with selected amount.

1const quotesParams = new URLSearchParams({
2 originChain: '8453', // Base mainnet
3 destinationChain: '42161', // Arbitrum mainnet
4 sellToken: '0x4200000000000000000000000000000000000006', // WETH on Base
5 buyToken: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', // USDC on Arbitrum
6 sellAmount: '1000000000000000000', // Amount of sellToken in base units
7 originAddress: '$USER_TAKER_ADDRESS', // Address that will make the trade
8 sortQuotesBy: 'price', // Prefer the quote that will result in the best price / output
9 maxNumQuotes: 1 // only the best quote
10});
11
12const headers = {
13 '0x-api-key': '[api-key]', // Get your live API key from the 0x Dashboard (https://dashboard.0x.org/apps)
14};
15
16const quoteResponse = await fetch('https://api.0x.org/cross-chain/quotes?' + quotesParams.toString(), { headers });
17
18console.log(await quoteResponse.json());
1{
2 "liquidityAvailable": true,
3 "allowanceTarget": "0x0000000000001ff3684f28c67538d4d072c22734",
4 "originChainId": 8453,
5 "originChain": "base",
6 "destinationChainId": 42161,
7 "destinationChain": "arbitrum",
8 "sellToken": "0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf",
9 "buyToken": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
10 "issues": {
11 "allowance": {
12 "actual": "0",
13 "spender": "0x0000000000001ff3684f28c67538d4d072c22734"
14 },
15 "balance": {
16 "token": "0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf",
17 "actual": "0",
18 "expected": "2570"
19 },
20 "simulationIncomplete": false,
21 "invalidSwapSourcesPassed": [],
22 "invalidBridgesPassed": []
23 },
24 "zid": "0x0d2804bc7404160d778f0f7f",
25 "quotes": [
26 {
27 "sellAmount": "2570",
28 "buyAmount": "2016894",
29 "minBuyAmount": "1996725",
30 "fees": {
31 "integratorFee": null,
32 "integratorFees": null,
33 "zeroExFee": {
34 "amount": "24",
35 "token": "0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf",
36 "type": "volume"
37 },
38 "bridgeNativeFee": null
39 },
40 "gasCosts": {
41 "chainType": "evm",
42 "gasPrice": "7610802",
43 "gasLimit": "151386",
44 "totalNetworkFee": "1252356913486"
45 },
46 "steps": [
47 {
48 "type": "bridge",
49 "originChainId": 8453,
50 "destinationChainId": 42161,
51 "sellToken": "0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf",
52 "buyToken": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
53 "sellAmount": "2546",
54 "buyAmount": "2016894",
55 "minBuyAmount": "1996725",
56 "provider": "relay",
57 "estimatedTimeSeconds": 1
58 }
59 ],
60 "transaction": {
61 "chainType": "evm",
62 "details": {
63 "to": "0x0000000000001ff3684f28c67538d4d072c22734",
64 "data": "0x2213bc0b...",
65 "gas": "151386",
66 "gasPrice": "7610802",
67 "value": "0"
68 }
69 },
70 "estimatedTimeSeconds": 1,
71 "issues": {
72 "allowance": {
73 "actual": "0",
74 "spender": "0x0000000000001ff3684f28c67538d4d072c22734"
75 },
76 "balance": {
77 "token": "0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf",
78 "actual": "0",
79 "expected": "2570"
80 },
81 "simulationIncomplete": false
82 },
83 "quoteId": "0x0d2804bc7404160d778f0f7f4ec037a2"
84 }
85 ]
86}

2. Set a token allowance

It is possible, that you will need to set proper token allowance to 0x’s AllowanceHolder contract. In cases where allowance will be needed, it will be reported in issues in the response and will be the same as the allowance target for Swap API (when using AllowanceHolder flow). It may differ across chains, so always use the value from the response rather than hardcoding an address.

Below you can find an example on how to set proper token allowance for received quote.

1// Example assumes that you've set up your walletClient with viem
2
3import { erc20Abi } from "viem";
4...
5// Code that fetches a valid quotesResponse
6...
7const approveTxHash = await walletClient.writeContract({
8 address: quoteResponse.sellToken as `0x${string}`,
9 abi: erc20Abi,
10 functionName: "approve",
11 args: [
12 quotesResponse.quotes[0].issues.allowance.spender as `0x${string}`,
13 BigInt(quoteResponse.sellAmount),
14 ],
15});
16
17await walletClient.waitForTransactionReceipt({
18 hash: approveTxHash,
19 confirmations: 1,
20});

Important: Only EVM chains require token allowances. Solana and Tron do not need an approval step and allowanceTarget will be null for those chains. Native tokens (ETH, BNB, etc.) also do not need approvals.

3. Sign and submit a transaction

Now that we have both the approval and the quote, we can construct, sign, and submit the transaction. Optionally, after the approval, you might want to re-fetch a fresh quote.

Code below will construct, sign, and submit the transaction based on the quote response.

1// Prepare a transaction request
2const txRequest = {
3 to: quoteResponse.quotes[0].transaction.details.to as `0x${string}`,
4 data: quoteResponse.quotes[0].transaction.details.data as `0x${string}`,
5 value: BigInt(quoteResponse.quotes[0].transaction.details.value),
6 gas: quoteResponse.quotes[0].transaction.details.gas
7 ? BigInt(quoteResponse.quotes[0].transaction.details.gas)
8 : undefined,
9};
10
11// Sign and submit the transaction request
12const txHash = await walletClient.sendTransaction(txRequest);
13
14// Wait for confirmation on Base
15const receipt = await walletClient.waitForTransactionReceipt({
16 hash: txHash,
17 confirmations: 2,
18});
19console.log(`Transaction confirmed in block: ${receipt.blockNumber}`);
20console.log(`View on BaseScan: https://basescan.org/tx/${txHash}`);

4. Monitor the cross chain execution

The last step is to monitor the execution of the cross chain transaction, including the fill on the destination chain. For that, we will use the /status endpoint.

1const statusParams = new URLSearchParams({
2 originChain: 8453, // origin chain
3 originTxHash, // transaction hash of the origin chain transaction, submitted in previous step
4 quoteId, // the quote ID of the submitted quote, equivalent to quoteResponse.quotes[0].quoteId
5});
6
7const headers = {
8 '0x-api-key': '[api-key]', // Get your live API key from the 0x Dashboard (https://dashboard.0x.org/apps)
9};
10
11const statusResponse = await fetch('https://api.0x.org/cross-chain/status?' + statusParams.toString(), { headers });
12
13console.log(await statusResponse.json());

In your application, you might need to monitor the status repeatedly, as bridging operation might take several seconds or minutes to complete.

It is a good practice to always pass quoteId when polling /status. Without it, bundled transactions (e.g. with ERC-4337) cannot be disambiguated, and the first status request will be slower.

For full status lifecycle details, polling intervals, and failure handling, see Tracking Transaction Status and Handling Failures and Recovery.

1{
2 "status": "bridge_filled",
3 "bridge": "stargate",
4 "steps": [
5 {
6 "bridge": "stargate",
7 "originChainId": 8453,
8 "destinationChainId": 42161,
9 "sellToken": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
10 "buyToken": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
11 "sellAmount": "997500",
12 "minBuyAmount": "987525",
13 "quotedBuyAmount": "997059",
14 "estimatedTimeSeconds": 20,
15 "transactions": [
16 {
17 "chainId": 8453,
18 "chain": "base",
19 "txHash": "0x53287620e5e527c6e11c70dfb1fc9e9f2675fcc27d7c98b2d0c7e82e9ac011d6",
20 "timestamp": 1762985805
21 },
22 {
23 "chainId": 42161,
24 "chain": "arbitrum",
25 "txHash": "0x390ca459f68cc5320d0c8904ba890cb5e4f25484a1a0def62c65852b48ddabd8",
26 "timestamp": 1762985833
27 }
28 ],
29 "type": "bridge"
30 }
31 ],
32 "failure": null,
33 "transactions": [
34 {
35 "chainId": 8453,
36 "chain": "base",
37 "txHash": "0x53287620e5e527c6e11c70dfb1fc9e9f2675fcc27d7c98b2d0c7e82e9ac011d6",
38 "timestamp": 1762985805
39 },
40 {
41 "chainId": 42161,
42 "chain": "arbitrum",
43 "txHash": "0x390ca459f68cc5320d0c8904ba890cb5e4f25484a1a0def62c65852b48ddabd8",
44 "timestamp": 1762985833
45 }
46 ],
47 "zid": "0xeccee60cd76a5084fdff17d4"
48}

Want to collect fees? See Monetize Your App to learn how to add integrator fees to cross-chain swaps.