# MetaFlux client rust
[](https://crates.io/crates/metaflux-client)
[](https://docs.rs/metaflux-client)
[](https://github.com/mtf-exchange/metaflux-client-rust/actions/workflows/ci.yml)
[](./LICENSE)
Rust SDK for the MetaFlux derivatives L1 — REST + WebSocket, EIP-712 signing,
and typed builders for the node's full signed-action surface.
## Installation
Published on [crates.io](https://crates.io/crates/metaflux-client):
```toml
[dependencies]
metaflux-client = "0.1"
```
or `cargo add metaflux-client`. The crate is imported as `metaflux_client`.
A short alias crate, [`metaflux`](https://crates.io/crates/metaflux), re-exports
the entire API — `metaflux = "0.1"` and `use metaflux::...` work identically.
## What it does
- **REST** `/info` / `/exchange` / `/explorer` — snake_case JSON, plain-integer
numerics (sizes / prices on fixed-point planes), `market_id` rather than `coin`.
- **WebSocket** subscriptions — reconnect with backoff + heartbeat.
- **EIP-712 signing** — secp256k1 with deterministic (RFC-6979) nonces.
The `/exchange` surface is fully typed. Every signed action the node accepts has
a first-class request type and an `Exchange` method, including:
- **Orders** — submit / cancel / cancel-by-cloid / modify, plus batched
variants, schedule-cancel and cancel-all.
- **TWAP** — sliced orders and cancellation.
- **Leverage & margin** — update leverage, isolated-margin adjust / top-up,
portfolio-margin enroll.
- **Vaults** — create, transfer, modify, follower withdraw.
- **Staking** — delegate / undelegate, claim rewards, link staking user.
- **Spot & Earn** — spot CLOB orders, leveraged spot margin, Earn lending.
- **Account & agents** — display name, referrer, agent approval, builder-fee
approval, multisig conversion, abstraction settings, priority bids.
- **Encrypted orders** — threshold-encrypted, MEV-resistant submissions.
- **MetaBridge** — cross-collateral withdrawals to other chains.
## Quick start
```rust,no_run
use metaflux_client::{
Client,
types::order::{Order, OrderKind, Side, TimeInForce},
wallet::Wallet,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. Build a wallet from a 32-byte secp256k1 private key.
let priv_key_hex = std::env::var("MTF_PRIVATE_KEY")?;
let wallet = Wallet::from_hex(&priv_key_hex)?;
println!("wallet address: 0x{}", hex::encode(wallet.address()));
// 2. Construct the client.
let client = Client::new("https://devnet-gateway.mtf.exchange")?;
// 3. Build + sign + submit a limit order. The node assigns the oid and
// returns it in the response — the submit shape never declares one.
let order = Order {
owner: wallet.address(),
market: metaflux_client::types::MarketId(1), // BTC-PERP
side: Side::Bid,
kind: OrderKind::Limit,
size: 1_000, // 0.001 BTC if size_decimals = 6
limit_px: 5_000_000_000_000, // $50,000.0000 in tick units
tif: TimeInForce::Gtc,
stp_mode: metaflux_client::types::order::StpMode::CancelOldest,
reduce_only: false,
cloid: None,
builder: None,
position_side: None,
};
let resp = client.exchange().submit_order(&wallet, &order).await?;
println!("submitted: {resp:?}");
Ok(())
}
```
## Spot trading
The spot CLOB (v0 = IOC limit only, `limit_px > 0` on the 1e8 price plane) is a
separate book from the perp engine, keyed by a numeric **pair id**. Discover
pairs via `spot_meta()`, trade with `spot_order` / `spot_cancel`, and read
balances back with `spot_clearinghouse_state(address)`:
```rust,ignore
use metaflux_client::types::{
order::Side,
spot::{SpotCancel, SpotOrder},
};
// `client` and `wallet` as in the Quick start above.
// 1. Discover pairs. `name` is derived as "{base}/{quote}" from the token
// registry; `id` is the numeric pair id.
let meta = client.rest().info().spot_meta().await?;
let pair = meta
.pairs
.iter()
.find(|p| p.name == "BTC/USDC")
.expect("pair listed");
// 2. Place an IOC limit spot order (signed, POST /exchange).
let order = SpotOrder::ioc_limit(pair.id, Side::Bid, 1_000, 5_000_000_000);
let resp = client.exchange().spot_order(&wallet, &order).await?;
println!("spot order: {resp:?}");
// 3. Read balances back.
let bals = client
.rest()
.info()
.spot_clearinghouse_state(wallet.address())
.await?;
for b in &bals.balances {
println!("{} ({}) = {}", b.name, b.asset, b.balance);
}
// 4. Cancel a resting order by oid.
client
.exchange()
.spot_cancel(&wallet, &SpotCancel { pair: pair.id, oid: 12345 })
.await?;
```
On the WebSocket `trades` / `candles` / `fills` channels, spot prints carry the
**numeric pair id** as the `coin` label (e.g. `"101"`), not the display name —
use `spot_meta()` to map `id` to its `"{base}/{quote}"` name.
### Spot margin & Earn (devnet preview)
Leveraged spot borrows quote from the **Earn** lending pool. It is **available on
devnet (preview)**: the full deposit → borrow → leveraged-buy → close loop works,
but forced-liquidation settlement is not yet wired and per-pair maintenance ratios
are still being calibrated — don't treat it as production-ready. All six actions
are sender-authorized (the signer is the actor) and return the `202 Accepted`
admission envelope, not a synchronous oid; observe committed state via `/info`
`spot_margin_state` / `earn_state`. Decimal amounts (`amount` / `borrow` /
`shares`) are passed as **strings**; `size` / `limit_px` are integers on the
raw-lot / 1e8 planes.
```rust,ignore
use metaflux_client::types::spot::{
EarnDeposit, SpotMarginClose, SpotMarginDeposit, SpotMarginOpen,
};
// `client`, `wallet`, `pair` as above.
// Supply side: a lender funds the pool (asset = the pair's quote token id).
client
.exchange()
.earn_deposit(&wallet, &EarnDeposit { asset: pair.quote, amount: "5000".into() })
.await?;
// Borrow side: post collateral, then open a leveraged long.
client
.exchange()
.spot_margin_deposit(&wallet, &SpotMarginDeposit { pair: pair.id, amount: "100".into() })
.await?;
client
.exchange()
.spot_margin_open(
&wallet,
&SpotMarginOpen { pair: pair.id, size: 200, limit_px: 200_000_000, borrow: "400".into() },
)
.await?;
// Close the position (sells the held base, repays principal + interest to the
// Earn pool, returns the remainder; a partial fill keeps the account open).
client
.exchange()
.spot_margin_close(&wallet, &SpotMarginClose { pair: pair.id, limit_px: 200_000_000 })
.await?;
```
Read the committed state over `POST /info` with `{ "type": "spot_margin_state",
"user": "0x.." }` (collateral / borrowed / base_held / current_debt per margin
account) and `{ "type": "earn_state", "user": "0x.." }` (per-pool supplied /
borrowed / idle / share_value, plus your shares when `user` is supplied).
## Module overview
| [`wallet`] | secp256k1 keypair management + EIP-712 signing (RFC-6979 deterministic nonces) |
| [`rest`] | `RestClient` — `/info`, `/exchange`, `/explorer` endpoints |
| [`ws`] | `WsClient` — subscriptions with reconnect-with-backoff |
| [`types`] | Domain types: orders, TWAP, margin, vaults, staking, spot / Earn |
[`wallet`]: ./src/wallet/mod.rs
[`rest`]: ./src/rest/mod.rs
[`ws`]: ./src/ws/mod.rs
[`types`]: ./src/types/mod.rs
## Examples
Runnable examples live under [`examples/`](./examples/):
- `submit_limit_order.rs` — fetches `markets()`, signs a limit order, posts to `/exchange`.
- `stream_trades.rs` — opens a WS connection, subscribes to BTC-PERP trades, prints first 10.
- `create_vault.rs` — creates a vault, queries its state.
Run with `cargo run --example <name>`. Examples expect `MTF_PRIVATE_KEY` env var.
## Versioning
Pre-1.0: **minor bumps may break**. We will follow strict SemVer once we tag
`v1.0`. The wire schema is governed by the node, not this SDK — the SDK
re-exposes wire types verbatim, so wire-breaking changes upstream cascade.
## License
MIT — see [LICENSE](./LICENSE).