Across Bridge Integration

Product Description

The Across Bridge is Across' first and most familiar product: an end-user application to move value between chains. It combines a reference implementation of Across' quoting engine, relayer network, and Across' modular intents settlement layer, Across Settlement.

Across Bridge is an end-user product but can be integrated into other protocols to provide quoting and bridging directly in a 3rd party application. Bungee (Socket), Jumper (Li.Fi), Rango, and many others do this today.

Integrating Across Bridge into Your Application

This guide contains instructions and examples for calling the smart contract functions that would allow third party projects to transfer assets between Across supported chains.

If you have further questions or suggestions for this guide, please send a message to the #developer-questions channel in the Across Discord.

Initiating a Deposit (User Intent)

Deposits are initiated by interacting with contracts called SpokePools. There is one SpokePool deployed on each chain supported by Across. Each SpokePool has minor modifications to work with each chain, but maintains the same core interface and implementation. For example, on Ethereum the SpokePool contract is named Ethereum_SpokePool.sol, and on Optimism the contract is named Optimism_SpokePool.sol.

Getting a Quote

The process for initiating a deposit begins with determining the fee that needs to be paid to the relayer. To do this, you can use the suggested-fees endpoint: app.across.to/api/suggested-fees with the following query parameters:

  • originChainId: chainId where the user's deposit is originating

  • destinationChainId: chainId where the user intends to receive their funds

  • token: the address of the token that the user is depositing on the origin chain

  • amount: the raw amount the user is transferring. By raw amount, this means it should be represented exactly as it is in Solidity, meaning 1 USDC would be 1e6 or 1 ETH would be 1e18.

Example for a user transferring 1000 USDC from Ethereum to Optimism:

curl "https://app.across.to/api/suggested-fees?token=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&originChainId=1&destinationChainId=10&amount=1000000000"

There are two elements of the response that you'll need to create the deposit:

  • totalRelayFee.total: this is the amount of the deposit that the user will need to pay to have it relayed. It is intended to capture all fees, including gas fees, relayer fees, and system fees.

  • timestamp: this is the quote timestamp. Specifying this timestamp onchain ensures that the system fees don't shift under the user while the intent is in-flight.

You can find details on the Across API here.

Checking Limits

You'll want to check the Across' limits to determine how long a relay is expected to take and to ensure that Across can process a deposit of this size. To do this, you can use the suggested-fees endpoint: app.across.to/api/limits endpoint with the following query parameters:

  • originChainId: chainId where the user's deposit is originating

  • destinationChainId: chainId where the user intends to receive their funds

  • token: the address of the token that the user is depositing on the origin chain

Example for a user transferring USDC from Ethereum to Optimism:

curl "https://app.across.to/api/limits?token=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&originChainId=1&destinationChainId=10"

The response has four elements:

  1. maxDepositInstant: check this value first. if the user's amount is less than or equal to this amount, there is known to be enough relayer liquidity on the destination chain to fill them immediately.

  2. maxDepositShortDelay: if the user's deposit amount is larger than maxDepositInstant, check this value. If the user's amount is less than or equal to this amount, there is known to be enough relayer liquidity that can be moved to the destination chain within 30 minutes, so you should expect them to be filled within that time frame.

  3. maxDeposit: if the user's deposit amount is larger than maxDepositShortDelay, check this value. If the user's amount is less than or equal to this amount, there is enough liquidity in Across to fill them via a slow fill, which could take up to 3 hours. If the user's deposit amount is larger than this, Across cannot fulfil the user's intent.

  4. reccommendedDepositInstant: this is used for certain integrations to limit the input size, and is currently hardcoded to 2 ETH/WETH and 5,000 USDC

You can find details on the Across API here.

Calling depositV3

Before making the call, you'll need the SpokePool address. This can be retrieved from the suggested-fees response for convenience, but we suggest manually verifying these and hardcoding them per chain in your application for security. You can find the addresses here.

Once you have the SpokePool address, you'll need to approve the SpokePool to spend tokens from the user's EOA or the contract that will be calling the SpokePool . The approval amount must be >= the amount value. If sending ETH, no approval is necessary.

The deposit call can come from an intermediary contract or directly from the user's EOA. This is the function that needs to be called:

function depositV3(
  address depositor,
  address recipient,
  address inputToken,
  address outputToken,
  uint256 inputAmount,
  uint256 outputAmount,
  uint256 destinationChainId,
  address exclusiveRelayer,
  uint32 quoteTimestamp,
  uint32 fillDeadline,
  uint32 exclusivityDeadline,
  bytes calldata message
) external;

