> ## Documentation Index
> Fetch the complete documentation index at: https://docs.darkpool.fun/llms.txt
> Use this file to discover all available pages before exploring further.

# Invariants & Conventions

> The rules the project enforces and assumes.

Project-wide invariants. Break these at your peril.

## On-chain

1. **Attribution.** `predict_facade::attribute_*` is wired in two places: the resolver's settlement PTB (`buildAtomicSettlementPTB` to `attribute_redeem`, used by both the HTTP endpoint and the keeper) and `scripts/supply-plp.ts` (`attribute_plp_supply`). Frontend and agent-service **mints are unattributed**, and no indexer stream consumes facade events. Wiring those is a republish-era follow-up.
2. **Predict pin.** Predict is pinned to `predict-testnet-4-16`. Never reference `main`. The branch will shift before mainnet.
3. **DUSDC** is the only quote asset Predict accepts (DeepBook-allowlisted): `0xe95040085976bfd54a1a07225cd46c8a2b4e8e2b6732f140a0fc49850ba73e1a::dusdc::DUSDC`.
4. **Binary market = one Move publish.** Slug must match `/^[a-z0-9_]+$/`. Registry on disk (`.binary-markets/registry.json`) is authoritative.
5. **`agent::pause` / `resume` emit no events** (verified at `agent.move:215`); server `paused` column is stuck at the registration default. Authoritative source for an agent's active state is `sui.getObject(TradeAgent).is_active`. Frontend `OwnerControls` and the agent-service tick guard both read it live. Do not trust the API field.
6. **Vault settlement sweep must pull idle vault balance.** Receipt redemption pays from `final_payout_micro` only, so idle DUSDC left in `vault.balance` after the keeper's final tick would be stranded forever. Encoded in `scripts/keeper-tick.ts`.
7. **Predict strike grid is `$1`.** `$63,601` quotes, `$63,600.50` aborts. Baselines snap to the nearest dollar (at most 50 cents from the true open). Probe-based strike pickers walk \$50 rungs.
8. **No DEEP needed for users.** All V3 swap PTBs use `pay_with_deep=false` (fees in the input token at the penalty multiplier). Quote estimates come from `get_quantity_out_input_fee` so the math matches.
9. **5% fee headroom** on every V3 quoting leg (keeper + seeder). `place_limit_order` with `pay_with_deep=false` locks qty + input-token fee at placement, so quoting 100% of holdings aborts `EBalanceManagerBalanceTooLow`. One shared convention.
10. **Auto-settle keeper redeems one position per PTB** (not batched). Memoises tried tuples: success = never re-fire, failure = 30 min backoff. Always self-pays (Enoki allow-list rejects Predict targets).

## Off-chain

11. **`.env` lives at repo root.** Gitignored. `.env.example` is the schema. `loadEnv()` throws with a clean message on missing required vars.
12. **Sponsored gas, three strategies.** Frontend `useTxSigner()` cascades:
    1. Self-sponsor via `POST /v1/sponsor`.
    2. Enoki sponsorAndExecute (zk only, on `SponsorUnavailableError`).
    3. Direct execution (user pays gas).
13. **Realized P\&L computed at read time.** `/v1/users/:address/redemptions` computes `payout - qty × avg_fill_cost` per `(oracle, strike, side)`. Binary rows return `null` basis (V3 fills aren't indexed). Profile chart end equals Realized P\&L tile.
14. **`useAppAccount()` is the only account hook UI should use.** dApp Kit's `useCurrentAccount()` returns `null` for zkLogin users; `useAppAccount` unifies wallet + zk.
15. **One resolver process.** A `tsx watch` reload can leave the old `setInterval` alive while a new one starts, causing duplicate-digest log lines and validator sequence-number conflicts. Always Ctrl-C and restart, never let two run.
16. **Indexer needs a restart per new binary market.** `binary::<slug>` event streams are created at startup from `.binary-markets/registry.json`. New scaffold needs an indexer restart before its stream exists. `/v1/binary-markets` is unaffected (reads disk + chain).

## Frontend

17. **Hide settled/expired markets from `/markets`.** Filtered in `allMarkets` memo. Reachable from `/positions` / `/profile`.
18. **Dedupe parallel oracles by (asset, expiry).** `lastUpdateMs` 1h-bucket primary, `fillCount` tiebreak. Twin stays reachable by direct URL.
19. **Trading is blocked on expired markets.** `BuyPanel` short-circuits + shows a banner. `RecentFills` disables its rows.
20. **Losing-side holdings of resolved markets are hidden** from `/positions`. The coins are worthless and unredeemable; showing them with a misleading "redeem" CTA was clutter.
