Titandocs

Batches & commitments

How off-chain fills become on-chain merkle commitments, and how to verify one.

Matching is off-chain, so the ledger needs a compact, verifiable summary of what happened. That summary is a two-level merkle structure committed on a fixed cadence.

The two levels

Per-user tree → UserBatch

Every fill for a party becomes a leaf. Leaves accumulate into a per-user merkle tree; the root is committed on-chain as a UserBatch contract, chained to that user's previous batch.

Global tree → BatchManifest

Each UserBatch root is itself a leaf of the global tree for that cadence window. Its root is committed as a BatchManifest, numbered monotonically by batchNum.

fill  fill  fill        fill  fill
 └──┬──┘     │           └──┬──┘
    ▼        ▼              ▼
  user tree (Alice)     user tree (Bob)
      root                 root
        └────────┬───────────┘

          global tree  ──►  BatchManifest #1421  (on-chain)

Why chained per-user batches

Chaining each UserBatch to its predecessor means a user's whole trading history is a hash chain they can walk independently. Dropping or reordering one batch breaks the chain — the operator cannot quietly rewrite an interval.

GET /v1/users/:party/user-batches returns them oldest first precisely so the chain can be verified in order.

Reading commitments

RouteReturns
GET /v1/batches?limitbatch_manifests, newest first
GET /v1/batches/:batchNumOne manifest
GET /v1/user-batches?limitAll user batches
GET /v1/users/:party/user-batches?limitOne user's chain, oldest first

Verifying

The BFF exposes cross-checks that compare what the engine claims against what the ledger records. These are the audit surface — they do not require trusting engine self-reporting.

# Every user batch: engine leaf set ↔ on-chain UserBatch root
curl 'http://localhost:4000/v1/verifications/user-batches?status=mismatch&limit=50'

# Every manifest: engine global root ↔ on-chain BatchManifest
curl 'http://localhost:4000/v1/verifications/manifests?status=mismatch&limit=50'

A non-empty status=mismatch result is an incident, not a warning. It means the off-chain engine and the on-chain commitment disagree about what was traded.

Cadence trade-off

Shorter cadenceLonger cadence
Faster finality for withdrawalsFewer ledger writes, lower cost
More on-chain trafficLarger exposure window if the engine misbehaves

Withdrawals are gated on the batch containing the relevant fills being committed — so cadence is directly the user-visible withdrawal latency. See Withdrawals.

On this page