One encrypted connection for orders, market data, and private account streams — with optional FIX, REST, and WebSocket gateways for migration.
- Brokers & venues — one native FIG server surface; legacy clients via
fig-gateway - Clients & integrators — connect once; trade and subscribe on channel paths, not three protocols
- Everyone — schema-defined messages (SBE/CBOR), sequenced streams, 0-RTT reconnect over TREE (QUIC)
Schema-native, multiplexed, zero-RTT binary protocol for trading systems.
FIG is a wire protocol — not a matching engine, account portal, or API-key issuer.
It specifies how messages move and how credentials ride on the wire (like FIX Logon or
an HTTP Authorization header). Your venue issues tokens; FIG libraries validate them
on the wire. See Authentication below.
Terms: FIG = protocol · TREE = QUIC transport (TLS 1.3, ALPN fig/1) · FSL = schema IDL (SPEC.md §1).
| I am… | Start here | You will… |
|---|---|---|
| New to FIG | Docs map | Pick a guide; don't mix SUBSCRIBE and REQUEST I/O |
| Broker / venue | Gateway guide · Architecture | Run a FIG backend + optional gateway; issue auth in your stack |
| Client / integrator | Tutorial · Quick start by language | Connect with fig-cli or a binding; use channel paths from SPEC.md |
| Migrating from FIX/REST/WS | Try it (gateway row) | Point legacy clients at :9876 / :8080 / :8090; proxy with --fig-backend |
| Persona | What you get |
|---|---|
| Venues & brokers | One native connection for order entry, live market data, and private account streams |
| Integration teams | Replace FIX + REST + WebSocket glue with one client library and one auth model |
| Gateway operators | Proxy legacy clients to a FIG backend with fig-gateway (--fig-backend) |
| Trading bot authors (TS) | Native FIG on Bun via bun:ffi over fig-ffi — no WASM; browsers use the gateway |
| Schema / tooling authors | FSL schemas compile to Rust, SBE, JSON Schema, and FIX mappings |
| Problem with status quo | FIG solution |
|---|---|
| FIX needs 4 RTTs to connect (TCP + TLS + Logon) | TREE 0-RTT session resumption — 1 RTT new, 0 RTT resumed |
| FIX is ASCII, 200–500 bytes header overhead | Binary 16-byte fixed header + compact TLV extensions |
| REST is stateless — no session, no sequencing | Three channel modes: stateless, session, affinity |
| WebSocket has no built-in schema or semantics | Schema-native framing — every frame carries a Schema ID |
| Each protocol needs its own auth, error, observability | One auth model, one error model, one tracing pipeline |
| No multiplexing — one session per FIX/TCP connection | 65,535 concurrent channels per TREE connection |
| Heavy parse cost on hot paths | SBE decode ~312 ns on reference hardware (BENCHMARKS.md) |
Fewer connections and smaller headers on the wire; faster decode on order hot paths. Full Criterion medians and tail-latency percentiles (p99/p99.9): docs/BENCHMARKS.md.
| FIG (SBE) | FIX ASCII (in-repo) | HTTP + JSON (in-repo) | |
|---|---|---|---|
| Order decode | 312 ns | ~4.8 μs | ~1.8 μs (headers) |
| Min header | 16 B | 200–500 B | 200–800 B |
| Reconnect | 0-RTT | full Logon | stateless |
Industry ballparks for generic JSON/FIX stacks appear in BENCHMARKS.md as context only — not FIG regression baselines.
git clone https://github.com/Macho0x/fig.git && cd fig
cargo run -p fig-exchange-sim # terminal 1 — server on 127.0.0.1:8443
cargo run -p fig-cli # terminal 2 — seven demos, one connectionMigration day 1 — legacy gateway in front of the same backend (REST :8080, WS :8090, FIX :9876):
cargo run -p fig-gateways --bin fig-gateway -- --fig-backend 127.0.0.1:8443Walkthrough: docs/TUTORIAL.md. You do not need to move all clients to native FIG at once.
Same TREE connection (127.0.0.1:8443 in the simulator). Each logical stream is a
channel (bidirectional TREE stream) identified by path extensions on the frame:
channel 1 → POST trading/accounts/DEMO-ACCT/orders → ExecutionReport stream
channel 2 → SUBSCRIBE marketdata/AAPL/candles/5m → CandleBarEvent stream
channel 3 → SUBSCRIBE accounts/DEMO-ACCT/balances → BalanceSnapshot, BalanceUpdate
- Example #1 — Live market data
- Example #2 — Order entry
- Example #3 — Private account stream
- Example #4 — Historical query, then resume live
Full sequences: docs/PROTOCOL.md.
| Public market data | No token — subscribe on marketdata/... paths |
| Private paths | AUTH_TOKEN extension on every frame; principal must match {account} in ChannelPath (SPEC §9.3) |
| Your venue | Issues, rotates, and revokes credentials (JWT/OAuth patterns in spec) — FIG only verifies |
| Local dev | Simulator accepts test token fig-dev-{account}; set FIG_DEV_OPEN=1 on the server to skip auth |
| Component | Role |
|---|---|
fig-core, SPEC, conformance |
Protocol implementation — CI-tested (~360 workspace tests) |
fig-exchange-sim |
Reference venue for learning — not a production exchange |
fig-gateway |
Migration bridge — run standalone or embed adapters |
| Auth / API keys / matching | Your venue stack — outside the protocol |
What the reference simulator and CLI exercise end-to-end (parity roadmap: TODO.md §17):
- Order entry (limit/market), cancel, execution reports
- Public market data subscribe (candles, ticker, agg trades, mark price, …)
- Private account streams (balances, positions, margin) with wire auth
- Historical query (GET + cursor) then resume live on the same connection
- Instrument catalog and capabilities (
GET /.well-known/instruments,/.well-known/capabilities) - Fill history on
accounts/{account}/fills - FIX / REST / WebSocket gateway translation paths
- PING/PONG, session resumption, channel sequencing
Native FIG on one TREE connection (TLS + multiplexed channels). If you know legacy APIs, the mapping is:
| You know… | FIG native | CHANNEL_PATH example |
|---|---|---|
WebSocket SUBSCRIBE / push stream |
SUBSCRIBE → STREAM_ITEM |
marketdata/AAPL/candles/5m |
REST GET / POST |
REQUEST → RESPONSE |
marketdata/AAPL/ticker, trading/…/orders |
| FIX NewOrderSingle (35=D) | REQUEST + CBOR NewOrderSingle |
trading/accounts/{account}/orders |
| FIX drop copy / MD incremental | SUBSCRIBE → STREAM_ITEM |
trading/accounts/{account}/executions |
Hyperliquid allMids |
GET → AllMidsBatch |
marketdata/ticker/all |
Wire notation (SPEC §4 names → Rust — not strings on the wire):
| Wire (SPEC) | Rust | SDK helper |
|---|---|---|
SUBSCRIBE |
FrameType::Subscribe |
subscribe_frame, subscribe_* |
REQUEST |
FrameType::Request |
request_frame, request_*, post_order |
RESPONSE |
FrameType::Response |
one-shot reply to REQUEST |
STREAM_ITEM |
FrameType::StreamItem |
server push (decode CBOR payload) |
Recommended path: fig-client (FigSdkClient) — same wire as below, less boilerplate. Full demo: fig-cli (run_demos).
SUBSCRIBE vs REQUEST: snapshot helpers (subscribe_*) do not wait for stream EOF.
Use subscribe_live + LiveSubscription::next_frame for later STREAM_ITEMs.
send_and_read is REQUEST only — using it on a live subscribe hangs.
use fig_client::{dev_auth_token, FigSdkClient};
use fig_core::messages::*;
use fig_core::transport;
use quinn::Endpoint;
// Connect once (like opening a FIX session or a single WS connection).
let client_cfg = transport::client_config()?;
let mut ep = Endpoint::client("0.0.0.0:0".parse()?)?;
ep.set_default_client_config(client_cfg);
let conn = ep.connect("127.0.0.1:8443".parse()?, "localhost")?.await?;
let client = FigSdkClient::new(&conn);
let account = "DEMO-ACCT";WebSocket-style subscribe (e.g. Binance @kline_5m) or a FIX market-data request — server pushes STREAM_ITEMs. No AUTH_TOKEN.
// wire: SUBSCRIBE → STREAM_ITEM (CandleBarEvent) — snapshot, then drop the live handle
let (candles, _frames) = client.subscribe_candles("AAPL", "5m", 2).await?;
if let Some(bar) = candles.current() {
println!("close={}", bar.close.0);
}
// wire: SUBSCRIBE → STREAM_ITEM (BestBidOffer)
let (bbo, _frames) = client.subscribe_bbo("AAPL", 3).await?;
println!("mid={:?}", bbo.implied_mid());
// Hold the recv stream for later bars (do not use send_and_read):
use fig_client::frames::subscribe_frame;
let path = "marketdata/AAPL/candles/5m";
let sub = subscribe_frame(2, 1, path, Some(path), None)?;
let (_snapshot, mut live) = client.subscribe_live(sub).await?;
while let Some(frame) = live.next_frame().await? {
println!("live {:?}", frame.frame_type);
}
// wire: REQUEST GET → RESPONSE (AllMidsBatch)
let (mids, _batch) = client.request_all_mids(4).await?;
println!("AAPL mid={:?}", mids.mid("AAPL"));REQUEST POST — same job as POST /accounts/{id}/orders or FIX MsgType D. One-shot; execution reports come back on that request stream.
let order = NewOrderSingle {
cl_ord_id: "CLI-001".into(),
side: Side::Buy,
symbol: "AAPL".into(),
order_qty: Quantity(100.0),
price: Some(Price(150.25)),
order_type: OrderType::Limit,
time_in_force: TimeInForce::Day,
account: Some(account.into()),
stop_price: None,
expire_time: None,
strategy_id: None,
security_id: None,
id_source: None,
security_exchange: None,
post_only: None,
reduce_only: None,
};
// wire: REQUEST POST → STREAM_ITEM (ExecutionReport)
let frames = client.post_order(account, &order, 1).await?;
// Decode ExecutionReport from frames (or use OrdersState for live merge)Authenticated user-data WebSocket or private FIX drop copy — AUTH_TOKEN required on every channel.
// wire: SUBSCRIBE → STREAM_ITEM (BalanceSnapshot, then BalanceUpdate)
let (balances, _frames) = client.subscribe_balances(account, 5).await?;
println!("USD={:?}", balances.balances.get("USD"));
// wire: SUBSCRIBE → STREAM_ITEM (ExecutionReport)
let (orders, _frames) = client.subscribe_executions(account, 6).await?;
println!("open orders={}", orders.open_count());
// Keep recv open for later STREAM_ITEMs (fills, position deltas):
use fig_client::frames::subscribe_frame;
let path = format!("accounts/{account}/positions");
let token = dev_auth_token(account);
let sub = subscribe_frame(7, 1, &path, Some(&path), Some(&token))?;
let (_snapshot, mut live) = client.subscribe_live(sub).await?;
while let Some(frame) = live.next_frame().await? {
println!("live {:?}", frame.frame_type);
}Like GET /candles?limit=100 then a live WebSocket subscribe — both on the same TREE connection.
// wire: REQUEST GET → RESPONSE (CandleBarBatch)
let (batch, _) = client.request_candles(
CandleBarRequest {
symbol: "AAPL".into(),
interval: "5m".into(),
start_time: None,
end_time: None,
limit: Some(100),
cursor: None,
},
7,
).await?;
println!("history bars={}", batch.bars.len());
// wire: SUBSCRIBE → STREAM_ITEM (live candles, same connection)
use fig_client::frames::subscribe_frame;
let path = "marketdata/AAPL/candles/5m";
let sub = subscribe_frame(8, 1, path, Some(path), None)?;
let (_snapshot, mut live) = client.subscribe_live(sub).await?;
while let Some(frame) = live.next_frame().await? {
println!("live {:?}", frame.frame_type);
}Run fig-gateway at the edge; translate legacy shapes to native FIG frames and proxy to your FIG backend (docs/GATEWAY.md).
use fig_gateways::rest::{parse_http_request};
use fig_gateways::rest_query::http_get_to_fig_request;
use fig_gateways::ws_catalog::legacy_ws_json_to_fig_subscribe;
use fig_gateways::fix::{parse_fix_message, fix_to_fig_order};
// wire: REST GET → REQUEST (FrameType::Request + Method GET)
let get = parse_http_request(
b"GET /marketdata/AAPL/ticker HTTP/1.1\r\nHost: localhost\r\n\r\n",
)?;
let ticker_req = http_get_to_fig_request(&get)?;
// wire: WS JSON SUBSCRIBE → SUBSCRIBE (FrameType::Subscribe)
let ws_sub = legacy_ws_json_to_fig_subscribe(
r#"{"method":"SUBSCRIBE","params":["aapl@ticker"]}"#,
1,
)?;
// Forward translated frames to your native FIG backend (fig-gateway --fig-backend)
// REST GET: proxy_frame(addr, ticker_req).await?
// WS SUBSCRIBE: BackendSession::connect(addr) then send_frame + recv_frame
// use fig_gateways::backend::{proxy_frame, BackendSession};
// wire: FIX 35=D → CBOR NewOrderSingle, then REQUEST POST at gateway
// let fix = parse_fix_message(b"8=FIX.4.4\x0135=D\x0111=CLI-001\x01...")?;
// let order = fix_to_fig_order(&fix.tags)?;Low-level wire: build Frame::new(FrameType::Subscribe, …) (or Request, etc.) with fig_core — see SPEC §4. Python/Go/C++ use the same frame types via fig-ffi / bindings/.
Day 1 — gateway only: legacy FIX/REST/WS clients hit fig-gateway; gateway speaks FIG to your backend.
Day N — native clients: bots and integrations connect directly over TREE; gateway shrinks to stragglers.
┌─────────────────────────────────────────────────────────────┐
│ FIG client (fig-cli, fig-core, bindings) │
└────────────────────────┬────────────────────────────────────┘
│ TREE — TLS 1.3, 0-RTT, 65k channels
┌────────────────────────▼────────────────────────────────────┐
│ FIG server (your venue or fig-exchange-sim reference) │
│ frames · channels · sessions · SBE/CBOR · auth on wire │
└────────────────────────┬────────────────────────────────────┘
│ optional
┌───────────────▼───────────────────┐
│ Gateway: FIX · REST · WebSocket │
│ legacy clients → native FIG │
└───────────────────────────────────┘
Reference crates: fig-core (protocol) · fig-gateways (adapters) · fig-exchange-sim (demo server) · fig-fsl (schemas). Module index: docs/API.md.
FIG is wire infrastructure, not an exchange. Every broker follows the same path: Day 1 gateway for legacy clients, Day 2 native FIG over TREE for performance clients. Binance and Hyperliquid wire shapes are gateway aliases only — see AGENTS.md and docs/GATEWAY.md. Crypto perps use the shared instrument model (ADR 0007); colo order entry uses SBE order path.
| Language | Get connected | Docs |
|---|---|---|
| Rust | cargo run -p fig-cli |
fig-core, fig-cli |
| Python | cargo build -p fig-python then import fig |
fig-python |
| Go / C# / TS / Java / OCaml / Zig / C++ | cargo build -p fig-ffi + binding README |
bindings/ |
Browser: no in-browser FIG stack — use fig-gateway REST/WebSocket against your FIG backend.
FFI bindings share fig-ffi for connect, request, subscribe, and SBE encode/decode on the hot path. Per-language generated SBE for complex messages is evolving; prefer FFI wire codecs until your binding's conformance coverage matches Rust.
Roadmap and parity definition: TODO.md §0 (active backlog) · §16–21 reference. Status key: ✅ shipped · 🔶 partial · ⬜ planned · 🏛 venue-owned.
| Language | SDK status | Package / path | FSL codegen (ftlc) |
Native FIG client |
|---|---|---|---|---|
| Rust | ✅ Reference | fig-core, fig-cli |
✅ full (Rust + SBE) | Tier 1–4 — subscribe_live + LiveSubscription |
| Python | ✅ Reference binding | fig-python (PyO3) |
✅ full + CBOR via PyO3 | request() (EOF); subscribe() snapshot; subscribe_live() / FigPySubscription.next() |
| C++ | ✅ FFI wrapper | bindings/cpp → fig-ffi |
✅ types + generated SBE | fig::Client::subscribe + Subscription::next over fig.h |
| C# | ✅ FFI wrapper | bindings/csharp → fig-ffi |
✅ types + SbeGenerated.cs |
FigClient.Subscribe + FigSubscription.Next |
| Go | ✅ FFI wrapper | bindings/go → fig-ffi |
✅ types + sbe_generated.go |
Client.Subscribe + Subscription.Next |
| Java | ✅ FFI wrapper | bindings/java → fig-ffi |
✅ --lang java |
JNI figClientSubscribe / figClientSubNext |
| TypeScript | ✅ FFI wrapper | bindings/typescript → fig-ffi |
✅ types + generated SBE | Bun FigClient.subscribe / FigSubscription.next; browsers/Node use gateway |
| OCaml | ✅ FFI wrapper | bindings/ocaml → fig-ffi |
✅ records + variant enums | C stubs subscribe / sub_next over fig.h |
| Zig | ✅ FFI wrapper | bindings/zig → fig-ffi |
✅ types + generated SBE | @cImport subscribe / Subscription.next |
C ABI: fig-ffi + bindings/README.md.
Index with “if you want to…” paths: docs/README.md.
| Document | Description |
|---|---|
| docs/README.md | What to read, in what order; SUBSCRIBE vs REQUEST |
| AGENTS.md | Contributor guide — one standard, gateway alias rules |
| SPEC.md | Normative protocol specification |
| docs/TUTORIAL.md | Getting started and CLI walkthrough |
| docs/PROTOCOL.md | Worked sequences and integration patterns |
| docs/STREAMING.md | Live subscribe paths and WS catalog |
| docs/QUERY.md | Historical queries and REST GET mapping |
| docs/GATEWAY.md | Legacy gateway deployment and alias E2E tests |
| docs/SBE_ORDER_PATH.md | Colo SBE order entry path |
| docs/adr/0007-crypto-instrument-model.md | Crypto perp instrument catalog (FSL) |
| docs/BENCHMARKS.md | Criterion microbenches + tail-latency harness |
| docs/API.md | Crate and module index |
| TODO.md §0 | Active backlog (priority-ordered open work) |
| TODO.md §18 | 1.0 release criteria + venue adoption (§19–21) |
| docs/DEPLOYMENT.md | HA deployment and operations |
| docs/PUBLISHING.md | Build and install SDKs |
| schemas/orders.fsl | Example FSL schema |
| TODO.md | Implementation roadmap |
cargo test --workspace
cargo test -p fig-cli # SDK demos + SBE order demo
cargo test -p fig-gateways --test gateway_legacy_ws_alias_e2e
cargo bench -p fig-bench # Criterion microbenches (medians)
cargo run --release -p fig-bench --bin fig-latency # tail latency p99/p99.9Dual-licensed under MIT or Apache-2.0. See CONTRIBUTING.md.
.png)