Crosschain Swap

The Across /swap/approval API enables developers to prepare and execute crosschain swaps through a single streamlined interface. It abstracts the complexity of bridging + swapping into a developer-friendly flow that can be integrated into any stack.

The response includes:

  • Allowance state: current and required allowance values.

  • Balance state: current and expected balance after the swap.

  • Executable approval transaction: calldata to set the required allowance if insufficient.

  • Executable swap transaction: ready-to-submit transaction for the crosschain swap.

  • Swap quote: details of the swap execution (input, output, fees, route).

This ensures developers can both validate prerequisites (balances, approvals) and directly execute crosschain swaps with minimal custom logic.


Crosschain Swap Flow

1

Fetch Chains and Token

Fetch all the available chains and tokens using GET /swap/chains and GET /swap/tokens and select your tradeType , originChain , destinationChain , inputToken and outputToken

2

Execute Crosschan Swap

Now that we have crosschain intent filled out, make sure you define the amount of inputToken you want to use here. Call the GET /swap/approval endpoint. This endpoint will return a ready transaction and you just need to sign and execute the transaction using your wallet.

Now let's dive deeper and code the whole flow out!


Basic Execution

Let us now focus on conducting a crosschain swap with the following parameters:

  1. tradeType : Defines the type of trade for eg - minOutput.

  2. amount : Specifies amount of inputToken in the smallest unit (wei for ETH or 6 decimals for USDC).

  3. inputToken : Address of the token being swapped.

  4. originChainId : Chain ID of the source/origin chain.

  5. outputToken : Address of the token to be received after the swap.

  6. destinationChainId : Chain ID of the destination chain where the output token will be delivered.

  7. depositor : The address of the sender (usually the wallet initiating the swap).

In this specific example, we will be swapping 1 USDC on Optimism with a related amount of ETH on Arbitrum.

Read the Complete Swap API Reference Here

Run the following script to conduct the crosschain swap:

import { createWalletClient, http, parseUnits } from 'viem'
import { privateKeyToAccount } from 'viem/accounts';
import { optimism, arbitrum } from 'viem/chains'
import axios from 'axios'
import dotenv from 'dotenv'

dotenv.config()

async function acrossSwapApproval() {
  const PRIVATE_KEY = process.env.PRIVATE_KEY


  const account = privateKeyToAccount(PRIVATE_KEY)
  const client = createWalletClient({
    account,
    chain: optimism,
    transport: http()
  })

  const { data } = await axios.get('https://app.across.to/api/swap/approval', {
    params: {
      tradeType: 'minOutput',
      amount: parseUnits('1',6).toString(),
      inputToken: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85',
      originChainId: 10,
      outputToken: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
      destinationChainId: 42161,
      depositor: account.address,
    }
  })

  // Handle token approvals if required
  if (data.approvalTxns) {
    for (const approvalTxn of data.approvalTxns) {
      const tx = await client.sendTransaction({
        to: approvalTxn.to,
        data: approvalTxn.data,
      })
      console.log('Approval tx hash:', tx)
    }
  }

  // Execute swap transaction
  const tx = await client.sendTransaction({
    to: data.swapTx.to,
    data: data.swapTx.data,
    value: data.swapTx.value ? BigInt(data.swapTx.value) : undefined,
  })

  console.log('Crosschain swap tx hash:', tx)
}

acrossSwapApproval()

Upon successful execution of the /swap/approval endpoint, the return data should be something like this:

{
  "checks": {
    "allowance": {
      "token": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
      "spender": "0x5c7BCd6E7De5423a257D81B442095A1a6ced35C5",
      "actual": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
      "expected": "1065159719994"
    },
    "balance": {
      "token": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
      "actual": "0",
      "expected": "1065159719994"
    }
  },
  "steps": {
    "bridge": {
      "inputAmount": "1065159719994",
      "outputAmount": "940201830",
      "tokenIn": {
        "decimals": 18,
        "symbol": "ETH",
        "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
        "name": "Ether",
        "chainId": 1
      },
      "tokenOut": {
        "decimals": 18,
        "symbol": "ETH",
        "address": "0xE5ecd226b3032910CEaa43ba92EE8232f8237553",
        "name": "Ether",
        "chainId": 232
      },
      "fees": {
        "totalRelay": {
          "pct": "999117313760226216",
          "total": "1064219518164"
        },
        "relayerCapital": {
          "pct": "99999954936899",
          "total": "106515923"
        },
        "relayerGas": {
          "pct": "999017313805289317",
          "total": "1064113002241"
        },
        "lp": {
          "pct": "0",
          "total": "0"
        }
      }
    }
  },
  "refundToken": {
    "decimals": 18,
    "symbol": "WETH",
    "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
    "name": "Ether",
    "chainId": 1
  },
  "inputAmount": "1065159719994",
  "expectedOutputAmount": "940201830",
  "minOutputAmount": "940201830",
  "expectedFillTime": 8,
  "swapTx": {
    "simulationSuccess": false,
    "chainId": 1,
    "to": "0x5c7BCd6E7De5423a257D81B442095A1a6ced35C5",
    "data": "0x7b939232000000000000000000000000a4d353bbc130cbef1811f27ac70989f9d568ceab000000000000000000000000c5939f59b3c9662377dda53a08d5085b2d52b719000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000e5ecd226b3032910ceaa43ba92ee8232f8237553000000000000000000000000000000000000000000000000000000f800777c3a00000000000000000000000000000000000000000000000000000000380a576600000000000000000000000000000000000000000000000000000000000000e800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000067f4f4530000000000000000000000000000000000000000000000000000000067f5494300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000c5939f59b3c9662377dda53a08d5085b2d52b719000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044ef8738d3000000000000000000000000e5ecd226b3032910ceaa43ba92ee8232f8237553000000000000000000000000a4d353bbc130cbef1811f27ac70989f9d568ceab00000000000000000000000000000000000000000000000000000000",
    "maxFeePerGas": "1213349849",
    "maxPriorityFeePerGas": "500000000"
  }
}

