Quickstart
This page takes a client through one full round trip on testnet: deposit, order, cancel and withdrawal.
Status. stable wire surface. Testnet endpoints, no mainnet warranty.
At the end of this page, your TypeScript, Python or curl session has deposited, placed an order, cancelled it and withdrawn on testnet. It takes about 5 minutes.
Prerequisites
- An EVM private key: any 32-byte hex value. Generate a new one. Never reuse a mainnet key.
- USDC on a MetaBridge source chain (Base or Arbitrum). On testnet, the faucet replaces this.
curlor any HTTP client.
Endpoints
The gateway is the one public entry point. It serves the MTF-native surface.
| Service | URL (testnet) |
|---|---|
| Gateway front door | https://api.testnet.mtf.exchange |
| MTF-native | POST /info · POST /exchange · GET /ws |
| EVM JSON-RPC | POST /evm |
| Faucet | POST /faucet |
| Explorer | https://app.mtf.exchange/explorer |
The faucet is not a separate service. It is the POST /faucet route on the gateway. If you run
the node yourself, it serves the same native surface (/info · /exchange · /ws · /faucet)
at http://localhost:8080. See POST /faucet.
Networks has the full list, with testnet and, after launch, mainnet.
1. Get testnet USDC
curl -X POST https://api.testnet.mtf.exchange/faucet \
-H 'content-type: application/json' \
-d '{"address":"0x<YOUR_ADDRESS>","expires":<unix seconds>,"signature":"0x<personal_sign result>"}'
# -> {"address":"0x…","usdc":3000,"mtf":10,"status":"queued"}
The recipient signs the claim with EIP-191 personal_sign, over a four-line text that names the
address, the chain id and expires. The node refuses an unsigned claim since node 0.9.17. See
sign the claim.
One claim grants 3000 USDC cross-collateral and 10 MTF spot tokens. Each address can claim only
once. A second claim returns 429 address already funded. The faucet also limits each source IP
to one claim per minute, in addition to the rule per address. The grant is fixed. Send no
amount. See Limits. The grant is "queued" and lands about 1 block later.
Wait a moment before you confirm the balance:
"queued" means staged, not creditedThe faucet transfers out of a reserve account. It does not create tokens, so the grant lands about
one block later. The faucet checks the reserve before it responds, so a 200 means the reserve
could pay at that moment. Confirm with account_state below before you trade. See
the reserve.
The faucet exists for testnet only. To fund a real account with bridged USDC, deposit through the
MetaBridge custody bridge. Call deposit(mtfDest, amount) on the source chain. Never send a plain
transfer to the custody address. See
bridge deposit.
The raw curls below use the MTF-native surface on the gateway, with snake_case types such as
account_state / open_orders. The @metaflux-dex/client examples use the same native surface.
The SDK builds the signed envelope for you.
curl -X POST https://api.testnet.mtf.exchange/info \
-H 'content-type: application/json' \
-d '{"type":"account_state","address":"0x<YOUR_ADDRESS>"}'
The response shows data.account_value: "3000".
2. Place a limit order
Placing orders is the main order guide. It covers the raw wire request and response, the two number planes, and a tiered map of every order action.
Signing has the full signing flow. For this quickstart, use the official
TypeScript SDK, @metaflux-dex/client. It ships before mainnet. See
TypeScript SDK.
import { Client } from '@metaflux-dex/client';
const client = new Client({
baseUrl: 'https://api.testnet.mtf.exchange', // MTF-native is the gateway default path
privateKey: Buffer.from(process.env.PRIVATE_KEY!.replace(/^0x/, ''), 'hex'), // 32 bytes
});
const owner = '0x<YOUR_ADDRESS>';
// `markets()` keys by `coin` (the symbol); the numeric id a signed action
// needs is `signing_id` on `markets_meta`, the STATIC read. There is no
// `asset_id` field — reading one gives you `undefined`.
const meta = await client.info.marketsMeta();
const btc = meta.perp.find((m) => m.coin === 'BTC')!;
const result = await client.placeOrder({
venue: 'perp',
owner,
market: btc.signing_id,
side: 'bid', // 'bid' = buy, 'ask' = sell
kind: 'limit',
size: 1_000, // raw lots, scaled by the market's sz_decimals
limit_px: 5_000_000_000_000, // 1e8 fixed-point plane
tif: 'gtc',
stp_mode: 'cancel_newest',
reduce_only: false,
});
if (result.route === 'batch_order') {
console.log('order status:', result.legs[0]?.status);
}
Raw curl, in the MTF-native shape. You build the signature yourself. See signing.
curl -X POST https://api.testnet.mtf.exchange/exchange \
-H 'content-type: application/json' \
-d @order.json
Here order.json is the signed MTF-native envelope that you assembled.
Spot trading example
Spot is a token-for-token CLOB, separate from perps. It has no leverage
and no positions. Place a spot order with the native
spot_order action. It takes a spot pair id (not a
perp market), a side, a limit_px, a size and a tif. A resting gtc/alo order locks
reserved-balance escrow. An ioc order never rests.
// the `action` you sign and POST to /exchange (sender-authorized; owner is optional)
{
"type": "spot_order",
"order": {
"pair": 200, // spot pair id from /info, not a perp market id
"side": "bid", // bid = buy base (pays quote); ask = sell base
"size": 100000000,
"limit_px": 200000000, // 1e8 plane; 0 places a market order (must use tif "ioc")
"tif": "gtc",
"stp_mode": "cancel_oldest"
}
}
The synchronous response carries the assigned oid with a resting or filled entry. This is
the same status union as a perp order. Read your spot balances and open spot orders through
POST /info. Cancel with
spot_cancel, which refunds the escrow.
3. Check the order on the book
curl -X POST https://api.testnet.mtf.exchange/info \
-H 'content-type: application/json' \
-d '{"type":"open_orders","address":"0x<YOUR_ADDRESS>"}'
The response shows your order with the oid from step 2.
You can also subscribe to streaming updates. This is the preferred method for any real use:
const ws = await client.connectWs();
ws.onMessage((f) => {
if (f.channel === 'order_updates') console.log('event:', f.data);
});
await ws.subscribe({ type: 'order_updates', user: owner });
4. Cancel
if (result.route === 'batch_order') {
const status = result.legs[0]?.status;
const oid = status && 'resting' in status ? status.resting.oid : undefined;
if (oid !== undefined) await client.cancelOrderNative({ owner, market: btc.signing_id, oid });
}
# raw curl
curl -X POST https://api.testnet.mtf.exchange/exchange \
-d @cancel.json
5. Withdraw
await client.mbWithdraw({
chain: 'Arbitrum',
asset: 0, // 0 = USDC cross-collateral
amount: 100_000_000, // 100 USDC, base units
dst_addr: '0x<DESTINATION>',
});
This call queues a MetaBridge withdrawal. The MetaFlux validator set co-signs it to a ⅔
stake-weighted quorum. Then the dispute window elapses, which takes a few minutes. Then you can
claim on the destination chain. See bridge.
Request flow
Next steps
- Placing orders: the main order guide, with batches, spot, cancels and number planes
- Signing: what the SDK signing does
- Agent wallets in practice: the production hot-key pattern
- Order types: order types other than plain limit orders
- Error handling: admission, commit and network errors
- WS subscriptions: push for real-time data
- Migrating from HL: read this first if you have a Hyperliquid bot
Troubleshooting
Show troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
401 signer is not the sender | Wrong EIP-712 domain chain id | The SDK signs against MTF_CHAIN_ID (testnet 114514, mainnet 8964) by default. Do not override chainId on a call unless you mean to target a different network |
400 action: <parse error> | Wrong field name, wrong type, or a missing required field | Check the action's entry in the catalog |
404 unknown user on info | The address has no on-chain state yet | Deposit first (faucet) |
429 rate limit | Too many requests | See rate limits, and back off |
| Withdrawal stuck on destination | MetaBridge withdrawal pending (dispute window) | Wait for the ⅔ co-signature and the dispute window, then claim on the destination chain. See bridge |
See also
- Networks: testnet and mainnet endpoints and chainIds
- Signing: the full envelope spec
POST /exchangePOST /info- WS