Public aggregates.
Private everything else.
A normal perp publishes the book, the position, and the liquidation price. This market splits those. The mark, open interest, insurance, and fees are a public account anyone can read. Each trader's position, collateral, and intent live in a portfolio that is delegated into a MagicBlock Private Ephemeral Rollup on Intel TDX. The rollup serves that account only to the trader, the keeper, and at most two auditors the trader names.
There is no order book and no slab. A trader submits one intent. The keeper crosses up to 16 portfolios in one call. Every fill happens at the same mark. Buys take sells. With pool_cap at 0 the pool takes no inventory, so a batch fills only what both sides brought. Unfilled size stays open for the next batch.
Time in this market is rollup time. A rollup slot on this devnet validator was measured at 9.956 ms (1,133 slots in 11,280 ms). The bounty4 figures of 150 slots and 6,480,000 slots assume a 400 ms base-layer slot, which would be a 60 second mark and a 30 day EWMA half-life. Those same wall-clock targets were converted onto the measured rollup clock and then written into the market. Nothing in the program can change them.
Collateral is native SOL in one vault. The vault is owned by the Dark Percolator program and is never delegated, so the private rollup cannot move the lamports. Insurance sits in that same vault. The program has no instruction that pays insurance or pool lamports back out.
The hedge is a ticket, not an order. commit_market on the rollup schedules record_hedge. That instruction accepts only the keeper's escrow PDA as signer, which only the MagicBlock program can produce, and it writes the pool's inventory onto the hedge ticket. An operator reads the ticket and places any Phoenix hedge from their own wallet.
The mark is a push from the oracle key, bounded by a solvency envelope. The first push may set any positive price. Later pushes may move the mark by at most max_move_bps_per_slot times the rollup slots elapsed. When the oracle reports the price as stale, the EWMA walks toward the indicative price and the trading fee rises with the gap between the EWMA and its reference.
The accounts on chain.
Cluster: devnet (https://api.devnet.solana.com). Program Cf8dRz9xhvdwU825LBu6SGHa3gcHMiJwVh8LciJK1hPz, executable, upgradeable loader. Authority, oracle, and keeper are the same key.
| Account | Address | Owner | Lamports | Data |
|---|---|---|---|---|
| Market | 64kBzfr4wFLLiNBAh5iZ1xQLsEpFvAmqETFArkUKSccN | Dark Percolator | 2,230,120 | 311 bytes |
| Vault | CZXGH62x18zkpbzthFPAdB5tS9dxeML1zwUp191AFGz3 | Dark Percolator | 50,853,440 | 40 bytes |
| Hedge ticket | J9Af88NzfUJ6GazSDHwSBB7rBRgJnnazcPwTmRW7NZkL | Dark Percolator | 1,183,640 | 105 bytes |
| Keeper escrow, index 255 | Eb4b35WtV64d6gjaEvpHHxfMwhry6NeCTtyyUtYstaXK | System program | 850,240 | 0 bytes |
How the PDAs are derived.
| Account | Program | Seeds | Bump |
|---|---|---|---|
| Market | Dark Percolator | ["market", authority, id_le_u64] with id 0 | 254 |
| Vault | Dark Percolator | ["vault", market] | 253 |
| Hedge ticket | Dark Percolator | ["hedge", market] | 252 |
| Escrow | DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh | ["balance", keeper, 255] | 254 |
A future portfolio is ["portfolio", market, trader] under Dark Percolator. Its permission account is ["permission:", portfolio] under ACLseoPoyC3cBqoUtkbjZ4aDrkurZW86v19pXz2XQnp1. Neither exists yet.
What the market instructions name.
Written once.
Nothing in the program can change them.
Meanings below use the measured 9.956 ms rollup slot.
| Field | Value | Meaning at 9.956 ms/slot |
|---|---|---|
| mm_bps | 500 | 5% maintenance margin |
| im_bps | 500 | 5% initial margin |
| base_fee_bps | 1 | 1 bp when the oracle is fresh |
| max_fee_bps | 10,000 | fee can rise to 100% of notional while the oracle is stale |
| liq_fee_bps | 5 | 5 bp liquidation fee |
| liq_fee_cap | 50,000,000,000 | 50 SOL cap on that fee |
| max_move_bps_per_slot | 1 | about 100 bp per second of mark movement |
| max_mark_age_slots | 6,027 | about 60.0 seconds, then trading stops |
| ewma_halflife_slots | 260,348,936 | about 30.0 days |
| pool_cap | 0 | a batch takes no pool inventory |
max_move_bps_per_slot is an integer. The 49 bp-per-400 ms bounty4 envelope is 122.5 bp per second. One basis point per rollup slot is the closest integer, about 100 bp per second.
Margin is symmetric: initial equals maintenance, and initial is not allowed below maintenance. A new intent enters the batch only when a full fill would still clear initial margin. A reduce that does not flip the position enters anyway. Liquidation runs when equity is under maintenance and hands the closed size to the pool. pool_cap does not apply to that handoff. Pool collateral is 0, so a liquidation that leaves the pool unable to carry the position becomes bad debt, and insurance pays that first.
Read back after creation.
Vault CZXGH62x18zkpbzthFPAdB5tS9dxeML1zwUp191AFGz3 stores the market pubkey and the lamports. Of its 50,853,440 lamports, 50,000,000 are the insurance balance recorded on the market and 853,440 are the vault's own rent. Traders will deposit into this same account. Their claim is the collateral field on their portfolio, not a separate token account.
Hedge ticket J9Af88NzfUJ6GazSDHwSBB7rBRgJnnazcPwTmRW7NZkL stores the market pubkey, the keeper pubkey, and bump 252. Sequence, pool size, mark, and recorded slot are 0 until the first record_hedge.
Escrow Eb4b35WtV64d6gjaEvpHHxfMwhry6NeCTtyyUtYstaXK is an empty system account. Devnet rent for a 0-byte account is 650,240 lamports. The top-up added 200,000 lamports on top of that, so the account holds 850,240. record_hedge accepts this PDA, at index 255, as its only signer. The index is fixed by ActionArgs in the Magic program and by the Dark Percolator check. A top-up at index 0 would not satisfy it.
Both are finalized,
error null.
| Step | Signature | Slot |
|---|---|---|
init_market and fund (insurance, not pool) | 577FwMsf5hHqazQqrVrScbuzruVSk3cx4GkZcXnzRLkiaoTfd6i2HWvPGh8Uo4ksmz6qqrLPeatzg5u3CepDp33K | 521249243 |
| Escrow top-up, index 255, 200,000 lamports | 5p3fgDtEc4YrGV29RDJ4J94EopEBRF1az6KV86M55ogk1hCbRUCUUWLLkkJm5Kdh6ToytPfdtbbaZTPxBQruUEe2 | 521249245 |
The payer EJXjqJ51JCncsNN5mM8DXjw34d85fm5zgSwoQckghhq5 went from 1.662602132 SOL to 1.607474692 SOL. The 55,127,440 lamports break down as:
| Use | Lamports |
|---|---|
| Market rent | 2,230,120 |
| Vault rent | 853,440 |
| Insurance, locked | 50,000,000 |
| Hedge-ticket rent | 1,183,640 |
| Escrow rent | 650,240 |
| Escrow top-up | 200,000 |
| Two base fees | 10,000 |
| Total | 55,127,440 |
Base layer, then the rollup.
Base layer, public
open_portfolio creates the trader's portfolio and pre-funds the rent its permission account will need.
deposit moves SOL into the vault and adds the same amount to that portfolio's collateral.
delegate_portfolio and delegate_market hand those accounts to MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo.
Rollup, inside the TEE
The RPC is https://devnet-tee.magicblock.app with a MagicBlock session token from verifyTeeRpcIntegrity and getAuthToken. The token is a signed challenge, not a dashboard key.
init_permission, then set_privacy(true, auditors). Members are the trader, the keeper, and up to two auditors. The keeper is a member of every portfolio so it can cross and liquidate.
push_mark(price_e6, fresh) from the oracle key.
submit_intent stores one intent and the SHA-256 of its v2 encoding. The portfolio must already be private.
cross_batch fills at the mark, up to 16 portfolios.
liquidate closes a portfolio that is under maintenance.
commit_market commits the public market and schedules record_hedge. The keeper escrow pays that action from the 200,000 lamport top-up.
Back on the base layer, withdraw pays collateral out only when the portfolio is undelegated, flat, and has no open intent.
Limits that are part of this market's shape: one open intent per portfolio, at most 16 portfolios in a batch, at most two auditors, and a batch that cannot take inventory. The mark is whatever the oracle key pushes inside the envelope. Deposits and the market account are public. The private part is the delegated portfolio.
Program flow and the build are in dark-percolator.md.
