Migrating from HL
MetaFlux speaks its own MTF-native protocol — there is no Hyperliquid-compatible shim. Your bot keeps its strategy and trading logic; what changes is the client/wire layer. The fastest path is the official TypeScript or Rust SDK, which builds the native envelope and EIP-712 signature for you. For other languages, implement typed-data signing directly.
If your bot already trades on a Hyperliquid-style perps DEX, the move to MetaFlux is a client-layer rewrite, not a strategy rewrite. The concepts you depend on — limit orders, fills, funding, cross / isolated margin, agent wallets, sub-accounts, vaults — all exist on MTF. What you swap out is the wire shape, the action / query names, the chain ID, and the asset IDs.
The shape of the move
- Wire shape. MTF-native is snake_case JSON over
POST /exchange(write),POST /info(read), andGET /ws(stream), each EIP-712-signed where required. Adopt the SDK or implement the native signing scheme. - Strategy & risk logic. Unchanged — your quoting, sizing, and hedging code carries over.
- Names & a few semantics. Action types and query types are renamed (table below) and a handful of behaviours differ (asset IDs, the T0 liquidation tier, agent-approval latency).
What works the same
- Limit / IOC / ALO orders, reduce-only, client order ids (
cloid). - EIP-712 signing — same signature primitive, different domain and chain ID.
- Cross / isolated margin, funding payments, fills and order-status reads.
- Agent wallets (hot keys with no withdrawal authority), sub-accounts, vaults.
What changes
1. Protocol surface
There is one MTF-native surface; you call it through the SDK or build the envelope yourself. Names map cleanly:
| You used on HL | MTF-native equivalent |
|---|---|
POST /exchange order | submit_order / batch_order |
POST /exchange cancel | cancel_order / cancel_by_cloid |
POST /exchange modify / batchModify | modify / batch_modify |
POST /info meta | markets |
POST /info clearinghouseState | Two reads, not one. account_state for the collateral and margin health, clearinghouse_state for the position rows. HL keeps positions inside the account read; MetaFlux does not — see below |
POST /info spotClearinghouseState | account_state — the spot.balances array. There is no separate spot read |
POST /info openOrders / frontendOpenOrders | open_orders — one kind for both. There is no separate "frontend" variant; the time-in-force, cloid and trigger detail is folded into every open_orders row. |
POST /info userFills | user_fills |
POST /info candleSnapshot | candle_snapshot (the standalone candle type is removed). candle_type selects one of THREE series: mark (the default), oracle or trade. A mark or oracle bar is a price series, not executions — its v and q read "0" and its n reads 0. v, q and n can also be ABSENT on any series, because durable history holds no volume for the bucket; a trade bar served from history drops q in every case. Test that the key is present before you read it — absent means "no data", "0" means "no trades". See the volume rule |
WS userEvents, l2Book, candle | fills / order_updates / ledger_updates (there is no grab-bag events channel), l2_book, candles — see WS subscriptions |
The account read splits differently from HL's. HL splits per PRODUCT —
clearinghouseState and spotClearinghouseState — and keeps position rows
inside the perp one. MetaFlux splits the same way per lane, but it also moves
position DETAIL out to its own read. So account_state gives you one internally
consistent set of money figures for the whole account, and
clearinghouse_state gives you the rows. Do not rebuild HL's single object by
joining the two frames: they can be rendered a commit apart, and the joined
result was true at no single block. Compare the height both frames carry if you
must combine them.
The full catalogs are POST /exchange and POST /info.
2. Chain ID
MetaFlux is its own L1, not an HL deployment. Sign against the MetaFlux chain ID, not HL's:
| Network | MTF chainId |
|---|---|
| Mainnet | 8964 (0x2304) |
| Testnet | 114514 (0x1bf52) |
| Devnet / local | 31337 (0x7a69) |
The MTF EIP-712 domain uses name = "MetaFlux", version = "1", verifyingContract = 0x0. See networks and signing.
3. Base URL
MTF: https://api.<net>.mtf.exchange/{info,exchange,ws}
The gateway is the single front door for the MTF-native surface. Running the node yourself, the same surface is served at http://localhost:8080.
4. Asset IDs
HL and MTF both use integer asset IDs but the integers are not the same. 0 on HL is BTC perp; 0 on MTF might be ETH or anything else depending on the deployment. Always look up your asset IDs via POST /info { "type": "markets" } at startup; never hard-code.
5. Numeric precision
Price and size fields are scaled integers transmitted as JSON strings because IEEE-754 loses precision past 2^53. If your bot parses with default JS JSON.parse, switch to a big-int-aware parser for these fields.
6. Liquidation behaviour
MetaFlux adds a T0 yellow-card grace tier that HL does not have. Practical effect: at health [1.0, 1.1) your account's resting ALO orders get force-cancelled and a warning event is emitted, but positions are not touched. Then T1 / T2 / T3 behave like HL's Partial / Market / Backstop.
If your bot listens for liquidation events to trigger margin top-ups, add a handler for the new T0 event — that's the early-warning signal HL doesn't give you. Catching it gives you one block of grace to act.
7. Agent wallet semantics
An agent is a key with no withdrawal authority — same model as HL (see agent wallets). The action is approve_agent. The one mechanical difference: MTF's agent approval becomes effective one block after commit, vs HL's typically two-block latency. Slightly faster; same warm-up dance.
8. Vaults
HL vaults and MetaFlux vaults are not the same product. The vault_state read returns MTF's own vault types (MFlux Vault, user vaults). HL vault addresses won't resolve. Expect MTF entities, not HL ones.
Step-by-step migration
Day 0 — adopt the native client
- Install the TypeScript or Rust SDK (or implement typed-data signing for your language).
- Point
baseUrlat the MTF gateway and setchainIdfor your target network. - Re-implement asset lookup against
POST /info { "type": "markets" }.
Day 1 — map your actions
Translate each action your bot sends to its MTF-native equivalent (see the table in §1). order → submit_order, cancel → cancel_order, leverage / margin changes → update_leverage / update_isolated_margin. The EIP-712 envelope is built by the SDK; only the action variant name and field casing differ.
Day 2 — wire the new signals
- Read
account_statewithdetail: "overview"if you operate sub-accounts (MTF allows up to 32 subs per master); the sub-account list is one of its facets. - Add a handler for T0 yellow-card events on the
notificationsWS channel (kindyellow_card). - If you depend on portfolio margin, enroll on MTF with
user_portfolio_margin. The threshold and scenario set are network parameters — see portfolio margin.
Day 3+ — adopt MTF-only features
Optional. If you want features HL doesn't have:
- RFQ — request-for-quote primitives, useful for size that doesn't want to advertise on the book.
- FBA — frequent batch auction matching for designated markets, reduces MEV.
- Cross-chain primitives — bridge primitives natively callable from EVM contracts.
These are MTF-native actions on POST /exchange; see the API overview.
Top HL bot patterns — concrete migration
1. Simple limit-order MM (the canonical pattern)
import { Client } from '@metaflux-dex/client';
const client = new Client({
baseUrl: 'https://api.testnet.mtf.exchange',
privateKey: Buffer.from(process.env.PRIVATE_KEY!.replace(/^0x/, ''), 'hex'),
});
const owner = '0x<YOUR_ADDRESS>';
// asset lookup: HL `meta.universe` → MTF `marketsMeta` (`signing_id` is the
// numeric id a signed action needs; may not be 0)
const meta = await client.info.marketsMeta();
const BTC = meta.perp.find((m) => m.coin === 'BTC')!.signing_id;
// order / cancel — your strategy logic, native action names
await client.submitOrderNative({
owner, market: BTC, side: 'bid', kind: 'limit',
size: 1_000, limit_px: 1_000_000_000_000,
tif: 'gtc', stp_mode: 'cancel_newest', reduce_only: false,
});
The strategy stays; the client layer becomes the SDK call.
2. Liquidation-watching bot (margin top-up)
HL emits liquidation events at the partial / market tier. MTF adds a yellow_card notification as the earliest signal, on the dedicated notifications channel.
import { isChannelFrame } from '@metaflux-dex/client';
const ws = await client.connectWs();
ws.onMessage((f) => {
if (!isChannelFrame(f, 'notifications')) return;
for (const record of f.data) {
switch (record.kind) {
case 'yellow_card':
// T0 — one block to act; ALO orders already cancelled
deposit(YELLOW_CARD_DEPOSIT);
break;
case 'forced_close_tier':
// T1 partial OR T2 full — too late for prevention
emergencyUnwind();
break;
}
}
});
await ws.subscribe({ type: 'notifications', user: owner });
See risk-watcher for the full pattern.
3. Funding-rate arb bot
Funding cadence is similar (hourly by default, configurable per market on MTF). Formula structure is identical; the read is the native funding query.
const funding = await client.info.fundingHistory('BTC');
// values may differ from HL because oracle composition differs
const rate = funding.samples.at(-1)?.funding_rate;
MTF's oracle composition is governed per-market (committed SetOracleWeights) — if your arb depends on specific oracle providers, verify the weighted source list. See mark prices.
4. Multi-account / institutional setup
HL: master + agents per host. MTF: same, plus multi-sig accounts.
// existing: master + agents (each host is its own Client with its own key;
// `owner` on each action routes it to the master, not a client option)
await master.approveAgent({ agent: host1AgentAddr });
await master.approveAgent({ agent: host2AgentAddr });
// new on MTF: convert master to multi-sig for cold custody
await master.convertToMultiSigUser({
threshold: 2,
signers: [signer1, signer2, signer3],
});
// every subsequent master-level action then requires 2 sigs;
// agents still work as before for trading actions
See multi-sig.
5. Sub-account portfolio manager
HL sub-accounts: up to 8. MTF: up to 32.
// MTF: create one of up to 32 subs
await master.createSubAccount({ name: 'desk-A', shared_stp_group: false });
await master.subAccountTransfer({ sub_index: 0, deposit: true, amount: '10000' });
Per-sub agent management, per-sub PM enrollment, and per-sub margin modes are all supported.
Reference table
| Action you used on HL | MTF-native action |
|---|---|
order (place limit / IOC / ALO) | submit_order / batch_order |
cancel (by OID) | cancel_order |
cancelByCloid | cancel_by_cloid |
modify / batchModify | modify / batch_modify |
usdSend / spot transfers | native spot transfer actions |
withdraw3 | bridge_withdraw |
sendToEvmWithData | send_to_evm_with_data (same field names) — or core_evm_transfer. Both are live. Read the note below. |
approveAgent | approve_agent |
updateLeverage / updateIsolatedMargin | update_leverage / update_isolated_margin |
convertToMultiSigUser | convert_to_multi_sig_user |
setReferrer / createReferral | set_referrer (semantics may differ) |
sendToEvmWithData — a copied payload will be refused
send_to_evm_with_data keeps the HL field names, so it is tempting to copy the
payload across unchanged. Do not. Three fields that HL accepts and ignores are
refused here, and the one you will hit is the first:
source_dexmust be0. An HL payload commonly carriessource_dex: 1. MTF debits one ledger, the spot ledger, so it refuses any other value rather than debiting a ledger you did not name.to_perpmust befalse. The EVM side has no perp account to credit.destination_chain_idmust be0or the local EVM chain id. Any other value is refused. It is not a cross-chain lane — usebridge_withdrawto leave the chain.
Two more things before you port it:
- The action is live. An earlier version of this page said the network refused
it and told you to port to
core_evm_transferinstead. That is no longer true. - It debits the spot ledger only. It cannot move USDC held as perp collateral.
core_evm_transfercan, and it is live now, so it is the better target for most ports.
Full rules: send_to_evm_with_data.
Getting help
- This repo (
mtf-exchange/metaflux-knowledges) — file an issue. - See
POST /exchangeand the signing walkthrough for the wire-level reference.