In this article, we will explore and learn about the Liquidity Layer API of Hyperlane. Hyperlane's Liquidity layer wraps token bridges to allow developers to send tokens alongside their message. If you have checked our previous articles on Messaging API and Warp Route, analogically LL API is used when you want to do both send tokens and messages in a single cross-chain transaction.

At this moment, LL API supports Circle and Portal bridges to transfer your assets from one chain to another along with your interchain message. Circle’s bridge is used to natively bridge USDC tokens across multiple chains. It was developed by the core team who built the USDC token. If you want to bridge any other token, then you can prefer the Portal bridge. In this tutorial, we will be using Circle’s native CCTP bridge to transfer USDC tokens. Also, we will be using Goerli and Fuji as our two chains where we will deploy our code and call the Liquidity Router.

Before moving ahead in this tutorial, I will suggest you get some testnet USDC from the faucet. Here is the link to the faucet. Fill up your wallet address and click on the tweet option to create a tweet about USDC. Once you tweeted about it, copy the link to the tweet, fill up the tweet URL field, and click on the submit button to get 10 testnet USDCs.

Screenshot 2023-09-07 at 7.27.44 PM.png

Below is the Github repo link, where you can find all the important contracts needed for this tutorial. We will be explaining every snippet of code of all these contracts in this tutorial. You can find all the important contracts in the “src” folder in this repo.

GitHub - HyperlaneIndia/LL-API

Contracts

In this tutorial, we will build a simple smart contract using which you can send tokens with messages, and once it receives the token along with the message we will emit an event that we can later use to track interchain transactions. As mentioned above, we will be using Circle’s bridge to bridge USDC to multiple chains.

As you can see in the workflow given below, we will be calling the dispatchWithTokens() function of the Liquidity Router, which will then pass on the interchain message using Hyperlane and the tokens will be bridged using Circle’s CCTP bridge. On the receiver’s side, we need to have a handleWithTokens() function which the Liquidity Router will call to confirm a transaction.

Screenshot 2023-09-07 at 7.34.00 PM.png

Now let’s dive into the implementation of Liquidity Layer with Hyperlane. We will be developing a single smart contract “LiquidityRouter.sol” which you can deploy onto multiple chains and bridge tokens with interchain messages. First, we will import and create all the important interfaces that we will be using in this contract. We need three interfaces, the IInterchainGasPaymaster will be used to pay for the interchain gas fees(more on this later), the IERC20 interface as we need to call a few approve and transfer functions for the USDC contract, and most importantly ILiquidityLayerRouter interface so that we call call the dispatchWithTokens() function which is present in the Liquidity Layer contract.

import "@hyperlane-xyz/core/contracts/interfaces/IInterchainGasPaymaster.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface ILiquidityLayerRouter {
    function dispatchWithTokens(
        uint32 _destinationDomain,
        bytes32 _recipientAddress,
        address _token,
        uint256 _amount,
        string calldata _bridge,
        bytes calldata _messageBody
    ) external returns (bytes32);
}

After importing all these interfaces, we will declare our contract and declare a few variables. We are naming our contract “LiquidityRouter”. For the variables, we need three variables to store the InterchainGasPaymaster contract address, the LiquidityLayerRouter contract address, and the USDC contract address. Also, we will declare two events to keep track of the interchain transactions. The TokenSentWithMessage event will be emitted when a new interchain transaction originates from this contract and the TokenReceivedWithMessage event will be emitted when a new interchain transaction is received through this contract.address liquidityRouter; address interchainGasPaymaster; address USDCAddress; event TokenSentWithMessage(bytes32 indexed messageId, uint32 indexed destDomain, address indexed recipientAddress, uint256 amount, string message); event TokenRecievedWithMessage(uint32 indexed origin, address indexed sender, string message, uint256 amount);

After this, we will create the constructor which will be used to initialize the three variables we already created. We will accept three variables as arguments in the constructor and then initialize the variables.

constructor(address _lrouter, address _igp, address _usdc){
    liquidityRouter = _lrouter;
    interchainGasPaymaster = _igp;
    USDCAddress = _usdc;
}

Now it’s time to write the core part of this contract which is the “send” function. We will be implementing five things in this function as described below:-