Titandocs

Matching engine

The Go engine — binaries, the reject-never-round contract, and the build flag that eats your binary.

server/engine — deterministic off-chain matching. Go.

Binaries (cmd/)

BinaryRole
engineThe matching engine + REST API + admin firehose
simulatorDrives synthetic activity against a running engine
flattenCloses every open position (test-stack reset)

go build ./cmd/a ./cmd/b does not write binaries. With multiple packages, go build only compiles them as a check and discards the output. Build one package at a time with -o:

go build -o ./engine ./cmd/engine
go build -o ./simulator ./cmd/simulator

This costs people an afternoon roughly once per team.

Quickstart

From the repo root: build contracts, upload the DAR, create the Vault, seed counters.

Generate test users and on-chain deposits, then restart the engine so it picks up keys.json.

Run the engine. ADMIN_TOKEN enables the indexer firehose and the admin UI on :8080.

Drive activity from another terminal with simulator.

Optionally flatten everyone to reset.

REST API

RouteAuthPurpose
POST /v1/session-authwallet signatureOpen a trading session
POST /v1/orderssession signaturePlace
DELETE /v1/orders/:idsession signatureCancel
POST /v1/tpslsession signatureArm TP/SL on a position
GET /v1/orderbook?depthpublicBook snapshot
GET /v1/user/mesession signature + signed bodyUser snapshot
GET /v1/admin/events/streambearer ADMIN_TOKENSSE firehose
GET /v1/admin/users/{party}/orders?market=bearerLive open orders

?market= on the admin order read is mandatory since c879f7f (400 market_required), and the reserved value all only exists from that same commit. The BFF depends on both.

Reject, never round

The engine validates against the market grid and rejects. It never silently adjusts an order.

RuleConstraint
SizeExact multiple of 10^-szDecimals
PriceInteger, or ≤5 significant figures and6 - szDecimals decimals
Notionalprice × size ≥ minNotional, opening leg only — a reduce is exempt

Rejections arrive as HTTP 200 with ok: false.

Rejecting rather than rounding is the right call for a derivatives venue: a rounded size changes the margin requirement the user just previewed. Better to refuse and let the client snap deliberately — which is exactly what market-grid.ts does.

Key concepts

ConceptDetail
Session keysEd25519, bound to a party by fingerprint. Every order signed with one
NoncesPer-operation. A reused nonce is rejected even for an identical payload
WALSigned write-ahead log. The indexer and the SSE firehose both read it
Position epochspos-<32 hex>. A flip closes one epoch and opens another
TriggersPosition-attached TP/SL. armedfired | cancelled. No stop-entry trigger
FundingPeriodic long↔short payment, fundingIntervalSeconds per market
LiquidationTriggered on maintenance margin fraction, priced off the oracle index

There is no stop-entry trigger. TP/SL can only attach to an existing position — which is why the interface parks LIMIT-order TP/SL in stores/pending-tpsl and arms it after the fill.

Commitments

Fills become leaves in a per-user merkle tree. On cadence the engine commits a UserBatch root per user and a global BatchManifest. Withdrawals are evaluated against the last committed batch, which is why cadence is directly the withdrawal latency.

Configuration

VarPurpose
ADMIN_TOKENEnables the firehose + admin UI
LEDGER_API_URLCanton ledger
REDIS_URLOracle index price source
MARKETS_FILEPath to markets.json

keys.json holds generated test-user keys and is read at startup. Generating users while the engine runs does nothing until you restart it — a classic "why does the deposit not show up".

On this page