Overview
Melt is a cross-chain token bridge built on LayerZero V2. It enables:
- Bridge In — Lock ERC-20 tokens on a source chain, receive wrapped tokens on HyperEVM.
- Bridge In to HyperCore — Same as above, but tokens land directly in your HyperCore spot balance for immediate trading.
- Bridge Out — Burn wrapped tokens on HyperEVM, receive original tokens on the source chain.
- Wrap / Unwrap — Convert between native HyperEVM tokens and their Melt-wrapped equivalents (same-chain only).
Source Chain HyperEVM
┌──────────────┐ LayerZero V2 ┌──────────────┐
│ MeltBridge │ ────────────────► │ MeltHub │
│ (lock/release) │ ◄──────────────── │ (mint/burn) │
└──────────────┘ └──────┬───────┘
│
│ toCore = true
▼
┌──────────────┐
│ HyperCore │
│ (spot balance)│
└──────────────┘
See Deployment Addresses for all contract addresses.
Core Concepts
Config Key
Every token is identified by a configKey — a hash of the original token address and its source chain:
configKey = keccak256(abi.encodePacked(originalToken, sourceEid))
sourceEid is the LayerZero Endpoint ID of the source chain (0 for same-chain HyperEVM tokens).
- Use
MeltHub.getConfigKey(originalToken, sourceEid) to compute it on-chain.
LayerZero Endpoint IDs
| Chain | EID |
|---|
| Ethereum | 30101 |
| HyperEVM | 30367 |
Recipient Encoding
Bridge functions accept recipients as bytes32. To convert an address:
bytes32 recipient = bytes32(uint256(uint160(recipientAddress)));
Fee Directions
Melt fees are directional:
- IN — applied on
wrap() and bridge-in (source → HyperEVM). Deducted from the amount received.
- OUT — applied on
unwrap() and bridgeOut() (HyperEVM → source). Deducted from the amount sent.
Flows
Bridge In (Source → HyperEVM)
Bridge tokens from a source chain to receive wrapped tokens on HyperEVM.
Approve
Approve the MeltBridge contract to spend your tokens.
Quote LayerZero fee
MessagingFee memory fee = meltBridge.quoteBridge(token, amount, recipient, false);
Bridge
MessagingReceipt memory receipt = meltBridge.bridge{value: fee.nativeFee}(
token,
amount,
recipient, // bytes32-encoded destination address on HyperEVM
false // toCore = false
);
Wait for delivery
LayerZero delivery takes ~1–5 minutes. Track using the guid from the receipt.
Receive
Wrapped tokens arrive on HyperEVM. Amount received = amount - fee (IN direction fee).
Events to monitor:
| Chain | Event | Meaning |
|---|
| Source | BridgeInitiated(guid, sender, token, amount, recipient) | Bridge tx submitted |
| HyperEVM | BridgedIn(guid, recipient, wrappedToken, amount, srcEid) | Wrapped tokens minted |
Bridge In to HyperCore
Bridge tokens directly into your HyperCore spot balance for immediate trading.
Your recipient address must be activated on HyperCore. The transaction reverts with CoreUserNotActivated otherwise. The token must also have HyperCore configured (coreLinked = true).
Approve
Approve the MeltBridge contract to spend your tokens.
Quote LayerZero fee (2x gas for toCore)
MessagingFee memory fee = meltBridge.quoteBridge(token, amount, recipient, true);
Bridge with toCore = true
MessagingReceipt memory receipt = meltBridge.bridge{value: fee.nativeFee}(
token,
amount,
recipient, // bytes32-encoded HyperEVM address (must be activated on Core)
true // toCore = true
);
Receive on HyperCore
Tokens land in your HyperCore spot balance. The amount is converted from EVM decimals to Core decimals using the token’s decimalDiff.
Events to monitor:
| Chain | Event | Meaning |
|---|
| Source | BridgeInitiated(guid, sender, token, amount, recipient) | Bridge tx submitted |
| HyperEVM | BridgedToCore(guid, recipient, wrappedToken, evmAmount, coreAmount) | Tokens delivered to HyperCore |
Bridge Out (HyperEVM → Source)
Burn wrapped tokens on HyperEVM to receive original tokens on the source chain.
Approve
Approve MeltHub to spend your wrapped tokens.
Quote LayerZero fee
MessagingFee memory fee = meltHub.quoteBridgeOut(wrappedToken, amount, recipient);
Bridge out
MessagingReceipt memory receipt = meltHub.bridgeOut{value: fee.nativeFee}(
wrappedToken,
amount,
recipient // bytes32-encoded destination address on source chain
);
Receive on source chain
Original tokens are released from the bridge. Amount received = amount - fee (OUT direction fee).
Events to monitor:
| Chain | Event | Meaning |
|---|
| HyperEVM | BridgedOut(guid, sender, wrappedToken, amount, dstEid, recipient) | Burn + LZ message sent |
| Source | BridgeCompleted(recipient, token, amount) | Original tokens released |
Wrap / Unwrap (Same-Chain)
For tokens native to HyperEVM that have a Melt-wrapped version (identified by sourceEid = 0).
Wrap (original → wrapped):
- Approve
MeltHub to spend the original token.
- Call:
uint256 amountOut = meltHub.wrap(configKey, amount);
- Receive
amountOut = amount - fee (IN direction).
Unwrap (wrapped → original):
- Approve
MeltHub to spend the wrapped token.
- Call:
uint256 amountOut = meltHub.unwrap(configKey, amount);
- Receive
amountOut = amount - fee (OUT direction).
Errors Reference
| Error | Contract | Cause |
|---|
TokenNotSupported | MeltBridge | Token is not whitelisted on this bridge |
InsufficientAmount | Both | Amount is zero |
InsufficientLiquidity | MeltBridge | Bridge doesn’t hold enough locked tokens for release |
TokenNotRegistered | MeltHub | Token config key not found |
TokenPaused | MeltHub | Token or hub is paused |
InvalidSourceChain | MeltHub | Source chain mismatch on bridge-out |
CoreNotConfigured | MeltHub | toCore=true but HyperCore not linked for this token |
CoreUserNotActivated | MeltHub | Recipient is not activated on HyperCore |
InsufficientCoreBridgeCapacity | MeltHub | Asset bridge doesn’t have enough capacity |
Fee Structure
- Fees are charged in basis points (1 bps = 0.01%), up to a maximum of 1,000 bps (10%).
- Fee calculation:
fee = amount * feeBps / 10_000.
- Fees are directional — IN and OUT fees may differ.
- The protocol can configure per-address fee overrides via
MeltFeeController. If your address has an override, it takes precedence over the token’s default fee.
- Fees are collected as wrapped tokens held by
MeltHub.
Checking Your Fee
// Get the token's default fee
(, , , uint256 feeBps, ,) = meltHub.tokenConfigs(configKey);
// If a fee controller is set, your actual fee may differ
IMeltFeeController controller = meltHub.feeController();
if (address(controller) != address(0)) {
uint256 fee = controller.getFee(configKey, yourAddress, amount, feeBps, isOut);
}
Function Signatures
MeltBridge (Source Chain)
// Bridge tokens to HyperEVM. Set toCore=true to deliver to HyperCore spot balance.
function bridge(address token, uint256 amount, bytes32 recipient, bool toCore)
external payable returns (MessagingReceipt memory);
// Quote the LayerZero fee for a bridge transaction.
function quoteBridge(address token, uint256 amount, bytes32 recipient, bool toCore)
external view returns (MessagingFee memory);
// Check if a token is supported.
function isTokenSupported(address token) external view returns (bool);
// Get the locked balance for a token.
function getLockedBalance(address token) external view returns (uint256);
MeltHub (HyperEVM)
// Wrap native HyperEVM tokens into Melt-wrapped tokens.
function wrap(bytes32 configKey, uint256 amount) external returns (uint256 amountOut);
// Unwrap Melt-wrapped tokens back to native HyperEVM tokens.
function unwrap(bytes32 configKey, uint256 amount) external returns (uint256 amountOut);
// Bridge wrapped tokens back to the source chain.
function bridgeOut(address wrappedToken, uint256 amount, bytes32 recipient)
external payable returns (MessagingReceipt memory);
// Quote the LayerZero fee for a bridge-out transaction.
function quoteBridgeOut(address wrappedToken, uint256 amount, bytes32 recipient)
external view returns (MessagingFee memory);
// Compute the config key for a token.
function getConfigKey(address originalToken, uint32 sourceEid)
external pure returns (bytes32);
// Get full token config.
function getTokenConfig(bytes32 configKey)
external view returns (TokenConfig memory);
// Get HyperCore config for a token.
function coreConfigs(bytes32 configKey)
external view returns (uint64 coreIndexId, int8 decimalDiff, bool isConfigured);
// Get config key from wrapped token address.
function wrappedToConfigKey(address wrappedToken) external view returns (bytes32);
// Get the fee controller (address(0) if none set).
function feeController() external view returns (IMeltFeeController);
LayerZero Types
struct MessagingFee {
uint256 nativeFee; // Gas fee in native token (ETH on Ethereum, HYPE on HyperEVM)
uint256 lzTokenFee; // Optional fee in LZ token (usually 0)
}
struct MessagingReceipt {
bytes32 guid; // Unique message ID — use to track delivery
uint64 nonce;
MessagingFee fee;
}