# Dark Percolator

Percolator-style perps whose per-trader state runs inside a MagicBlock
Private Ephemeral Rollup (PER) on Intel TDX. Market aggregates stay public;
positions, collateral and intents are readable only by the trader, the market
keeper and up to two auditors the trader names.

**Status:** deployed on **devnet only** (cluster `https://api.devnet.solana.com`)
on 2026-11-11 as program id `Cf8dRz9xhvdwU825LBu6SGHa3gcHMiJwVh8LciJK1hPz`. Not audited, not on mainnet. The deployed
ELF is `target/deploy/dark_percolator.so` (gitignored), built with
`cargo-build-sbf` 4.4.0 / platform-tools v1.57, sha256
`fd973918ee9dbd57ab428d5bd1d4b9c6a76f6add191a69df28cf9fae1fa3627a`. `declare_id!`, the IDL `address` and `program-abi.ts` all carry that
id. The upgrade authority is the deploy fee payer; the program is not
`--final`.

## Layout

| Path | What it is |
|---|---|
| `crates/dark-engine` | `no_std` risk core: hybrid fee, EWMA mark, mark envelope, positions, margin, liquidation, sealed batch cross, intent v2 commitment. |
| `programs/dark-percolator` | Anchor 1.2.2 program on `ephemeral-rollups-sdk` 0.17.3. |
| `idl/dark_percolator.json` | IDL generated from the program (real discriminators). |
| `src/lib/perps/aggregator/dark/engine.ts` | Bit-for-bit TypeScript mirror of `dark-engine`. |
| `src/lib/perps/aggregator/dark/program.ts` | Unsigned instruction builders, PDAs, account decoders. |
| `src/lib/perps/aggregator/dark/program-abi.ts` | Generated from the IDL by `scripts/gen-dark-abi.mjs`. |
| `fixtures/dark/accounts.json` | Account bytes written by the Rust tests and decoded by the TS tests. |

Cross-language checks: the intent commitment vector, PDA, escrow and
permission addresses, Anchor discriminators and account layouts are all
asserted on both sides.

## How a trade moves

1. **Base:** `init_market` creates the market, a lamport vault and a hedge
   ticket. `fund` seeds insurance or pool collateral.
2. **Base:** the trader calls `open_portfolio` (pre-funds the permission rent)
   and `deposit`, then `delegate_portfolio` to the market's TEE validator.
   The authority calls `delegate_market` to the same validator.
3. **Rollup:** `init_permission`, then `set_privacy(true, auditors)`. Members:
   trader (logs, message, balances), keeper (logs, balances), auditors
   (balances).
4. **Rollup:** the oracle calls `push_mark(price, fresh)`. Fresh means all three
   Pyth legs are inside 600 s. When stale, the EWMA drifts toward the
   indicative price, becomes the mark, and the fee charges for the drift.
5. **Rollup:** the trader calls `submit_intent`. It is refused unless the
   portfolio's permission account is private. The program stores the intent
   and its SHA-256 commitment.
6. **Rollup:** the keeper calls `cross_batch` with up to 16 portfolios. Every
   eligible intent fills at the mark. Buys cross sells; the pool absorbs the
   imbalance up to `pool_cap`; the heavy side fills pro rata. An intent
   enters only if a full fill keeps initial margin (reduce-only always
   enters). Unfilled remainders stay open for the next batch.
7. **Rollup:** `liquidate` closes an account under maintenance at the mark; the
   pool takes the position. Fees go to insurance, and insurance absorbs bad
   debt first.
8. **Rollup → base:** `commit_market` commits the market and schedules
   `record_hedge` as a post-commit action. `record_hedge` accepts only the
   keeper's escrow PDA as signer and writes the pool inventory to the hedge
   ticket. An operator reads it (`hedgeOrderFor`) and places the offsetting
   Phoenix order from their own wallet.
9. **Rollup → base:** `undelegate_portfolio` closes the permission and
   commits and undelegates. Back on base, `withdraw` pays out when the
   portfolio is flat with no open intent.

## Build

The ELF in Status was built with the placeholder `declare_id!` left in place, so the TypeScript client still matches. Repeat the build, including a new keypair, only when you are ready to deploy:

```bash
# Agave CLI and Anchor 1.2.2 (via avm), then:
solana-keygen new -o target/deploy/dark_percolator-keypair.json   # keep out of git
anchor keys sync          # replaces the placeholder declare_id!
cargo build-sbf --manifest-path programs/dark-percolator/Cargo.toml

# Host tests (no Solana toolchain needed)
cargo test -p dark-engine -p dark-percolator
npm test && npm run typecheck
```

After any program change, regenerate the IDL and the TS ABI:

```bash
ANCHOR_IDL_BUILD_PROGRAM_PATH=$PWD/programs/dark-percolator \
  cargo test -p dark-percolator --features idl-build __anchor_private_print_idl -- --show-output --quiet > /tmp/idl.txt
# assemble the program, address and errors blocks into idl/dark_percolator.json, then:
node scripts/gen-dark-abi.mjs
UPDATE_FIXTURES=1 cargo test -p dark-percolator layout_fixture   # only if account layouts changed
```

## Devnet runbook (needs an owner go before any step that spends SOL)

- Endpoints: base `https://api.devnet.solana.com`; PER
  `https://devnet-tee.magicblock.app?token=<authToken>` from
  `verifyTeeRpcIntegrity` + `getAuthToken` in the MagicBlock SDK.
- TEE validator identity (devnet and mainnet):
  `MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo`. Pass it as `tee_validator`.
- Timing parameters (`max_mark_age_slots`, `ewma_halflife_slots`) count
  **rollup** slots, which are much shorter than base-layer slots. Measure the
  rollup slot time on devnet before you choose values.
- The keeper needs a funded escrow (ephemeral balance, index 255) to pay for
  the post-commit action. Fund it per MagicBlock's Magic Actions docs before
  the first `commit_market`.

## Known gaps

- The mark is pushed by a trusted oracle key inside the rollup, bounded by the
  per-slot envelope. It is not a Pyth read, so freshness depends on that key.
- Pool collateral has no LP shares; the authority funds it directly.
- Withdrawals require a flat position. Insurance and pool lamports have no withdraw path yet.
- One keeper key runs batches, liquidations and commits. It is also the only
  non-trader that can read positions, so a stalled keeper stalls the market.
- The compute cost of a 16-portfolio `cross_batch` is unmeasured.
- The permission flow uses the portfolio PDA as payer, as in MagicBlock's own
  example, but it has not been exercised against a live PER.
- Privacy rests on Intel TDX and on MagicBlock operating the validator. PER
  ingress applies MagicBlock's geofencing and AML screening.
