Hyperliquid & LayerZero Composer - Core Concepts
This document covers the essential concepts of Hyperliquid and the LayerZero Hyperliquid Composer. Understanding these is key before proceeding with the deployment.
1. Introduction to Hyperliquid
Hyperliquid uses a custom consensus algorithm called HyperBFT.
Hyperliquid state execution is split into two broad components: HyperCore
and the HyperEVM
. HyperCore
includes fully onchain perpetual futures and spot order books. Every order, cancel, trade, and liquidation happens transparently with one-block finality inherited from HyperBFT. HyperCore
currently supports 200k orders / second
The HyperEVM
brings the familiar general-purpose smart contract platform pioneered by Ethereum to the Hyperliquid blockchain. With the HyperEVM
, the performant liquidity and financial primitives of HyperCore
are available as permissionless building blocks for all users and builders.
HyperCore
HyperCore, or Core, is a high-performance Layer 1 that manages the exchange’s on-chain order books with one-block finality.
Communication with HyperCore
is done via L1 actions
or actions
, as opposed to the usual RPC calls which are used for EVM chains. Full list of L1 actions
here: Exchange endpoint.
HyperEVM
HyperEVM, or EVM, is an Ethereum Virtual Machine (EVM)-compatible environment that allows developers to build decentralized applications (dApps).
You can interact with HyperEVM via traditional eth_
RPC calls (full list here: HyperEVM JSON-RPC).
HyperEVM
has precompiles that let you interact with HyperCore
, where spot and perpetual trading happens (and is probably why you are interested in going to Hyperliquid). If you are not listing on HyperCore
, then HyperEVM is your almost standard EVM network - you just need to switch block sizes.
Block Explorers:
HyperEVM
and HyperCore
have their own block explorers. You can find (a list of explorers here).
2. Hyperliquid API
Hyperliquid supports several API functions that users can use on HyperCore to query information, following is an example.
curl -X POST https://api.hyperliquid-testnet.xyz/info \
-H "Content-Type: application/json" \
-d '{"type": "spotMeta"}'
This will give you the spot meta data for HyperCore. A sample response is below.
{
"universe": [
{
"name": "ALICE",
"szDecimals": 0,
"weiDecimals": 6,
"index": 1231,
"tokenId": "0x503e1e612424896ec6e7a02c7350c963",
"isCanonical": false,
"evmContract": null,
"fullName": null,
"deployerTradingFeeShare": "1.0"
}
]
}
- The
tokenId
is the address of the token onHyperCore
. - The
evmContract
is the address of theERC20
token onHyperEVM
. - The
deployerTradingFeeShare
is the fee share for the deployer of the token.
3. HyperCore Actions
An action as defined by Hyperliquid is a transaction that is sent to the HyperCore
- as it updates state on the HyperCore
it needs to be a signed transaction from the wallet of the action sender.
You need to use ethers-v6
to sign actions - https://docs.ethers.org/v6/api/providers/#Signer-signTypedData
# add ethers-v6 to your project as an alias for ethers@^6.13.5
pnpm add ethers-v6@npm:ethers@^6.13.5
import {Wallet} from 'ethers'; // ethers-v5 wallet
import {Wallet as ethersV6Wallet} from 'ethers-v6'; // ethers-v6 wallet
const signerv6 = new ethersV6Wallet(wallet.privateKey); // where wallet is an ethers.Wallet from ethers-v5
const signature = await signerv6.signTypedData(domain, types, message);
This is because in ethers-v5
EIP-712 signing is not stable: https://docs.ethers.org/v5/api/signer/#Signer-signTypedData
Experimental feature (this method name will change) This is still an experimental feature. If using it, please specify the exact version of ethers you are using (e.g. spcify "5.0.18", not "^5.0.18") as the method name will be renamed from _signTypedData to signTypedData once it has been used in the field a bit.
You can use the official Hyperliquid Python SDK to interact with HyperCore
.
LayerZero also built an in-house minimal TypeScript SDK that focuses on switching blocks, deploying the HyperCore
token, and connecting the HyperCore
token to a HyperEVM
ERC20 (OFT).
4. Accounts
You can use the same account (private key) on both HyperEVM
and HyperCore
. HyperCore
uses signed Ethereum transactions to validate data.
5. Multi-Block Architecture
HyperEVM
and HyperCore
are separate entities, so they have separate blocks, interleaved by their creation order.
HyperEVM Blocks
HyperEVM
has two kinds of blocks:
- Small Blocks: Default, 2-second block time, 2M gas limit. For high throughput transactions. OFT deployments are typically larger than 2M gas.
- Big Blocks: 1 transaction per block, 1 block per minute, 30M gas limit. For deploying large contracts.
You can toggle between block types for your account using an L1 action
of type evmUserModify
:
{"type": "evmUserModify", "usingBigBlocks": true}
You can also switch to big blocks using LayerZero Hyperliquid SDK with a simple command:
npx @layerzerolabs/hyperliquid-composer set-block --size big --network mainnet --private-key $PRIVATE_KEY
Flagging a user for big blocks means all subsequent HyperEVM transactions from that user will be big block transactions until toggled off. To toggle back to small blocks, set usingBigBlocks
to false
.
Alternatively, use bigBlockGasPrice
instead of gasPrice
in transactions.
HyperCore Blocks
HyperCore
has its own blocks, which means there are 3 block types in total.
As HyperCore
and HyperEVM
blocks are produced at different speeds, with HyperCore
creating more than HyperEVM
, the blocks are created in not a strictly alternating manner.
For example, the block sequence might look like this:
[Core] → [Core] → [EVM-small] → [Core] → [Core] → [EVM-small] → [Core] → [EVM-large] → [Core] → [EVM-small]
6. Precompiles
Hyperliquid uses precompiles in two ways: System Contracts and L1ActionPrecompiles.
System Contracts:
0x2222222222222222222222222222222222222222
: System contract address for theHYPE
token.0x200000000000000000000000000000000000abcd
: System contract address for a created Core Spot token (asset bridge).
L1ActionPrecompiles:
0x0000000000000000000000000000000000000000
: One of manyL1Read
precompiles.0x3333333333333333333333333333333333333333
: TheL1WritePrecompile
for sending transactions to HyperCore.
L1Read
reads from the last producedHyperCore
block at EVM transaction execution.L1Write
writes to the first producedHyperCore
block after the production of the EVM block.
L1Write
is currently available on testnet only.
7. Token Standards
- Token standard on HyperEVM:
ERC20
(EVM Spot) - Token standard on HyperCore:
HIP-1
(Core Spot)
Deploying a Core Spot token involves a 31-hour Dutch auction for a core spot index, followed by configuration.
Using the Hyperliquid UI for spot deployment (https://app.hyperliquid.xyz/deploySpot) forces the use of "Hyperliquidity". This is NOT supported by LayerZero as it can lead to an uncollateralized asset bridge. Deploy via API/SDK to avoid this. The LayerZero SDK facilitates this.
8. The Asset Bridge: Linking EVM Spot (ERC20) and Core Spot (HIP-1)
For tokens to be transferable between HyperEVM
and HyperCore
, the EVM Spot (ERC20) and Core Spot (HIP-1) must be linked. This creates an asset bridge precompile at an address like 0x2000...abcd
(where abcd
is the coreIndexId
of the HIP-1 in hexadecimal).
Linking Process:
requestEvmContract
: Initiated by the HyperCore deployer, signaling intent to link HIP-1 to an ERC20.finalizeEvmContract
: Initiated by the HyperEVM deployer (EOA) to confirm the link.
Asset Bridge Mechanics:
The asset bridge (0x2000...abcd
) acts like a lockbox.
- To send tokens from HyperEVM to HyperCore: Transfer ERC20 tokens to its asset bridge address on HyperEVM.
- To send tokens from HyperCore to HyperEVM: Use the
spotSend
L1 action, targeting the asset bridge address on HyperCore.
Funding:
For tokens to move into HyperCore
, the deployer must mint the maximum supply (e.g., u64.max
via API) of HIP-1 tokens to the token's asset bridge address on HyperCore
(or to their deployer account and then transfer).
u64.max
is the maximum value for a u64
integer, which is 2^64 - 1
. It's a 20-digit number: 18,446,744,073,709,551,615
(18.4 quintillion, or 18
+ 18 zeros)
Example of transition in bridge balances:
- Initial state:
[AssetBridgeEVM: 0 | AssetBridgeCore: 0]
- Fund HyperCore bridge:
[AssetBridgeEVM: 0 | AssetBridgeCore: X]
- User bridges
X*scale
tokens from EVM to Core: User sendsX*scale
ERC20 to EVM bridge. - New state:
[AssetBridgeEVM: X*scale | AssetBridgeCore: 0]
Hyperliquid has no checks for asset bridge capacity. If you try to bridge more tokens than available on the destination side of the bridge, all tokens will be locked in the asset bridge address forever. The Hyperliquid Composer contract includes checks to refund users on HyperEVM
if such a scenario is detected.
"Partially funding" the HyperCore asset bridge is problematic. If initial funds are consumed ([X.EVM | 0]
) and you add more Y.Core
tokens to the HyperCore bridge, it might trigger a withdrawal of X.EVM
tokens, leading to [0 | Y.Core]
but with X.Core
tokens (previously converted from X.EVM
) still in circulation on HyperCore that cannot be withdrawn back to EVM.
Always fully fund the HyperCore side of the asset bridge with the total intended circulatable supply via the bridge.
9. Communication between HyperEVM and HyperCore
- HyperEVM reads state from HyperCore: Via
precompiles
(e.g., perp positions). - HyperEVM writes to HyperCore: Via
events
at specificprecompile
addresses AND by transferring tokens through the asset bridge.
10. Transfers between HyperEVM and HyperCore
Spot assets can be sent from HyperEVM to HyperCore and vice versa. They are called Core Spot
and EVM Spot
. These are done by sending an ERC20::transfer
with asset bridge address as the recipient.
To move tokens across:
- Send tokens to the asset bridge address (
0x2000...abcd
) on the source network (HyperEVM or HyperCore).- On HyperEVM, this is an
ERC20::transfer(assetBridgeAddress, value)
- The event emitted is
Transfer(address from, address to, uint256 value)
→Transfer(_from, assetBridgeAddress, value);
- The
Transfer
event is picked up by Hyperliquid's backend.
- On HyperEVM, this is an
- The tokens are credited to your account on the destination network.
- Then, on the destination network, send tokens from your address to the final receiver's address.
The HyperliquidComposer contract from LayerZero Hyperliquid SDK automates these actions.
11. Hyperliquid Composer
The Composer facilitates X-network
→ HyperCore
OFT transfers.
Why a Composer?
Users might want to hold tokens on HyperEVM
and only move to HyperCore
for trading. Auto-conversion in lzReceive
isn't ideal. An lzCompose
function allows this flexibility.
Mechanism:
- A LayerZero message sends tokens to Hyperliquid.
lzReceive
on the OFT on HyperEVM mints tokens to theHyperLiquidComposer
contract address. - The
composeMsg
inSendParam
(from the source chain call) contains the actual receiver's address on Hyperliquid. - The
HyperLiquidComposer
'slzCompose
function is triggered. - The Composer:
- Transfers the received EVM Spot tokens (ERC20) from itself to the token's asset bridge address (
0x2000...abcd
). ThisTransfer
event signals Hyperliquid's backend to credit the tokens on HyperCore. - Performs an
L1WritePrecompile
transaction (to0x33...33
) instructing HyperCore to execute aspot transfer
of the corresponding HIP-1 tokens from the Composer's implied Core address (derived from its EVM address) to the actual receiver's address (fromcomposeMsg
) on HyperCore.
- Transfers the received EVM Spot tokens (ERC20) from itself to the token's asset bridge address (
That particular Transfer
event is what Hyperliquid nodes/relayers listen to in order to credit the receiver
address on Core.
struct SendParam {
uint32 dstEid;
bytes32 to; // OFT address (so that the OFT can execute the `compose` call)
uint256 amountLD;
uint256 minAmountLD;
bytes extraOptions;
bytes composeMsg; // token receiver address (msg.sender if you want your address to receive the token)
bytes oftCmd;
}
HyperCore::HIP1
decimals can differ from HyperEVM::ERC20
decimals. The Composer handles scaling. Amounts on HyperCore will reflect HIP-1 decimals. Converting back restores ERC20 decimals.
Composer Contract:
The composer is a separate contract deployed on HyperEVM because we don't want developers to change their OFT contracts.
contract HyperLiquidComposer is IHyperLiquidComposer {
constructor(
address _endpoint,
address _oft,
uint64 _coreIndexId, // Core Spot token's index ID
uint64 _weiDiff // Decimal difference: HIP1.decimals - ERC20.decimals
) {...}
function lzCompose(address _oApp, bytes32 _guid, bytes calldata _message, address _executor, bytes calldata _extraData) external payable override {
// ... logic to transfer to asset bridge and L1WritePrecompile ...
}
}
12. LayerZero Transaction on HyperEVM
Since this is a compose call, the toAddress
is the HyperLiquidComposer
contract address.
The token receiver is encoded as an abi.encode/Packed()
of the receiver
address into SendParam.composeMsg
. This is later used in the lzCompose
phase to transfer the tokens to the L1 spot address on behalf of the token receiver
address.
_credit(toAddress, _toLD(_message.amountSD()), _origin.srcEid)
This mints
the amount in local decimals to the token receiver (HyperLiquidComposer
contract address).
We now need to create a Transfer
event to send the tokens from HyperEVM to HyperCore, the composer computes the amount receivable on HyerCore
based on the number of tokens in HyperCore's asset bridge, the max transferable tokens (u64.max * scale
) and sends the tokens to itself on HyperCore (this scales the tokens based on HyperAsset.decimalDiff
). It also sends to the receivers
address on HyperEVM any leftover tokens from the above transformation from HyperEVM amount to HyperCore.
IHyperAssetAmount amounts = quoteHyperCoreAmount(_amount, isOft);
oft::transfer(0x2000...abcd, amounts.evm); // <- gets the user amounts.core on HyperCore
oft::transfer(_receiver_, amounts.dust);
As a result the invariant of amounts.dust + amounts.evm = _amount
and amounts.evm = 10.pow(decimalDiff) * amounts.core
are always satisfied.
Composer's Internal Logic (_sendAssetToHyperCore
):
- Calculates
amounts.evm
(amount to send to EVM asset bridge) andamounts.core
(equivalent amount on HyperCore), considering bridge capacity and decimal scaling. - Calculates
amounts.dust
(any leftover EVM amount that cannot be bridged). token.safeTransfer(oftAsset.assetBridgeAddress, amounts.evm);
→ This moves tokens to HyperCore side.IHyperLiquidWritePrecompile(HLP_PRECOMPILE_WRITE).sendSpot(_receiver, oftAsset.coreIndexId, amounts.core);
→ This moves tokens on HyperCore from composer to receiver.token.safeTransfer(_receiver, amounts.dust);
→ Refunds dust to receiver on HyperEVM.
function _sendAssetToHyperCore(address _receiver, uint256 _amountLD) internal virtual {
IHyperAssetAmount memory amounts = quoteHyperCoreAmount(_amountLD, true);
if (amounts.evm > 0) {
token.safeTransfer(oftAsset.assetBridgeAddress, amounts.evm);
IHyperLiquidWritePrecompile(HLP_PRECOMPILE_WRITE).sendSpot(_receiver, oftAsset.coreIndexId, amounts.core);
}
if (amounts.dust > 0) {
token.safeTransfer(_receiver, amounts.dust);
}
}
10. OFTWrapper for Hyperliquid Bridge
Using Hyperliquid Bridge
will incur a fee (currently 0bp
) that may be enabled once write precompiles are enabled. We use Stargate's OFT Wrapper on ALL networks that we support on Hyperliquid Bridge
.
The repository that we use to deploy the bridge on various networks: LayerZero-Labs/hyperliquid-oft-wrapper