Liquidity Layer API
Attach value with your Hyperlane messages using the Liquidity Layer API.
The LiquidityLayer API is in beta. The API is subject to change
Hyperlane's Liquidity layer wraps token bridges to allow developers to send tokens alongside their message.
- Circle
- Token: USDC
- Chains: Etherum and Avalanche C-Chain (Mainnet), Goerli and Fuji (Testnet)
- Portal
- Token: USDC, ETH
- Chains: Goerli, Fuji, Mumbai, BSC Testnet, Alfajores (Testnet only)
Developers can send interchain messages very similarly to the Messaging API by calling the
LiquidityLayerRouter.dispatchWithTokens
endpoint. At the moment, dispatched messages must be received by a contract with the handleWithTokens()
function. You will learn more about this in the Receive section up next, but for now let's cover the message sending interface. Remember that you need to ERC-20-approve the tokens to the LiquidityLayerRouter
, otherwise it is not authorized to move them.// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
interface ILiquidityLayerRouter {
function dispatchWithTokens(
uint32 _destinationDomain,
bytes32 _recipientAddress,
address _token,
uint256 _amount,
string calldata _bridge,
bytes calldata _messageBody
) external returns (bytes32);
}
_recipientAddress
is the receiving contract, it needs to be a contract with the handleWithTokens()
function, you can read about it in the Receive section._messageBody
is the message you're passing._token
is the address of the token on the origin chain you are trying to transfer_amount
is the amount of _token
you want to transfer_bridge
is a string identifier for the value bridge you want to use, "Circle"
or "Portal"
are currently supported// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;
interface ILiquidityLayerMessageRecipient {
function handleWithTokens(
uint32 _origin,
bytes32 _sender,
bytes calldata _message,
address _token,
uint256 _amount
) external;
}
_sender
the address of the message sender on the source chain_messageBody
the message being passed._address
is the address of the token that was transferred to the recipient_amount
is the amount of the token that was transferredThe same points about access control and encoding from the Messaging API apply to the LiquidityLayer API as well, so be sure to check it out. However, rather than requiring access control such that the Mailbox can only call the
handle
function, the LiquidityLayerRouter on the local chain must be the only address that can call the handleWithTokens
function.Just like all Hyperlane messages that wish to have their messages delivered by a relayer, users must pay for interchain gas.
The
dispatchWithTokens
function in the Liquidity Layer API returns the message ID as a bytes32
. This message ID can then be used by the caller to pay for interchain gas.Because the Liquidity Layer uses the default ISM for security, the DefaultIsmInterchainGasPaymaster IGP should be used. When specifying the amount of gas, the caller must pay for a gas amount high enough to cover:
- 1."Overhead" gas used by the Liquidity Layer contract on the destination chain. This is about 280,000 gas. This is because the actual cost of bridging the tokens via the underlying Circle or Portal bridge can be fairly high.
- 2.The gas used by the recipient's
handleWithTokens
function on the destination chain.
function dispatchWithTokensAndPayGas() external payable {
// First, dispatch with tokens
bytes32 messageId = liquidityLayer.dispatchWithTokens(/* ... */);
// Then, pay for gas
// The mainnet DefaultIsmInterchainGasPaymaster
IInterchainGasPaymaster igp = IInterchainGasPaymaster(
0x56f52c0A1ddcD557285f7CBc782D3d83096CE1Cc
);
// Pay with the msg.value
igp.payForGas{ value: msg.value }(
// The ID of the message
messageId,
// Destination domain
destinationDomain,
// The total gas amount. This should be the
// overhead gas amount (280,000 gas) + gas used by the call being made.
// For example, if the handleWithTokens function uses 120,000 gas,
// we pay for 400k gas.
400000,
// Refund the msg.sender
msg.sender
);
}