Synapse Router

A quick overview of the Synapse Router

Overview

The Synapse Router is an overhaul of current Synapse Bridge contracts that abstracts much of the complexity around liquidity based bridging to one simple bridge() function. Previously, different functions would be used for different types of transactions which made it very complex to bridge at the contract level.

The new Router is comprised of one bridge() function and three supporting functions that help to construct a bridge transaction. All of the mentioned are organized by an important struct called a “Query”. Before diving into these functions, a deeper understanding of how the bridge actually works is fundamental.

Most Synapse Bridge transactions require an “intermediary token” which is a token the protocol has mint/burn permissions over. Any third-party bridge necessitates such a structure to properly account for bridged tokens. Find out more about Synapse intermediary tokens nUSD and nETH in the docs.

Thus, when users are not bridging or receiving an intermediary token, their transaction is routed through a liquidity pool on both chains. Determining what is the optimal route is not trivial most times because of certain considerations: slippage in pools, expected tokenOut/tokenIn. The router condenses this complexity into a “query”.

What is a Query:

A query is a data structure that describes generic swap instructions, containing two items [token, amountReceived]. Given the desired token to bridge, a query identifies what “swap” needs to happen on the respective chain in order for the bridge to:

A: Identify a route(s) for tokenIn to be swapped into an intermediary token

B: Identify a route(s) for the intermediary token to be swapped into the desired tokenOut on the destination chain.

Two of our supporting functions help do this:

getOriginAmountOut()
// returns us a list of "Queries" that would enable the desired bridging transaction on the Origin chain 
// This should be called on the origin chain for every attempt to construct a bridge transaction
getDestinationAmountOut()
// returns us a list of "Queries" that would enable the desired bridging transaction on the Desitnation chain 
// This should be called on the destination chain (after calling getOriginAmountOut on the origin chain) for every attempt to construct a bridge transaction

Note that these functions return a list of queries. This is because their can be several different routes for a bridge transaction. At the application level, a developer can decide which route(which query) to utilize. In examples further down, the cheapest route is chosen. The developer must generate and select one query for both the Origin and Destination chains to properly format a bridge() call.

Ultimately, the Query struct enables the ability to do the following:

  • Specify the tokenIn and its amount elsewhere and will transfer it before calling adapterSwap()

  • Set a parameter defining the minimumAmountOut of tokenOut

  • Set a deadline for the swap

  • Specify the Adapter that the swap will use

*Note that the resulting SwapQuery could be "empty" -- this only happens if the tokenIn or tokenOut is already a bridge token (e.g. nUSD or nETH)

See the Example Page for further information (and arguments) that the functions above require. It is imperative that the program uses the functions here to construct Queries instead of manually doing so -- this guarantees that the transaction won't be reverted for misconfigured parameters.

Constructing a Bridge Transaction

To construct a bridge transaction there are two main parts, the off-chain to submit the transaction, and the on-chain transaction construction . Here we explore all on-chain interactions to properly setup a bridge transaction at a high level.

Examples are provided on the ensuing page

The first step is utilizing the getConnectedBridgeTokens() supporting function to help structure getOriginAmountOut() — our method for generating the list of queries for the origin chain. We call this function with the desired output token and are returned a list of tokens and their symbols.

Once we have our list of supported bridge token symbols we can generate a list of queries for the Origin chain by calling getOriginAmountOut() with the desired input token, the result from getConnectedBridgeTokens(), and the amount of tokens being bridged. Now for every bridge token from the above list we know the amount of tokens being bridged, if this token is selected as the intermediary token.

After reformatting the output from the above, we can get our Destination queries by calling getDestinationAmountOut(). The Synapse Router V1 only supports swaps via one of the Synapse pools are available. Passing other instructions to the Router could result in failed transactions. Additionally, this function doesn't include tokenIn or token amount because those variables can be populated by the bridge given the arguments to getOriginAmountOut(). By passing the list of bridge tokens together with their bridged amount, we can determine the amount of tokens a user will receive at the end of the bridge transaction; each possible path having their own respective amount.

Now we have a list of queries for the Origin and Destination chain and need to select one (originQuery, destinationQuery) pair. Below is a simple function to extract the queries with the lowest slippage (cheapest route).

maxIndex = destQueries.indexOf(destQueries.maxBy, (query) => { return query.minAmountOut });
originQuery = originQueries[maxIndex];
// destQuery.minAmountOut is the full quote for tokenIn => tokenOut cross-chain swap
destQuery = destQueries[maxIndex];

Finally, the developer can apply slippage and deadline parameters using the applyUserSettings(), and then call the bridge() function with the relevant parameters. Basic examples displaying this setup can be found here.

Note that these are view functions that live on every supported chain, thus the program needs to call each function on its respective chain using off-chain services (such as an RPC).

At a High Level

On-chain we have two main objective, identify all possible routes (queries) for a specific bridging transaction, and then choose the desired routes so that we can call the bridge() function. To actually bridge funds we need to call the bridge function using a web3 client.

Last updated