Application Framework
We provide an application framework, which implements the common process of message passing, including sending, receiving, and validating messages and token transfers. After inherent the app framework contracts, the app developers only need to focus on the app-specific logic.
  • To send cross-chain message and token transfer, the app needs to inherent MessageSenderApp.sol and call the utils functions.
  • To receive cross-chain message and token transfer, the app needs to inherent MessageReceiverApp.sol and implement its virtual functions.

Example 1: Batch Token Transfer​

BatchTransfer.sol is an example app thatsends tokens from one sender at the source chain to multiple receivers at the destination chain through a single cross-chain token transfer. The high-level workflow consists of three steps:
  1. 1.
    Sender side calls batchTransfer at source chain, which internally calls app framework's sendMessageWithTransfer to send message and tokens.
  2. 2.
    Receiver side implements the executeMessageWithTransfer interface to handle the batch transfer message, and distribute tokens to receiver accounts according to the message content. It also internally calls app framework's sendMessage to send a receipt to the source app.
  3. 3.
    Sender side implements the executeMessage interface to handle the receipt message.

Example 2: Cross Chain Swap​

TransferSwap.sol is an example app that allows swapping one token on chain1 to another token on chain2 through cBridge and DEXes on both chain1 and chain2.
For the simplicity of explanation, let's say we deploy this contract on chain1 and chain2, and we want to input tokenA on chain1 and gain tokenC on chain2.
Public functions transferWithSwap and transferWithSwapNative are called by a user to initiate the entire process. These functions takes in a SwapInfo struct that specifies the behavior or "route" of the execution, and execute the process in the following fashion:
  1. 1.
    Swap tokenA on the source chain to gain tokenB
  2. 2.
    Packages a SwapRequest as a "message", which indicates the swap behavior on chain2
  3. 3.
    sendMessageWithTransfer is then called internally to send the message along with the tokenB through the bridge to chain2
  4. 4.
    On chain2, executeMessageWithTransfer is automatically called when the bridge determines that the execution conditions are met.
  5. 5.
    This contract parses the message received to a SwapRequest struct, then executes the swap using the tokenB received to gain tokenC. (Note: when executeMessageWithTransfer is called, it is guaranteed that tokenB already arrives at the TransferSwap contract address on chain2. You can check out this part of verification logic in MessageBusReceiver.sol's executeMessageWithTransfer).
  6. 6.
    If the execution of executeMessageWithTransfer of TransferSwap contract on chain2 reverts, or if the executeMessageWithTransfer call returns false, then MessageBus would call executeMessageWithTransferFallback. This is the place where you implement logic to decide what to do with the received tokenB.
The following is a more graphical explanation of all the supported flows of this demo app:
1. swap bridge swap
tokenA -> swap -> tokenB -> bridge -> tokenB -> swap -> tokenC -> out
2. swap bridge
tokenA -> swap -> tokenB -> bridge -> tokenB -> out
3. bridge swap
tokenA -> bridge -> tokenA -> swap -> tokenB -> out
4. just swap
tokenA -> swap -> tokenB -> out


Since bridging tokens requires a nonce to deduplicate same-parameter transactions, it is important that sendMessageWithTransfer is called with a nonce that is unique for every transaction at a per-contract per-chain level, meaning your application should always call transferWithSwap with a different nonce every time. A duplicated nonce can result in duplicated transferIds. Checkout how a transferId is computed here.

Example 2: Refund​

TestRefund.sol was originally written for testing, but it also demostrates how you can handle a refund in case of bridging failures (bad slippage, non-existant token, amount too smol, etc...)
The function executeMessageWithTransferRefund will be called by your executor automatically on the source chain when it finds out that there is any available refund for your contract in SGN. Like in executeMessageWithTransfer, you can expect that the tokens are guaranteed to arrive at the contract before the function is called. The _message you receive in this function is exactly the same as it was sent through sendMessageWithTransfer.