Migrating from HL
This page explains how to move a Hyperliquid bot to the MetaFlux API.
There is no Hyperliquid-compatible shim. Your bot keeps its strategy and trading logic. The client and wire layer changes. The fastest path is the official TypeScript or Rust SDK. The SDK builds the native envelope and the EIP-712 signature for you. For other languages, implement typed-data signing directly.
If your bot trades on a Hyperliquid-style perps DEX, the move to MetaFlux rewrites the client layer. It does not rewrite the strategy. The concepts you depend on all exist on MTF: limit orders, fills, funding, cross and isolated margin, agent wallets, sub-accounts and vaults. You replace the wire shape, the action and query names, the chain ID and the asset IDs.
Scope of the change
- Wire shape. MTF-native is snake_case JSON over
POST /exchange(write),POST /info(read) andGET /ws(stream), each EIP-712-signed where required. Use the SDK, or implement the native signing scheme. - Strategy and risk logic. Unchanged. Your quoting, sizing and hedging code carries over.
- Names and some semantics. Action types and query types have new names (table below). Some behaviours differ: asset IDs, the T0 liquidation tier and agent-approval latency.
Unchanged features
- Limit, IOC and ALO orders, reduce-only, and client order ids (
cloid). - EIP-712 signing: the same signature primitive, with a different domain and chain ID.
- Cross and isolated margin, funding payments, fills and order-status reads.
- Agent wallets (hot keys with no withdrawal authority), sub-accounts and vaults.
Changes
Protocol surface
There is one MTF-native surface. You call it through the SDK, or you build the envelope yourself. The names map one to one:
| 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. Every open_orders row includes the time-in-force, cloid and trigger detail. |
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", and "0" means "no trades". See the volume rule |
WS userEvents, l2Book, candle | fills / order_updates / ledger_updates (there is no combined events channel), l2_book, candles. See WS subscriptions |
The account read splits differently from HL's. HL splits per product, into
clearinghouseState and spotClearinghouseState, and keeps position rows inside the perp read.
MetaFlux splits per lane in the same way, but it also moves position detail to its own read.
account_state therefore gives you one consistent set of money figures for the whole account.
clearinghouse_state gives you the rows. Do not rebuild HL's single object by a join of the two
frames. The two frames can be rendered a commit apart, and the joined result was true at no
single block. If you must combine them, compare the height that both frames carry.
The full catalogs are POST /exchange and
POST /info.
Chain ID
MetaFlux is its own L1. It is 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.
Base URL
MTF: https://api.<net>.mtf.exchange/{info,exchange,ws}
The gateway is the one entry point for the MTF-native surface. If you run the node yourself, it
serves the same surface at http://localhost:8080.
Asset IDs
HL and MTF both use integer asset IDs, but the integers are different. 0 on HL is the BTC perp.
0 on MTF can be ETH or any other asset, depending on the deployment. Always look up your asset
IDs through POST /info { "type": "markets" } at startup. Never hard-code them.
Numeric precision
Price and size fields are scaled integers sent as JSON strings, because IEEE-754 loses precision
above 2^53. If your bot parses with the default JS JSON.parse, use a parser that supports big
integers for these fields.
Liquidation behaviour
MetaFlux adds a T0 yellow-card grace tier that HL does not
have. At health [1.0, 1.1), the chain force-cancels your account's resting ALO orders and emits
a warning event. It does not touch positions. T1, T2 and T3 then behave like HL's Partial, Market
and Backstop tiers.
If your bot listens for liquidation events to add margin, add a handler for the new T0 event. That event is the early warning that HL does not give you. It gives you one block of grace to act.
Agent wallet semantics
An agent is a key with no withdrawal authority. HL uses the same model (see
agent wallets). The action is
approve_agent. The one mechanical difference:
an MTF agent approval takes effect one block after commit. HL typically takes two blocks. MTF is
a little faster, and the warm-up steps are the same.
Vaults
HL vaults and MetaFlux vaults are different products. The
vault_state read returns MTF's own vault types
(MFlux Vault, user vaults). HL vault addresses do not resolve. Expect MTF entities, not HL ones.
Migration steps
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. - Implement the asset lookup again, 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 protocol surface. order becomes submit_order, cancel
becomes cancel_order, and leverage and margin changes become update_leverage /
update_isolated_margin. The SDK builds the EIP-712 envelope. Only the action variant name and
the field casing differ.
Day 2
Connect the new signals.
- If you operate sub-accounts, read
account_statewithdetail: "overview". The sub-account list is one of its facets. MTF allows up to 32 sub-accounts per master. - 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 the scenario set are network parameters. See portfolio margin.
Day 3 and later
Adopt the features that only MTF has. This step is optional:
- RFQ. Request-for-quote primitives, for size that you do not want to show on the book.
- FBA. Frequent batch auction matching for designated markets. It reduces MEV.
- Cross-chain primitives. Bridge primitives that EVM contracts can call natively.
These are MTF-native actions on POST /exchange. See the API overview.
Common HL bot patterns
Simple limit-order market maker
This is the most common 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.
Liquidation watcher
This bot adds margin when the account nears liquidation. HL emits liquidation events at the
partial and market tiers. 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.
Funding-rate arbitrage bot
The funding cadence is similar: hourly by default, and configurable per market on MTF. The
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;
Governance sets the MTF oracle composition per market, through committed SetOracleWeights. If
your arbitrage depends on specific oracle providers, verify the weighted source list. See
mark prices.
Multi-account and institutional setup
HL uses a master and one agent per host. MTF uses the same setup, and adds 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.
Sub-account portfolio manager
HL allows up to 8 sub-accounts. MTF allows 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' });
MTF supports agent management, PM enrollment and margin modes per sub-account.
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 active. Read the note below. |
approveAgent | approve_agent |
updateLeverage / updateIsolatedMargin | update_leverage / update_isolated_margin |
convertToMultiSigUser | convert_to_multi_sig_user |
setReferrer / createReferral | set_referrer_by_code / register_referral_code. set_referrer binds by address, but only to an address that holds a code. A code needs trailing 30-day volume, not lifetime volume. The referee discount and the referrer share each stop at a cap on the referee's volume. See the referral program |
Copied sendToEvmWithData payloads
The chain refuses a payload copied from HL without changes. send_to_evm_with_data keeps the HL
field names, so a direct copy is tempting. Do not do it. HL accepts and ignores three fields that
MTF refuses. You will hit the first one:
source_dexmust be0. An HL payload often carriessource_dex: 1. MTF debits one ledger, the spot ledger. It refuses any other value, so that it never debits 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. The chain refuses any other value. This field is not a cross-chain lane. To leave the chain, usebridge_withdraw.
Check two more things before you port it:
- The action is active. An earlier version of this page said that the network refused it, and
told you to port to
core_evm_transfer. 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 active now. It is therefore the better target for most ports.
Full rules: send_to_evm_with_data.
Help
- File an issue in this repository (
mtf-exchange/metaflux-knowledges). - For the wire-level reference, see
POST /exchangeand the signing walkthrough.