Skip to main content
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 (buildAtomicSettlementPTBattribute_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 (≤50¢ 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

  1. .env lives at repo root. Gitignored. .env.example is the schema. loadEnv() throws with a clean message on missing required vars.
  2. 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).
  3. 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 == Realized P&L tile.
  4. useAppAccount() is the only account hook UI should use. dApp Kit’s useCurrentAccount() returns null for zkLogin users; useAppAccount unifies wallet + zk.
  5. 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 ⌃C and restart, never let two run.
  6. Indexer needs a restart per new binary market. binary::<slug> event streams are created at startup from .binary-markets/registry.json. New scaffold → indexer restart before its stream exists. /v1/binary-markets is unaffected (reads disk + chain).

Frontend

  1. Hide settled/expired markets from /markets. Filtered in allMarkets memo. Reachable from /positions / /profile.
  2. Dedupe parallel oracles by (asset, expiry). lastUpdateMs 1h-bucket primary, fillCount tiebreak. Twin stays reachable by direct URL.
  3. Trading is blocked on expired markets. BuyPanel short-circuits + shows a banner. RecentFills disables its rows.
  4. 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.