Here is an example of how the parameters could be populated in Javascript:

const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";

// Bridge to the same address on the destination chain.
// Note: If the depositor is not an EOA then the depositor's address
// might not be valid on the destination chain.
const depositor = <USER_ADDRESS>;
const recipient = depositor;

// Test WETH deposit.
const inputToken = <ORIGIN_CHAIN_WETH_ADDRESS>;

// The 0 address is resolved automatically to the equivalent supported
// token on the the destination chain. Any other input/output token
// combination should be advertised by the Across API available-routes
// endpoint.
const outputToken = ZERO_ADDRESS;

// The outputAmount is set as the inputAmount - relay fees.
// totalRelayFee.total is returned by the Across API suggested-fees
// endpoint.
const outputAmount = inputAmount.sub(BigNumber.from(response.totalRelayFee.total));

// fillDeadline: A fill deadline of 5 hours. Can be up to
// SpokePool.getCurrentTime() + SpokePool.fillDeadlineBuffer() seconds.
const fillDeadlineBuffer = 18000;
const fillDeadline = Math.round(Date.now() / 1000) + fillDeadlineBuffer;

// timestamp is returned by the Across API suggested-fees endpoint.
// This should be _at least 2_ mainnet blocks behind the current time
// for best service from relayers.
const quoteTimestamp = response.timestamp;

// Exclusive relayer and exclusivity deadline should be taken from the
// Across API suggested-fees response.
const exclusivityDeadline = response.exclusivityDeadline;
const exclusiveRelayer = response.exclusiveRelayer;

// No message will be executed post-fill on the destination chain.
// See `Across+ Integration` for more information.
const message = "0x";

spokePool.depositV3(
  depositor,
  recipient,
  inputToken,
  outputToken,
  inputAmount,
  outputAmount,
  destinationChainId,
  exclusiveRelayer,
  quoteTimestamp,
  fillDeadline,
  exclusivityDeadline,
  message
)

and in Solidity (see also the Javascript descriptions above):

spokePool.depositV3(
  depositor, // User's address on the origin chain.
  recipient, // Receiving address on the destination chain.
  weth, // inputToken. This is the WETH address on the origin chain.
  address(0), // outputToken: Auto-resolve the destination equivalent token.
  inputAmount,
  inputAmount - totalRelayFee, // outputAmount
  destinationChainId,
  exclusiveRelayer, // exclusiveRelayer from suggested-fees, or 0x0 to disable
  quoteTimestamp, // suggested-fees timestamp, or block.timestamp - 36
  block.timestamp + spokePool.fillDeadlineBuffer(),
  exclusivityDeadline, // exclusivityDeadline from suggested-fees, or 0 to disable
  "", // message (empty)
);

Append Origination Unique Identifier to CallData

In order to track the origination source for deposits, we request all integrators to append a delimiter of 1dc0de and a unique identifier provided by our team to the deposit transaction call data.

Here is an example of depositV3 call data with 1dc0de delimiter and 0000 unique identifier appended.

Do NOT use 0000 identifier in your implementation, see below on how to request your unique ID. Do NOT pass delimiter + identifier to any depositV3 param, including the message param. Only append to call data of the transaction.

Function: depositV3(address depositor,address recipient,address inputToken,address outputToken,uint256 inputAmount,uint256 outputAmount,uint256 destinationChainId,address exclusiveRelayer,uint32 quoteTimestamp,uint32 fillDeadline,uint32 exclusivityDeadline,bytes message)

0x7b939232000000000000000000000000c30c7ea910a71ce06ae840868b0c7e47616ba4c9000000000000000000000000c30c7ea910a71ce06ae840868b0c7e47616ba4c9000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000009502f9000000000000000000000000000000000000000000000000000000000094fd2f84000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066799bfb000000000000000000000000000000000000000000000000000000006679f0f40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000000001dc0de0000

Request your unique identifier in our shared communication channel (TG, Slack, etc), if already set up, or reach out to [email protected]. Please do not use any other identifier other than the one provided to you.

Updating Deposits

Relayer fees are a function of the spread between the outputAmount and inputAmount parameters of a depositV3 . If this spread is too low, it may not be profitable for relayers to fill. While a deposit is not filled, the outputAmount can be increased by calling speedUpDepositV3 , which requires a signature from the depositor address. Example coming soon!

Last updated