English | 한국어
A hybrid perpetual futures exchange: orders are matched off-chain by a Rust engine, then settled on-chain in batches on Arbitrum Sepolia. Users sign EIP-712 orders, the contracts verify every signature and re-check margin, so the operator can sequence trades but cannot forge them.
Live demo: coming back soon · Contracts: Arbitrum Sepolia
flowchart LR
subgraph client [Client]
WEB[React app<br/>wagmi + viem]
end
subgraph offchain [Off-chain, Rust]
API[api server<br/>axum, REST + WS]
ENGINE[matching-engine<br/>price-time priority]
RELAYER[settlement relayer]
MIRROR[mirror bot]
end
subgraph onchain [Arbitrum Sepolia]
CH[ClearingHouse]
VAULT[USDCMarginVault]
ORACLE[OracleAdapter]
FUND[FundingEngine]
end
PYTH[Pyth Hermes]
BIN[Binance feeds]
WEB -- "EIP-712 signed orders" --> API
WEB -- "deposit / faucet" --> VAULT
BIN --> MIRROR
MIRROR -- "liquidity quotes" --> API
API --> ENGINE
ENGINE -- "fills" --> RELAYER
RELAYER -- "submitFillBatch + Pyth VAA" --> CH
PYTH --> API
CH --> ORACLE
CH --> VAULT
CH --> FUND
Order lifecycle: the browser signs an EIP-712 order and POSTs it to the API. The matching engine (strict price-time priority, integer-only math) produces fills at the maker's resting price. The relayer batches fills per market, attaches a fresh Pyth price update, and calls ClearingHouse.submitFillBatch. The contract verifies both signatures on every fill, checks the fill against a ±1% oracle price band, applies funding, updates positions, and finally requires every touched account to still have non-negative free collateral. Any violation reverts the whole batch.
More detail in docs/architecture.md.
Fully on-chain order books are expensive and slow; fully off-chain exchanges ask you to trust the operator with custody. This project splits the difference:
- Off-chain: order intake, the book, and matching. Sub-millisecond, free to cancel-and-replace, no gas per order.
- On-chain: custody (USDC vault), settlement, and risk checks. The ClearingHouse never trusts the matcher: it recovers both EIP-712 signatures, enforces the maker-price rule, bounds fills to an oracle band, and re-derives margin before accepting a batch.
The failure mode this buys: a malicious or buggy operator can censor or reorder trades, but cannot invent a fill you never signed, fill you at a price outside the band, or touch collateral beyond what settled fills justify. Withdrawal requests bump an on-chain nonce that invalidates outstanding signed orders.
The tradeoffs are documented honestly in docs/design-decisions.md and docs/known-limitations.md.
contracts/ Foundry project: ClearingHouse, vault, funding, oracle adapter, waterfall
services/ Rust workspace
matching-engine order book + numeric core (no deps, no floats, no unsafe)
api axum server: REST/WS, Pyth + Binance feeds, relayer, demo ledger
mirror Binance depth mirroring bot (testnet liquidity)
oracle, vault-quote-engine, liquidator designed + unit-tested, not yet wired in
apps/web/ React trading UI (chart, book, order form, positions, faucet)
webtestnet/ separate read-only ops dashboard prototype
Prerequisites: Rust 1.91 (pinned via rust-toolchain.toml), Foundry, Node 22 + pnpm.
API server (demo mode, no chain access needed):
cd services
DEMO_MODE=true cargo run -p api # listens on :3001Frontend:
cd apps/web
pnpm install
pnpm dev # http://localhost:5173, talks to :3001 by defaultOptional liquidity bot, mirrors Binance depth into the book:
cd services
cargo run -p mirror # API_URL defaults to http://localhost:3001Contracts:
cd contracts
forge test
forge script script/Deploy.s.sol --rpc-url $ARBITRUM_SEPOLIA_RPC --broadcast # needs DEPLOYER_KEYEnvironment variables the API understands: API_PORT (default 3001), DEMO_MODE (off-chain position ledger + liquidation loop), RELAYER_KEY + ARBITRUM_SEPOLIA_RPC (both set = relayer submits batches on-chain, otherwise it logs what it would send), PYTH_HERMES_URL, LOSS_VAULT_BPS / LOSS_PROTOCOL_BPS / LOSS_INSURANCE_BPS. Frontend: VITE_API_URL, VITE_WS_URL.
- Contracts: Solidity 0.8.24, Foundry,
via_ir. No forge-std, no OpenZeppelin: every interface and test harness is vendored here, so the whole trust surface is in this repo. - Services: Rust 1.91, axum, tokio, alloy (EIP-712 + contract calls), tokio-tungstenite. The matching engine crate has zero dependencies, forbids unsafe code, and denies float arithmetic.
- Frontend: React 18, Vite, TypeScript, wagmi v2 + viem, TradingView lightweight-charts, Tailwind.
- Oracle: Pyth pull model. Hermes SSE stream for display prices, per-batch VAA payloads for settlement.
- Infra: Docker images for the API, mirror bot, and static frontend; deployed on Railway.
Arbitrum Sepolia (chain id 421614):
| Contract | Address |
|---|---|
| ClearingHouse | 0xE76F8a552896B01AcdEcdE28F429CAcaF42f7bfB |
| USDCMarginVault | 0xBE12e78E49E573E6D006F1E90473EA44415806A8 |
| MockUSDC (public mint faucet) | 0xC381AADCf93839E456357feCC57a0eC1574665f9 |
| OracleAdapter | 0xf9ec765939C16a92BDeb24e681123651856De54c |
| FundingEngine | 0x3c1231Fbde68465047e4064343a9B987C4f6b737 |
| MarketRegistry | 0x8497Cb7B3b830BBe456eBbF16C90d1dF2BF67821 |
| InsuranceFund | 0xbae7D477B76Cb1ea2B6A0863e3bA9189461a4fe4 |
| BadDebtWaterfall | 0xB838B9b6096A3B8B8839c791DbB497BC9f941D3A |
| AccessController | 0xec4AC4ABD73997758673Dcea703DbD07F5b4B4CD |
Markets: BTC-PERP, ETH-PERP, SOL-PERP. The canonical list lives in contracts/deployments/arbitrum-sepolia.json, written by the deploy script.
Frontend design by Marsgo.
Testnet only. Unaudited. Built for learning, not for real funds. MockUSDC has an open mint, the deployer EOA holds every admin role, and several protocol pieces (on-chain liquidation, funding rate governance) are intentionally incomplete. See docs/known-limitations.md before assuming anything.