With the above response from the /swap/approval endpoint, you can proceed to sign transaction on the wallet client.

While performing a crosschain swap, if liquidity is not present for a particular swap, the Swap API will respond with the error bubbled up from the swap provider.


Sample Routes using /swap/approval Endpoint

This section is aimed at displaying how some routes can be formed to help developers build crosschain swap interfaces quickly.

1. Swap USDC on Ethereum Mainnet to ETH on Arbitrum

import axios from 'axios'
import { parseUnits } from 'viem'

async function fetchSwapDetails() {
  const { data } = await axios.get('https://app.across.to/api/swap/approval', {
    params: {
      tradeType: 'minOutput',
      amount: parseUnits("1",6).toString(),
      inputToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // USDC on Ethereum
      originChainId: 1,
      outputToken: '0xA4d353BBc130cbeF1811f27ac70989F9d568CeAB', // ETH on Arbitrum
      destinationChainId: 42161,
      depositor: '<address-of-the-sender>'
    }
  })
 // console.log(data) [if needed]
  return data
}

Key Parameters:

  1. tradeType : Defines the type of trade for eg - minOutput.

  2. amount : Specifies amount of inputToken in the smalled unit (wei for ETH or 6 decimals for USDC).

  3. inputToken : Address of the token being swapped.

  4. originChainId : Chain ID of the source/origin chain.

  5. outputToken : Address of the token to be received after the swap.

  6. destinationChainId : Chain ID of the destination chain where the output token will be delivered.

  7. depositor : The address of the sender (usually the wallet initiating the swap).

Same parameters will be followed in all other examples in this section.

2. Swap ETH from Ethereum Mainnet to GHO on Lens Chain

import axios from 'axios'
import { parseUnits } from 'viem'

async function fetchSwapDetails() {
  const { data } = await axios.get('https://app.across.to/api/swap/approval', {
    params: {
      tradeType: 'minOutput',
      amount: parseUnits("0.001",18).toString(),
      inputToken: '0x1ff1dC3cB9eeDbC6Eb2d99C03b30A05cA625fB5a', // ETH on Ethereum
      originChainId: 1,
      outputToken: '0x6bDc36E20D267Ff0dd6097799f82e78907105e2F', // GHO on Lens Chain
      destinationChainId: 232,
      depositor: '<address-of-the-sender>'
    }
  })
 // console.log(data) [if needed]
  return data
}

3. Swap USDC on Ethereum Mainnet to GHO on Lens Chain

import axios from 'axios'
import { parseUnits } from 'viem'

async function fetchSwapDetails() {
  const { data } = await axios.get('https://app.across.to/api/swap/approval', {
    params: {
      tradeType: 'minOutput',
      amount: parseUnits("1",6).toString(),
      inputToken: '0x1ff1dC3cB9eeDbC6Eb2d99C03b30A05cA625fB5a', // USDC on Ethereum
      originChainId: 1,
      outputToken: '0x6bDc36E20D267Ff0dd6097799f82e78907105e2F', // GHO on Lens Chain
      destinationChainId: 232,
      depositor: '<address-of-the-depositor>'
    }
  })
 // console.log(data) [if needed]
  return data
}

Tracking Deposits

To track the lifecycle of a deposit you can use the GET /deposit/status endpoint with the following parameters:

  • originChainId: chainId where the deposit originated from. Use this in conjunction with the depositId parameter.

  • depositId: The deposit id that is emitted from the DepositV3 function call as a V3FundsDeposited event. Use this in conjunction with the originChainId parameter.

  • depositTxHash : The deposit transaction hash that is emitted from the DepositV3 function call as a V3FundsDeposited event. If you are using this, you do not need the above parameters.

The recommended solution for tracking all Across deposits originating from your integration, for a single user, is to store the user's depositId and originChainId or the depositTxHash from each transaction originating from your app, and then get the status of each via the above endpoint.

In case you face any errors or need support, please feel free to reach out to us.

Last updated