Example

An example of a direct contract integration


/**
 * Struct representing a bridge token.
 * @param symbol  Bridge token symbol: unique token ID consistent among all chains
 * @param token   Bridge token address
 */
type BridgeToken = {
  symbol: String;
  token: Address;
};

/**
 * Struct representing a request for a swap quote from a bridge token.
 * @param symbol    Bridge token symbol: unique token ID consistent among all chains
 * @param amountIn  Amount of bridge token to start with, before the bridge fee is applied
 */
type DestRequest = {
  symbol: String;
  amountIn: BigInt;
};

/**
 * Struct representing a request swap (list of instructions) for SynapseRouter.
 * @param swapAdapter   Adapter address that will perform the swap. Address(0) specifies a "no swap" query.
 * @param tokenOut      Token address to swap to.
 * @param minAmountOut  Minimum amount of tokens to receive after the swap, or tx will be reverted.
 * @param deadline      Latest timestamp for when the transaction needs to be executed, or tx will be reverted.
 * @param rawBytes      ABI-encoded params for the swap that will be passed to `swapAdapter`.
 */
type SwapQuery = {
  swapAdapter: Address;
  tokenOut: Address;
  minAmountOut: BigInt;
  deadline: BigInt;
  rawParams: BytesLike;
};

type UserSettings = {
  maxSlippage: BigInt;
  deadlineOrigin: BigInt;
  deadlineDest: BigInt;
};

interface SynapseRouter {
  /**
   * Initiate a bridge transaction with an optional swap on both origin and destination chains
   * @param to            Address to receive tokens on destination chain
   * @param chainId       Destination chain id
   * @param token         Initial token for the bridge transaction to be pulled from the user
   * @param amount        Amount of the initial tokens for the bridge transaction
   * @param originQuery   Origin swap query. Empty struct indicates no swap is required
   * @param destQuery     Destination swap query. Empty struct indicates no swap is required
   */
  bridge(
    to: Address,
    chainId: Number,
    token: Address,
    amount: BigInt,
    originQuery: SwapQuery,
    destQuery: SwapQuery
  ): null;

  /**
   * Gets the list of all bridge tokens (and their symbols), such that destination swap
   * from a bridge token to `tokenOut` is possible.
   * @param tokenOut  Token address to swap to on destination chain
   */
  getConnectedBridgeTokens(tokenOut: Address): BridgeToken[];

  /**
   * Finds the best path between `tokenIn` and every supported bridge token from the given list,
   * treating the swap as "origin swap", without putting any restrictions on the swap.
   * @param tokenIn       Initial token that user wants to bridge/swap
   * @param tokenSymbols  List of symbols representing bridge tokens
   * @param amountIn      Amount of tokens user wants to bridge/swap
   */
  getOriginAmountOut(
    tokenIn: Address,
    tokenSymbols: String[],
    amountIn: BigInt
  ): SwapQuery[];

  /**
   * Finds the best path between every supported bridge token from the given list and `tokenOut`,
   * treating the swap as "destination swap", limiting possible actions to those available for every bridge token.
   * Will take the bridge fee into account, when returning a quote for every bridge token.
   * @param requests  List of structs with following information:
   *                  - symbol: unique token ID consistent among all chains
   *                  - amountIn: amount of bridge token to start with, before the bridge fee is applied
   * @param tokenOut  Token user wants to receive on destination chain
   */
  getDestinationAmountOut(
    requests: DestRequest[],
    tokenOut: Address
  ): SwapQuery[];
}

/// Perform a cross-chain swap using Synapse:Bridge
/// Start from `amountIn` worth of `tokenIn` on origin chain
/// Receive `tokenOut` on destination chain
function synapseBridge(
  originChainId: Number,
  destChainId: Number,
  tokenIn: Address,
  tokenOut: Address,
  amountIn: BigInt,
  userOrigin: Address,
  userDest: Address,
  userSettings: UserSettings
) {
  // Every cross-chain swap via Synapse:Bridge is fueled by using one of the
  // supported "bridge tokens" as the intermediary token.
  // A following set of actions will be initiated by a single SynapseRouter.bridge() call:
  // - Origin chain: tokenIn -> bToken swap is performed
  // - Synapse: bridge bToken from origin chain to destination
  // - Destination chain: bToken -> tokenOut is performed

  // Here we describe a list of actions to perform such a cross-chain swap, knowing only
  // - tokenIn, tokenOut, amountIn
  // - SynapseRouter deployments
  // - User settings for maximum slippage and deadline
  // - User address on origin and destinaion chain (might be equal or different)

  // Beware: below is a TypeScript pseudocode.

  // 0. Fetch deployments of SynapseRouter on origin and destiantion chains
  let routerOrigin = getSynapseRouter(originChainId);
  let routerDest = getSynapseRouter(destChainId);

  // 1. Determine the set of bridge tokens that could enable "receive tokenOut on destination chain"
  // For that we pefrorm a static call to SynapseRouter on destination chain
  let bridgeTokens = routerDest.getConnectedBridgeTokens(tokenOut);
  // Then we get the list of bridge token symbols
  let symbols = bridgeTokens.map((token) => token.symbol);

  // 2. Get the list of Queries with possible swap instructions for origin chain
  // For that we pefrorm a static call to SynapseRouter on origin chain
  // This gets us the quotes from tokenIn to every bridge token (one quote per bridge token in the list)
  let originQueries = routerOrigin.getOriginAmountOut(
    tokenIn,
    symbols,
    amountIn
  );

  // 3. Get the list of Queries with possible swap instructions for destination chain
  // First, we form a list of "destiantion requests" by merging
  // list of token symbols with list of quotes obtained in step 2.
  let requests = symbols.map((value, index) => {
    let request: DestRequest = {
      symbol: value,
      amountIn: originQueries[index].minAmountOut,
    };
    return request;
  });
  // Then we perform a static call to SynapseRouter on destination chain
  // This gets us the quotes from every bridge token to tokenOut (one quote per bridge token in the list)
  // These quotes will take into account the fee for bridging the token to destination chain
  let destQueries = routerDest.getDestinationAmountOut(requests, tokenOut);

  // 4. Pick a pair of originQueries[i], destQueries[i] to pefrom the cross-chain swap
  // In this example we are picking the pair that yeilds the best overall quote
  let destQuery = maxBy(destQueries, (query) => query.minAmountOut);
  let selectedIndex = destQueries.indexOf(destQuery)
  let originQuery = originQueries[selectedIndex]

  // Now we apply user slippage and deadline settings
  originQuery = applyUserSettings(originQuery, userSettings)
  destQuery = applyUserSettings(destQuery, userSettings)

  // 5. Call SynapseRouter on origin chain to perform a swap
  let amountETH: BigInt;
  // 0xEeee address is used to represent native ETH
  if (tokenIn == "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE") {
    // If user selected "native ETH" as tokenIn, we would need to modify msg.value for the call
    amountETH = amountIn;
  } else {
    // If user selected an ERC-20 token as tokenIn, we would need to use msg.value=0
    amountETH = 0
    // We also need to check if user approved routerOrigin to spend `tokenIn`
    if (allowance(tokenIn, userOrigin, routerOrigin) < amountIn) {
      // Users needs to issue a token approval
      // tokenIn.approve(routerOrigin, amountIn)
    }
  }
  // Perform a call to Synapse Router with all the derevied parameters
  // Use previously determined msg.value for this call
  // (WETH wrapping is done by the Synapse Router)
  routerOrigin.bridge{value: amountETH}(
    userDest,
    destChainId,
    tokenIn,
    amountIn,
    originQuery,
    destQuery
  );
}

Last updated