On-chain
ShieldedPool — the entry point for every user action. Holds the commitment tree, the nullifier set, and the proof verifiers. Never learns an amount. It also custodies all collateral directly, in one shared pool across every market — a deposit does not name a market, so there is nothing to hold per-market. Solvency is a running global check (collateral paid out can never exceed collateral deposited), not a per-market invariant. ElGamalAccumulator — holds the running encrypted total per market, per side. It stores ciphertext points and has no ability to decrypt anything. Adding a stake is point addition on BabyJubJub. Vault — holds the outcome and the market’s immutable clock (bettingCloseTime,
resolutionStartTime). It does not custody collateral for the shielded flow — see
ShieldedPool, above. Vault.split/merge/redeem still exist as plain, non-ZK functions, but
the shielded flow never calls them.
EncryptedParimutuelPool — settlement. Accepts the decrypted final totals along with a
Chaum-Pedersen proof that the decryption is honest with respect to the published committee
key. This is the only path a payout ever reads.
PythResolver — resolves a market from signed price data at a timestamp committed before
betting opened. See Resolution.
Off-chain
Sequencer — batches commitments into the tree and serves Merkle paths.Inserting one commitment costs more gas than a user action can afford, so actions only queue
a commitment and the sequencer grafts 64 at a time. Batching is a correctness requirement of
the gas budget, not an optimisation.
betEncrypted, redeemPrivate, and withdraw on
your behalf — proof-gated, not sender-gated, so nothing about their validity depends on
trusting it. deposit is never relayed this way: it moves real collateral via
transferFrom(msg.sender), so it always needs your own wallet regardless of relaying. For the
other three, relaying means your wallet address never appears on that specific transaction —
the sequencer knows it was you, but the chain does not.
That is trust relocated, not removed. Relaying adds a liveness dependency, the same way
batching does, and it is optional — configured per deployment, off unless the operator funds
it. Optionally, right after a relayed withdrawal lands, the same relayer can send the recipient
a small amount of native gas in a follow-up transaction, so a freshly-used address has enough
to transact again.
Mid-market odds are indicative. Verifying them on-chain would require the plaintext totals,
which is exactly what the encryption exists to prevent — so no payout ever reads them.
Settlement goes through a cryptographic proof instead. The odds inform; the money is proved.
The flow
1
Deposit
Collateral enters ShieldedPool’s shared pool, a commitment is queued, and the sequencer
grafts it into the tree.
2
Bet
A proof shows you own an unspent note without revealing which. The encrypted stake is
added to the accumulator. A position note is queued.
3
Resolve
The oracle resolver reads a signed price at the committed timestamp and sets the outcome.
4
Settle
The committee publishes final totals with a proof that the decryption is honest.
5
Redeem
A winning position becomes a settled note. No collateral moves.
6
Withdraw
A settled note becomes public collateral, in a fixed denomination, at a time of your
choosing.