diff --git a/.qoder/docs/onix-protocol-paper-ru.md b/.qoder/docs/onix-protocol-paper-ru.md
index 3fd104a67e..0d5bfd6df8 100644
--- a/.qoder/docs/onix-protocol-paper-ru.md
+++ b/.qoder/docs/onix-protocol-paper-ru.md
@@ -137,7 +137,7 @@ $$W = S_{\text{lose}} - f_{\text{oracle}} - f_{\text{creator}} - f_{\text{liq}}$
$$q_0 \xrightarrow{\text{оракул принимает}} q_1 \xrightarrow{t \geq t_{\text{bet}}} q_2 \xrightarrow{\text{оракул разрешает}} q_3 \xrightarrow{\text{льготный период}} \text{выплачено}$$
-с путём отклонения $q_0 \xrightarrow{\text{оракул отклоняет}} q_{-1}$ (удалён, ликвидность возвращена).
+Из $q_0$ ведут два пути в терминальное удалённое состояние $q_{-1}$, оба возвращают начальную ликвидность создателю (антиспам-комиссия за создание, уже уплаченная в фонд DAO при создании, *не* возвращается): явное отклонение $q_0 \xrightarrow{\text{оракул отклоняет}} q_{-1}$ и *таймаут* $q_0 \xrightarrow{t \geq t_0 + \tau_{\text{accept}}} q_{-1}$, срабатывающий поблочным кроном, когда оракул не принимает и не отклоняет рынок в течение заданного управлением окна акцепта $\tau_{\text{accept}}$ (по умолчанию один час). Таймаут ограничивает, как долго неотвечающий оракул может удерживать начальный капитал создателя.
---
@@ -304,6 +304,8 @@ $$a_{\text{alloc}} = B_{\text{free}} \cdot \alpha / 100$$
при условиях $a_{\text{alloc}} \geq a_{\min}$ и $B_{\text{alloc}} + a_{\text{alloc}} \leq B_{\text{total}} \cdot \alpha_{\max} / 100$, где $\alpha$ — процент размещения на рынок, а $\alpha_{\max}$ — потолок суммарного размещения.
+Размещение дополнительно ограничено **порогом вознаграждения**: пул предоставляет капитал рынку, только если его LP-комиссия $f_{\text{liq}}$ достигает управляемого минимума $f_{\text{liq}}^{\min}$ (по умолчанию $2\%$). Рынок, предлагающий LP меньше этого, не окупает альтернативные издержки пула, поэтому пул размещает ноль, и единственная глубина рынка — собственный сид создателя. Это делает LP-комиссию явной ценой, которую рынок платит за автоматическую пуловую ликвидность, и не даёт пулу субсидировать рынки, настроенные направлять всю комиссионную выручку мимо поставщиков ликвидности.
+
Формула размещения включает поправки на качество оракула:
$$a_{\text{alloc}} = B_{\text{free}} \cdot \frac{\alpha}{100} \cdot (1 - \beta)^{n_o} \cdot (1 - \gamma)^{f_o}$$
@@ -397,7 +399,7 @@ $$\Omega = \lambda m \,(1 + r),$$
Эти две комиссии входят в систему в разных точках, и их нельзя смешивать. **Процентная комиссия** $f_{\text{oracle}}$ удерживается из $S_{\text{lose}}$ при разрешении и фигурирует в денежном потоке Теоремы 1 (§5.2). **Фиксированная комиссия** $f_{\text{fixed}}$ — это прямой перевод создатель→оракул, выполняемый при *принятии* рынка, до каких-либо ставок; поэтому она *не* является частью денежного потока ставок/LP — она не добавляется к начальной ликвидности $L$ и не черпается из $S_{\text{lose}}$, а значит не появляется в Теореме 1. (Она опущена в векторе комиссий $\boldsymbol{\theta}$ из §3.3 по той же причине: $\boldsymbol{\theta}$ собирает только процентные комиссии на момент разрешения, финансируемые проигравшими.) Для рынков с самооракулом перевод не происходит и $f_{\text{fixed}} = 0$.
-Процесс принятия оракулом реализует механизм «оферта–котировка»: создатель публикует потолки комиссий, а оракул фиксирует свои фактические условия (ограниченные как потолком создателя, так и потолком управления) при принятии.
+Процесс принятия оракулом реализует механизм «оферта–котировка»: создатель публикует потолки комиссий, а оракул фиксирует свои фактические условия (ограниченные как потолком создателя, так и потолком управления) при принятии. Принятие ограничено во времени окном $\tau_{\text{accept}}$ (§3.4): рынок, оставшийся в ожидании дольше этого срока, авто-аннулируется поблочным кроном, который возвращает начальную ликвидность создателя, удерживая антиспам-комиссию за создание, — так неотвечающий оракул не может бесконечно замораживать сид.
### 7.2 Двухрежимная система споров
diff --git a/.qoder/docs/onix-protocol-paper-ru.pdf b/.qoder/docs/onix-protocol-paper-ru.pdf
index 95a7b4a9ae..f0300b9d70 100644
Binary files a/.qoder/docs/onix-protocol-paper-ru.pdf and b/.qoder/docs/onix-protocol-paper-ru.pdf differ
diff --git a/.qoder/docs/onix-protocol-paper.md b/.qoder/docs/onix-protocol-paper.md
index 55cdb2f0ef..aba1f723b4 100644
--- a/.qoder/docs/onix-protocol-paper.md
+++ b/.qoder/docs/onix-protocol-paper.md
@@ -136,7 +136,7 @@ The lifecycle of $\mathcal{M}$ follows a finite state machine:
$$q_0 \xrightarrow{\text{oracle accepts}} q_1 \xrightarrow{t \geq t_{\text{bet}}} q_2 \xrightarrow{\text{oracle resolves}} q_3 \xrightarrow{\text{grace period}} \text{paid}$$
-with a rejection path $q_0 \xrightarrow{\text{oracle rejects}} q_{-1}$ (deleted, liquidity returned).
+Two paths lead out of $q_0$ back to the terminal deleted state $q_{-1}$, both returning the seed liquidity to the creator (the anti-spam creation fee, already paid to the DAO fund at creation, is *not* refunded): an explicit rejection $q_0 \xrightarrow{\text{oracle rejects}} q_{-1}$, and a *timeout* $q_0 \xrightarrow{t \geq t_0 + \tau_{\text{accept}}} q_{-1}$ triggered by the per-block cron when the oracle neither accepts nor rejects within the governance-defined acceptance window $\tau_{\text{accept}}$ (default one hour). The timeout bounds how long an unresponsive oracle can hold a creator's seed capital hostage.
---
@@ -302,6 +302,8 @@ $$a_{\text{alloc}} = B_{\text{free}} \cdot \alpha / 100$$
subject to $a_{\text{alloc}} \geq a_{\min}$ and $B_{\text{alloc}} + a_{\text{alloc}} \leq B_{\text{total}} \cdot \alpha_{\max} / 100$, where $\alpha$ is the per-market allocation percentage and $\alpha_{\max}$ is the maximum total allocation cap.
+Allocation is further gated by a **reward floor**: the pool provides capital to a market only if its LP fee $f_{\text{liq}}$ meets a governance minimum $f_{\text{liq}}^{\min}$ (default $2\%$). A market that offers LPs less than this earns nothing worth the pool's opportunity cost, so the pool allocates zero and the market's only depth is the creator's own seed. This makes the LP fee an explicit price the market pays for automated pool liquidity, and prevents the pool from subsidizing markets configured to route all fee revenue away from liquidity providers.
+
The allocation formula includes oracle quality adjustments:
$$a_{\text{alloc}} = B_{\text{free}} \cdot \frac{\alpha}{100} \cdot (1 - \beta)^{n_o} \cdot (1 - \gamma)^{f_o}$$
@@ -395,7 +397,7 @@ Each oracle $o$ must maintain an insurance bond $I_o \geq I_{\min}$. The bond cr
These two fees enter the system at different points and must not be conflated. The **percentage fee** $f_{\text{oracle}}$ is deducted from $S_{\text{lose}}$ at resolution and appears in the money flow of Theorem 1 (§5.2). The **fixed fee** $f_{\text{fixed}}$ is a direct creator→oracle transfer executed at market *acceptance*, before any betting; it is therefore *not* part of the betting/LP money flow — it neither adds to the seed liquidity $L$ nor draws from $S_{\text{lose}}$, and so does not appear in Theorem 1. (It is omitted from the fee vector $\boldsymbol{\theta}$ of §3.3 for the same reason: $\boldsymbol{\theta}$ collects only the resolution-time, losers-funded percentage fees.) For self-oracle markets no transfer occurs and $f_{\text{fixed}} = 0$.
-The oracle acceptance flow implements an offer-quote mechanism: the creator publishes fee ceilings, and the oracle freezes its actual terms (bounded by both the creator ceiling and a governance cap) at acceptance.
+The oracle acceptance flow implements an offer-quote mechanism: the creator publishes fee ceilings, and the oracle freezes its actual terms (bounded by both the creator ceiling and a governance cap) at acceptance. Acceptance is time-bounded by the window $\tau_{\text{accept}}$ (§3.4): a market left pending beyond it is auto-expired by the per-block cron, which refunds the creator's seed liquidity while retaining the anti-spam creation fee — so an unresponsive oracle cannot indefinitely freeze the seed.
### 7.2 Two-Mode Dispute System
diff --git a/.qoder/docs/onix-protocol-paper.pdf b/.qoder/docs/onix-protocol-paper.pdf
index e146fd777f..ad743784e5 100644
Binary files a/.qoder/docs/onix-protocol-paper.pdf and b/.qoder/docs/onix-protocol-paper.pdf differ
diff --git a/.qoder/docs/pm-workflow/README.md b/.qoder/docs/pm-workflow/README.md
new file mode 100644
index 0000000000..225253b6e6
--- /dev/null
+++ b/.qoder/docs/pm-workflow/README.md
@@ -0,0 +1,177 @@
+# Prediction Market — Workflow by Role (HF14 Onix)
+
+One **canonical scenario** traced through every participant. Each role has its own folder with an
+interaction diagram, the **signed operations** it sends, the **virtual operations** that touch it,
+and a **tokens sent / received** table for two outcomes:
+
+- **Normal resolve** — oracle resolves, grace passes, `pm_auto_payout` settles. No dispute.
+- **Disputed resolve** — oracle resolves **A**, a dispute **overturns to B**, then settlement runs.
+
+All amounts are abstract **VIZ** units (ignore the 3 decimals). All percents are **bp** (10000 = 100.00%).
+Settlement is strictly **zero-sum** — no tokens are ever minted; `current_supply` is untouched:
+
+```
+Σ winner_payout + oracle_take + creator_take + lp_bonus + LP_principal
+ == Σ all bet amounts + LP_principal + forfeit_pool (+ insurance slash, in a dispute)
+```
+
+---
+
+## Canonical market **M** (binary CPMM, A vs B)
+
+| Item | Value |
+|------|-------|
+| Engine | binary CPMM (`x·y=k`), `weight = tokens_out` |
+| Seed liquidity (marketmaker) | **2000** → reserves A=1000 / B=1000 |
+| `oracle_fee_percent` (oracle quote) | **1000** (10%) |
+| `creator_fee_percent` | **500** (5%) |
+| `liquidity_fee_percent` | **500** (5%) |
+| `oracle_fixed_fee` (oracle quote) | **10** |
+| `dispute_penalty_percent` | **+10000** (slash up to 100% of insurance ×consensus) |
+
+Illustrative chain props: `pm_market_creation_fee` 5, `pm_oracle_registration_fee` 10,
+`pm_min_oracle_insurance` 5000, `pm_dispute_fee` 1000, `pm_dispute_reward_multiplier` 30000 (**3×**),
+`pm_no_contest_penalty_percent` 5000 (50% of dispute fee), `pm_oracle_penalty_percent` 500 (5% of
+insurance on a missed deadline), `pm_lazy_emergency_penalty_percent` 5000 (50% of profit),
+`pm_leverage_pool_profit_percent` **R = 10%**, `pm_lazy_alloc_percent` 2000 (20%).
+
+### Roster
+
+| Actor | Role | Stake / action |
+|-------|------|----------------|
+| **maker** | creator + first LP | seeds 2000 liquidity |
+| **orac** | external oracle | insurance 5000; quotes fee 10% + fixed 10 |
+| **LP1** | in-market liquidity provider | adds 1000 liquidity |
+| **A** | bettor — winner | 100 on **A**, early; weight 100 |
+| **C** | bettor — late winner | 100 on **A** at T+85%; weight 100; time-penalty **50%** |
+| **B** | bettor — loser | 200 on **B**; weight 200 |
+| **D** | leverage **×10** winner | collateral 10 + loan 90 (market **L**) |
+| **E** | leverage **×5** liquidated | collateral 20 + loan 80 (market **L**) |
+| **LZ1** | lazy-pool depositor | deposits 1000 |
+| **disp** | disputer | escrows dispute fee 1000 |
+
+> Curve weights (100 / 100 / 200) are written explicitly to keep the parimutuel arithmetic readable;
+> a real CPMM hands out slightly less weight as reserves shift.
+
+---
+
+## Master ledger — NORMAL resolve (A wins)
+
+`losers_sum = 200` (B). Fees off the losers' pool:
+`oracle_fee = 200×10% = 20`, `creator_fee = 200×5% = 10`, `liq_fee = 200×5% = 10`, `oracle_fixed = 10`.
+`winners_pool = 200 − 20 − 10 − 10 − 10 = 150`. `Σ winning weight = 200` (A 100 + C 100).
+
+- **A**: profit `150×100/200 = 75`, penalty 0 → **payout 175**.
+- **C**: profit 75, time-penalty `75×50% = 37` (→ LP) → **payout 138**.
+- **LP bonus** = `liq_fee 10 + penalties 37 = 47`, split by time in market: **maker ~31 / LP1 ~16**.
+- **oracle_take** = `oracle_fee 20 + fixed 10 = 30`. **creator_take** = `creator_fee 10`.
+
+| Actor | sends | receives | net (this market) |
+|-------|-------|----------|-------------------|
+| maker | 2000 liquidity + 5 creation-fee | 2000 principal + 10 creator-fee + 31 LP-bonus | **+36** |
+| orac | (10 reg-fee, 5000 insurance locked) | 30 oracle-take | **+30** |
+| LP1 | 1000 liquidity | 1000 principal + 16 LP-bonus | **+16** |
+| A | 100 | 175 | **+75** |
+| C | 100 | 138 | **+38** |
+| B | 200 | 0 | **−200** |
+
+**Zero-sum:** in `= bets 400 + LP principal 3000 = 3400`; out `= 175+138+0 + 30 + 10 + 47 + 3000 = 3400`. ✔
+The 5 creation-fee + 10 reg-fee leave to the **DAO fund** (not part of the market pool).
+
+---
+
+## Master ledger — DISPUTED resolve (oracle said A → overturned to B)
+
+`disp` escrows `dispute_fee 1000`. The verdict overturns to **B**; the oracle is slashed.
+With `dispute_penalty_percent = 10000` and consensus strength **100%**: `slash = 5000×100%×100% = 5000`.
+Reward carve-out: `reward_target = fee×3 = 3000` → `bonus = 3000 − 1000 = 2000` (≤ slash). Disputer gets
+`fee 1000 + bonus 2000 = 3000`. Remainder `slash − bonus = 3000 → forfeit_pool`.
+
+Now **B wins**. `losers_sum = 200` (A 100 + C 100). Fees 20/10/10 + fixed 10.
+`winners_pool = 200 − 50 + forfeit 3000 = 3150`. `Σ winning weight = 200` (B).
+- **B**: profit `3150×200/200 = 3150` → **payout 3350**.
+- **oracle_take** still `30` (the *market* fee is paid from the frozen config even when overturned — the
+ punishment is the **insurance slash**, separate money). **creator_take** 10. **LP bonus** = liq 10.
+
+| Actor | sends | receives | net (this market) |
+|-------|-------|----------|-------------------|
+| maker | 2000 + 5 | 2000 principal + 10 creator-fee + ~6 LP-bonus | **+11** |
+| orac | insurance −**5000** slashed | 30 oracle-take | **−4970** |
+| LP1 | 1000 | 1000 principal + ~4 LP-bonus | **+4** |
+| A | 100 | 0 | **−100** |
+| C | 100 | 0 | **−100** |
+| B | 200 | 3350 | **+3150** |
+| disp | 1000 dispute-fee | 3000 (fee back + 2000 reward) | **+2000** |
+
+**Zero-sum:** in `= bets 400 + LP principal 3000 + dispute_fee 1000 + insurance slash 5000 = 9400`;
+out `= B 3350 + oracle 30 + creator 10 + lp_bonus 10 + LP principal 3000 + disputer 3000 = 9400`. ✔
+The slash 5000 splits into disputer bonus 2000 + forfeit 3000 (→ B via the winners' pool).
+
+---
+
+## Implementation status (verified against the code)
+
+**Regular operations — all 21 present** in the `operation` variant (`operations.hpp`), validated +
+evaluated in `pm_evaluator.cpp`:
+`pm_oracle_register`, `pm_oracle_update`, `pm_create_market`, `pm_oracle_accept_market`, `pm_place_bet`,
+`pm_commit_bet`, `pm_reveal_bet`, `pm_cancel_bet`, `pm_add_liquidity`, `pm_withdraw_liquidity`,
+`pm_resolve_market`, `pm_no_contest`, `pm_dispute_create`, `pm_dispute_vote`, `pm_dispute_resolve`,
+`pm_transfer_position`, `pm_lazy_deposit`, `pm_lazy_withdraw`, `pm_leverage_open`, `pm_leverage_close`,
+`pm_leverage_convert`. ✔
+
+**Virtual operations** — emitted by `database::process_pm_markets()` / the evaluators:
+
+| Virtual op | Fires? | Trigger (code) |
+|------------|--------|----------------|
+| `pm_market_accepted` | ✔ | on `pm_oracle_accept_market` (accept) **and** self-oracle `pm_create_market` |
+| `pm_payout` | ✔ | **per active bet** at settle — carries `account`, `market_id`, `bet_id`, `side`/`outcome_index`, `amount` (stake), `payout` (**0 on a loss**) |
+| `pm_auto_payout` | ✔ | **once per market** at settle — a summary marker (`bets_sum`) alongside the per-bet `pm_payout`s |
+| `pm_commit_forfeit` | ✔ | unrevealed commit past `reveal_deadline` |
+| `pm_dispute_finalize` | ✔ | committee `voting_end_time` |
+| `pm_dispute_auto_close` | ✔ | `auto_close_time` (anti-freeze) |
+| `pm_oracle_missed_penalty` | ✔ | oracle missed `result_expiration` |
+| `pm_lazy_recall` | ✔ | idle-allocation graduated recall step |
+| `pm_batch_settle` | ✔ | epoch boundary |
+| `pm_leverage_liquidate` | ✔ | mid-market liquidation: reason **0** opposing-bet, **1** cancel-bet (`cascade_liquidate`) |
+| `pm_leverage_resolve` | ✔ | **settlement** of a leveraged position (reason=expiration in `force_close_positions`): carries `market_id`, `outcome_index`, `won` (solvent ⇒ positive), `pool_received`/`bettor_received`, and `leverage` (= `total_bet/collateral`) |
+
+> A leveraged position is force-closed at its `cancel_value` at settlement: the pool takes
+> `min(cv, obligation)`, the bettor gets the rest. **`pm_leverage_resolve`** marks that close (positive
+> if `cv ≥ obligation`, else collateral lost); **`pm_leverage_liquidate`** is only for the *mid-market*
+> opposing-bet / cancel-bet cascades. See [bettor D](bettor-d-leverage-winner/bettor-d-leverage-winner.md).
+
+**API plugin `prediction_market_api.*`** — **21** read methods (`prediction_market_api.hpp`):
+`get_market`, `list_markets`, `list_markets_by_oracle`, `list_markets_by_creator`, `get_market_outcomes`,
+`get_market_weight_sums`, `get_market_bets`, `get_account_positions`, `get_market_liquidity`,
+**`get_account_leverage_positions`**, **`get_market_leverage_positions`**, **`get_creator_ban`**,
+`get_oracle`, `list_oracles`, `get_dispute`, `get_dispute_votes`, `get_lazy_pool`, `get_lazy_deposit`,
+`get_pm_chain_properties`, `get_market_meta`, `list_markets_by_category`.
+
+> Per-bettor results (`pm_payout`) and leverage settlements (`pm_leverage_resolve`) appear in
+> `account_history`; leverage **positions** are now also queryable via `get_account_leverage_positions` /
+> `get_market_leverage_positions`, and creator bans via `get_creator_ban`.
+
+---
+
+## Index
+
+| Folder | Role |
+|--------|------|
+| [marketmaker/](marketmaker/marketmaker.md) | Market maker (creator + first LP) |
+| [oracle/](oracle/oracle.md) | Oracle — register → accept (quote) → resolve |
+| [oracle-banned/](oracle-banned/oracle-banned.md) | Banned oracle |
+| [oracle-dispute-winner/](oracle-dispute-winner/oracle-dispute-winner.md) | Oracle upheld in a dispute |
+| [oracle-dispute-loser/](oracle-dispute-loser/oracle-dispute-loser.md) | Oracle overturned + slashed |
+| [bettor-a-winner/](bettor-a-winner/bettor-a-winner.md) | Bettor A — early winner |
+| [bettor-b-loser/](bettor-b-loser/bettor-b-loser.md) | Bettor B — loser |
+| [bettor-c-late-winner/](bettor-c-late-winner/bettor-c-late-winner.md) | Bettor C — late winner (time penalty) |
+| [bettor-d-leverage-winner/](bettor-d-leverage-winner/bettor-d-leverage-winner.md) | Bettor D — leverage ×10 winner |
+| [bettor-e-leverage-liquidated/](bettor-e-leverage-liquidated/bettor-e-leverage-liquidated.md) | Bettor E — leverage ×5 liquidated |
+| [lp-market/](lp-market/lp-market.md) | Liquidity provider in the market |
+| [lazy-pool/](lazy-pool/lazy-pool.md) | The lazy liquidity pool itself |
+| [lp-lazy-pool/](lp-lazy-pool/lp-lazy-pool.md) | Liquidity provider in the lazy pool |
+| [disputer-winner/](disputer-winner/disputer-winner.md) | Disputer — vindicated |
+| [disputer-loser/](disputer-loser/disputer-loser.md) | Disputer — fee forfeited |
+| [resolver-committee/](resolver-committee/resolver-committee.md) | Committee resolver (stake-weighted) |
+| [resolver-account/](resolver-account/resolver-account.md) | Account-mode resolver (centralized) |
+| [dispute-auto-close/](dispute-auto-close/dispute-auto-close.md) | Dispute forced to end (anti-freeze) |
diff --git a/.qoder/docs/pm-workflow/bettor-a-winner/bettor-a-winner.md b/.qoder/docs/pm-workflow/bettor-a-winner/bettor-a-winner.md
new file mode 100644
index 0000000000..af04133550
--- /dev/null
+++ b/.qoder/docs/pm-workflow/bettor-a-winner/bettor-a-winner.md
@@ -0,0 +1,52 @@
+# Bettor A — early winner
+
+Part of [PM Workflow](../README.md). **A** stakes **100 on side A early** (no time penalty) and wins
+when the market resolves to A. Payout = stake back + a weight-proportional share of the winners' pool.
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ A -->|pm_place_bet side=A 100| BET[(pm_bet_object weight 100)]
+ BET --> M[(market M reserves shift)]
+ M ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|payout 175| A
+```
+
+## Operations it sends (signed)
+- `pm_place_bet` — `side=0 (A)`, `amount=100`, `mode=0` (instant). Debits 100; mints `weight` from the CPMM.
+- (optional) `pm_transfer_position` to reassign part of the weight; `pm_cancel_bet` if the market allows it.
+
+## Virtual operations that touch it
+- `pm_auto_payout` — credits the final payout once the dispute grace elapses with no open dispute.
+
+## Tokens — normal resolve (A wins)
+| sends | receives | net |
+|-------|----------|-----|
+| 100 | **175** | **+75** |
+
+`profit = winners_pool 150 × weight 100 / Σweight 200 = 75`; no time penalty → `payout = 100 + 75`.
+
+## Tokens — disputed resolve (overturned to B)
+| sends | receives | net |
+|-------|----------|-----|
+| 100 | **0** | **−100** |
+
+The overturn makes A the **losing** side; A's stake funds the new winners' pool. (A could itself file
+the dispute if it believed A was correct — see [disputer-winner](../disputer-winner/disputer-winner.md) / [disputer-loser](../disputer-loser/disputer-loser.md).)
+
+## Notes
+- Winnings come **only** from losers' stakes (+ forfeit pool), never from emission.
+- If **A had bet late**, its profit would be docked by the time penalty — that's [bettor C](../bettor-c-late-winner/bettor-c-late-winner.md).
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| `pm_place_bet` (instant, CPMM weight, `allow_instant_bet` gate) | ✔ | `pm_place_bet_evaluator` |
+| `pm_transfer_position` / `pm_cancel_bet` (optional) | ✔ | their evaluators |
+| winner credit at settle | ✔ | `settle_market` pays `amount + profit − penalty` via `adjust_balance` |
+| vop `pm_payout` (per bet) | ✔ | carries `amount` (100), `side` (A), `payout` (175) |
+| vop `pm_auto_payout` | ✔ | one per market (summary marker) |
+
+**Observe via plugin:** `get_account_positions` (your bets + `expected_payout` / `expected_payout_approx`),
+`get_market_bets`, `get_market_weight_sums` (denominator for your share); the realized `pm_payout` is in `account_history`.
diff --git a/.qoder/docs/pm-workflow/bettor-b-loser/bettor-b-loser.md b/.qoder/docs/pm-workflow/bettor-b-loser/bettor-b-loser.md
new file mode 100644
index 0000000000..ea4fb7ddaa
--- /dev/null
+++ b/.qoder/docs/pm-workflow/bettor-b-loser/bettor-b-loser.md
@@ -0,0 +1,50 @@
+# Bettor B — loser
+
+Part of [PM Workflow](../README.md). **B** stakes **200 on side B**. When the market resolves to **A**,
+B's stake funds the winners and B receives **nothing**. (In the disputed path B becomes the winner.)
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ B -->|pm_place_bet side=B 200| BET[(pm_bet_object status active)]
+ BET --> M[(market M)]
+ M ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|status=resolved, payout 0| BET
+```
+
+## Operations it sends (signed)
+- `pm_place_bet` — `side=1 (B)`, `amount=200`, `mode=0`. Debits 200.
+
+## Virtual operations that touch it
+- `pm_auto_payout` — marks the losing bet `status=resolved`, `resolved_amount=0` (no credit).
+
+## Tokens — normal resolve (A wins)
+| sends | receives | net |
+|-------|----------|-----|
+| 200 | **0** | **−200** |
+
+B's 200 **is** the `losers_sum`: it pays the fees (40) and the winners' pool (150 → A 75 + C 75) plus
+LP bonus. Loser gets nothing back — this is the parimutuel "losers fund winners" rule.
+
+## Tokens — disputed resolve (overturned to B)
+| sends | receives | net |
+|-------|----------|-----|
+| 200 | **3350** | **+3150** |
+
+The overturn makes **B the winning side**; now A + C are the losers (200) and the oracle's slashed
+insurance (forfeit 3000) is injected into B's winners' pool. `payout = 200 + 3150`.
+
+## Notes
+- A loser who believes the oracle is wrong can dispute. If vindicated it becomes this very B-wins
+ ledger; if not, it also loses the dispute fee — see [disputer-loser](../disputer-loser/disputer-loser.md).
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| `pm_place_bet` | ✔ | `pm_place_bet_evaluator` |
+| losing bet → `status=resolved`, `resolved_amount=0` (no credit) | ✔ | `settle_market` (loser branch) |
+| vop `pm_payout` (per bet, **payout=0**) | ✔ | losers also get a record — `amount` (200), `side` (B), `payout` 0 |
+
+**Observe via plugin:** `get_account_positions` (your bet shows `expected_payout=0` once resolved),
+`get_market_bets` (status flips to resolved), `account_history` (`pm_payout` with payout 0). Dispute path via `get_dispute`.
diff --git a/.qoder/docs/pm-workflow/bettor-c-late-winner/bettor-c-late-winner.md b/.qoder/docs/pm-workflow/bettor-c-late-winner/bettor-c-late-winner.md
new file mode 100644
index 0000000000..c8cd75cd6c
--- /dev/null
+++ b/.qoder/docs/pm-workflow/bettor-c-late-winner/bettor-c-late-winner.md
@@ -0,0 +1,55 @@
+# Bettor C — late winner (time penalty)
+
+Part of [PM Workflow](../README.md). **C** stakes **100 on side A** but **late** — at T+85% of the
+betting window — so a **time penalty** is applied to its *profit only* (not its principal). C still
+wins when A resolves, but earns less than the equally-weighted early [bettor A](../bettor-a-winner/bettor-a-winner.md);
+the docked amount flows to the LPs.
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ C -->|pm_place_bet side=A 100 at T+85%| BET[(pm_bet_object weight 100 time_penalty 50%)]
+ BET --> M[(market M)]
+ M ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|payout 138| C
+ VP -. penalty 37 .-> LPb[LP bonus]
+```
+
+## Operations it sends (signed)
+- `pm_place_bet` — `side=0 (A)`, `amount=100`, `mode=0`. The node stamps `time_penalty` from the
+ market's penalty curve at placement time (1e6-scaled). Here ≈ **50%**.
+
+## Virtual operations that touch it
+- `pm_auto_payout` — credits `amount + profit − penalty`; the penalty is added to the LP bonus.
+
+## Tokens — normal resolve (A wins)
+| sends | receives | net |
+|-------|----------|-----|
+| 100 | **138** | **+38** |
+
+`profit = 150 × 100/200 = 75`; `penalty = profit 75 × 50% = 37` (→ LPs); `payout = 100 + 75 − 37 = 138`.
+Same weight as A, but **−37** vs A's +75 because of the late entry.
+
+## Tokens — disputed resolve (overturned to B)
+| sends | receives | net |
+|-------|----------|-----|
+| 100 | **0** | **−100** |
+
+Overturned to B → C is a loser; the time penalty is moot (penalties apply only to a winner's profit).
+
+## Notes
+- The penalty discourages last-second sniping of near-certain outcomes; it subsidises liquidity, not the protocol.
+- Penalty curve + scale are market config (`time_penalty_type/value`, `penalty_curve_type`).
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| `pm_place_bet` stamps `time_penalty` at placement | ✔ | `pm_place_bet_evaluator` (curve eval) |
+| penalty applied to **profit only**, docked amount → LP bonus | ✔ | `compute_settlement` / `settle_market` |
+| winner credit `amount + profit − penalty` | ✔ | `settle_market` `adjust_balance` |
+| vop `pm_payout` (per bet) | ✔ | `amount` 100, `side` A, `payout` 138 (penalty already netted) |
+| vop `pm_auto_payout` | ✔ | one per market (summary) |
+
+**Observe via plugin:** `get_account_positions` (your bet's `time_penalty` + reduced `expected_payout`),
+`get_market_bets` (the stored `time_penalty`); the realized `pm_payout` in `account_history`.
diff --git a/.qoder/docs/pm-workflow/bettor-d-leverage-winner/bettor-d-leverage-winner.md b/.qoder/docs/pm-workflow/bettor-d-leverage-winner/bettor-d-leverage-winner.md
new file mode 100644
index 0000000000..efb68661b4
--- /dev/null
+++ b/.qoder/docs/pm-workflow/bettor-d-leverage-winner/bettor-d-leverage-winner.md
@@ -0,0 +1,71 @@
+# Bettor D — leverage ×10 winner
+
+Part of [PM Workflow](../README.md). **D** opens a **×10** boosted position: **10 collateral + 90 loan**
+from the [lazy pool](../lazy-pool/lazy-pool.md) = **100** staked on side A. Shown in an isolated
+leverage market **L** (binary, `pm_leverage_enabled=true`, `R = 10%`) so the loan/interest math stays
+clean. When A wins, D keeps the upside on the full 100 after repaying the pool its loan + interest.
+
+Key figures: `pool_profit = loan 90 × R 10% = 9`; `liquidation_threshold (obligation) = 90 × 1.10 = 99`.
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ D -->|pm_leverage_open collateral 10 + loan 90| POS[(pm_leverage_position total_bet 100, obligation 99)]
+ POOL[(lazy pool)] -.loan 90.-> POS
+ POS --> L[(market L, side A)]
+ L ==>|settle: force_close at cancel_value| VR[[pm_leverage_resolve won=true, leverage=10]]
+ VR -->|min(cv,obligation) 99| POOL
+ VR -->|cv 200 − 99 = 101| D
+```
+
+## Operations it sends (signed)
+- `pm_leverage_open` — `collateral 10`, `loan 90`, `outcome_index 0 (A)`, `min_tokens`, `max_slippage_percent`.
+ Pool: `free_balance −= 90`, `leverage_fund_used += 90`.
+- (optional) `pm_leverage_close` — voluntary, only if `cancel_value ≥ obligation`; or
+ `pm_leverage_convert` — pay off the loan + a profit-share fee to turn it into a plain bet.
+
+## Virtual operations that touch it
+- `pm_leverage_resolve` at settlement — `force_close_positions` unwinds the position at its
+ `cancel_value`; the pool takes `min(cv, obligation) = 99` (loan 90 + interest 9), the bettor gets
+ `cv − pool_received`. The vop carries `won` (solvent ⇒ true), `outcome_index`, `pool_received`,
+ `bettor_received`, and **`leverage`** (= `total_bet / collateral` = 10).
+- `pm_leverage_liquidate` — only if the position is liquidated **mid-market** (opposing bet / cancel
+ cascade) before settlement — the losing path of [bettor E](../bettor-e-leverage-liquidated/bettor-e-leverage-liquidated.md).
+
+## Tokens — normal resolve (A wins)
+| sends | receives | net |
+|-------|----------|-----|
+| collateral **10** | cancel_value 200 − obligation 99 = **101** | **+91** |
+
+A profitable position closes at its `cancel_value` (here 200 — the curve value of its A-tokens after the
+market moved its way). The pool reclaims its `obligation 99` (loan 90 + **9 interest**); D keeps the rest
+on just 10 of its own → **+91**. Pool net: **+9**. (Not a parimutuel share — leverage settles by
+liquidation, never via `pm_auto_payout`.)
+
+## Tokens — disputed resolve (overturned to B)
+| sends | receives | net |
+|-------|----------|-----|
+| collateral 10 | 0 | **−10** |
+
+The overturn flips A→B, so the winning position becomes a **loss**: it is settled lost, the pool is
+repaid from D's collateral first, and D forfeits its 10. (Leverage magnifies both directions; the
+`expiration_buffer` normally force-closes positions before a contested resolution can flip them.)
+
+## Notes
+- Zero-sum (market L): in `= D 10 + pool 90 + counterparty 100 = 200`; out `= D 101 + pool 99 = 200`. ✔
+- The pool only ever fronts `pm_leverage_fund_percent` of its `free_balance`, capped per position.
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| `pm_leverage_open` (collateral + pool loan, obligation, kill-switch) | ✔ | `pm_leverage_open_evaluator` |
+| `pm_leverage_close` (voluntary, `cv ≥ obligation`) | ✔ | `pm_leverage_close_evaluator` |
+| `pm_leverage_convert` (pay loan + fee → normal bet) | ✔ | `pm_leverage_convert_evaluator` |
+| settlement = force-close at `cancel_value` | ✔ | `force_close_positions` → `liquidate_position(reason=2)` |
+| vop `pm_leverage_resolve` (settlement) | ✔ | carries `market_id`, `outcome_index` (A), `won=true`, `pool_received` 99, `bettor_received` 101, `leverage` 10 |
+| vop `pm_leverage_liquidate` | — | only for *mid-market* opposing/cancel cascades, not this settlement |
+
+**Observe via plugin:** **`get_account_leverage_positions(D, …)`** / **`get_market_leverage_positions(L, …)`**
+(your `pm_leverage_position`: `collateral`, `loan`, `liquidation_threshold`, `status`, `bettor_received`);
+the realized close is the `pm_leverage_resolve` vop in `account_history`; pool via `get_lazy_pool`.
diff --git a/.qoder/docs/pm-workflow/bettor-e-leverage-liquidated/bettor-e-leverage-liquidated.md b/.qoder/docs/pm-workflow/bettor-e-leverage-liquidated/bettor-e-leverage-liquidated.md
new file mode 100644
index 0000000000..23564ef0b3
--- /dev/null
+++ b/.qoder/docs/pm-workflow/bettor-e-leverage-liquidated/bettor-e-leverage-liquidated.md
@@ -0,0 +1,87 @@
+# Bettor E — leverage ×5 liquidated
+
+Part of [PM Workflow](../README.md). **E** opens a **×5** position: **20 collateral + 80 loan** = **100**
+on side B (market **L**, `R = 10%`). Before resolution an **opposing bet** (someone backs A) moves the
+curve against B; E's `cancel_value` reaches its liquidation threshold and the **cascade liquidation**
+force-closes it. **E loses its collateral, but the pool is always made whole** — it recovers the loan
+plus its profit. By design, an opposing-bet liquidation can **never** go negative for the pool
+(strategy doc §6 "cancel_value < threshold — mathematically impossible during the betting period").
+
+Key figures: `obligation = liquidation_threshold = 80 × 1.10 = 88`. The opposing bet is bounded by the
+slippage cap, and the position was opened with a safety margin (Constraint 2), so at liquidation
+`cancel_value ≥ loan` always — here it triggers right at `cancel_value = 88`.
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ E -->|pm_leverage_open collateral 20 + loan 80| POS[(pm_leverage_position obligation 88)]
+ POOL[(lazy pool)] -.loan 80.-> POS
+ X -->|pm_place_bet side=A| L[(market L)]
+ L ==>|cascade at PRE-bet reserves cv 88 ≤ threshold| VL[[pm_leverage_liquidate reason=opposing_bet]]
+ VL -->|pool_received 88 = loan 80 + profit 8| POOL
+ VL -->|bettor_received 0| E
+```
+
+## Operations it sends (signed)
+- `pm_leverage_open` — `collateral 20`, `loan 80`, `outcome_index 1 (B)`. Pool: `free −= 80`, `leverage_fund_used += 80`.
+- E sends nothing further — liquidation is **involuntary**, triggered by another account's bet.
+
+## Virtual operations that touch it
+- `pm_leverage_liquidate` (`reason = opposing_bet`) — force-closes the position at the current reserves:
+ `pool_received = min(cancel_value, obligation) = 88`, `bettor_received = cancel_value − pool_received = 0`.
+ (A position that instead survives to settlement is closed by `pm_leverage_resolve` — [bettor D](../bettor-d-leverage-winner/bettor-d-leverage-winner.md).)
+
+## Tokens — outcome is the same in both resolve paths
+E is liquidated **before** resolution, so the final A/B result (disputed or not) doesn't change E:
+
+| actor | sends | receives | net |
+|-------|-------|----------|-----|
+| **E** | collateral **20** | **0** | **−20** |
+| **pool** | loan 80 | **88** (loan 80 + R% profit 8) | **+8** |
+
+The pool recovers its full obligation (`min(cancel_value, obligation)`); since `cancel_value ≥ loan` for
+opposing-bet liquidations, the pool **never** takes a loss — it gets the loan back plus its profit. E
+loses its collateral (the liquidated position pays the bettor `cancel_value − obligation = 0`).
+
+## Edge case — the ONLY path that can go negative: cancel-bet (Case B)
+There is exactly one bounded exception where the pool can take a shortfall — a **`pm_cancel_bet` on the
+same side** as a leveraged position (strategy doc §5 Case B). A cancel reverses a *prior, large* same-side
+bet, which can move the curve by more than the per-bet slippage cap allows. For **fairness to the
+cancel-bettor**, the cancel executes FIRST (at the price they submitted), then the cascade liquidates —
+so `cancel_value` can land **below the loan**:
+
+```
+shortfall = obligation − cancel_value (if cancel_value < obligation)
+pool_received = cancel_value (e.g. 70 < loan 80)
+bettor_received = 0
+lazy_pool.free_balance −= shortfall (bad debt — bounded by SL%, rare)
+```
+
+This bad debt is **bounded** (`shortfall ≤ cancel_value_before × SL%`) and **rare** (the pool's R% on
+every other position more than offsets it). It is the *only* negative-liquidation path; it is covered
+on-chain by the `leverage_cancel_bet_cascade_bad_debt` test. Opposing-bet liquidations (this page) and
+settlement force-close ([bettor D](../bettor-d-leverage-winner/bettor-d-leverage-winner.md)) are always
+full-recovery.
+
+## Notes
+- Pool protection is structural: `max_per_position`, `max_position_ratio`, `safety_margin`, the slippage
+ cap (`max_slippage_percent`), and the `expiration_buffer`. `pm_leverage_enabled=false` blocks **new**
+ opens, but the liquidation cascade is **not** gated by the flag — if delegates toggle leverage off
+ while positions are open, those positions are still liquidated/settled normally, so governance can
+ never strip the pool's protection mid-flight. Covered by `leverage_disabled_keeps_liquidation_protection`.
+- A *solvent* outcome (`cancel_value > obligation`) instead pays E `cancel_value − obligation` and the
+ pool its interest — the winning side of the same mechanic shown in [bettor D](../bettor-d-leverage-winner/bettor-d-leverage-winner.md).
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| `pm_leverage_open` | ✔ | `pm_leverage_open_evaluator` |
+| opposing-bet liquidation at **pre-bet** reserves (`cv ≥ loan` ⇒ pool whole) | ✔ | `pm_place_bet` → `cascade_liquidate(reason=0)` before the bet applies |
+| `pool_received = min(cv, obligation)`, `bettor_received = cv − pool_received` | ✔ | `liquidate_position` |
+| bad debt **only** for cancel-bet (Case B) | ✔ | `pm_cancel_bet` → `cascade_liquidate(reason=1)` after the cancel |
+| vop `pm_leverage_liquidate` (`reason` 0 opposing / 1 cancel) | ✔ | `liquidate_position` |
+
+**Observe via plugin:** **`get_account_leverage_positions(E, …)`** / **`get_market_leverage_positions(L, …)`**
+(position `status=1` liquidated, `cancel_value_at_liquidation`, `pool_received`, `bettor_received`); the
+event is the `pm_leverage_liquidate` vop in `account_history`; pool impact via `get_lazy_pool`.
diff --git a/.qoder/docs/pm-workflow/dispute-auto-close/dispute-auto-close.md b/.qoder/docs/pm-workflow/dispute-auto-close/dispute-auto-close.md
new file mode 100644
index 0000000000..7847a95ef5
--- /dev/null
+++ b/.qoder/docs/pm-workflow/dispute-auto-close/dispute-auto-close.md
@@ -0,0 +1,56 @@
+# Dispute forced to end (anti-freeze auto-close)
+
+Part of [PM Workflow](../README.md). A dispute that is **never decided** — the oracle stays silent and
+(committee mode) no quorum forms — cannot freeze the market forever. At `auto_close_time` the
+`pm_dispute_auto_close` cron force-ends it: **everyone is refunded**, the disputer's fee is **returned**,
+and the unresponsive oracle is penalised. No winner is picked.
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ disp -->|pm_dispute_create escrow fee 1000| D[(dispute, status open)]
+ D -. oracle silent / no quorum .-> WAIT[auto_close_time reached]
+ WAIT ==>|VIRTUAL| AC[[pm_dispute_auto_close]]
+ AC -->|refund all bets| bettors
+ AC -->|fee 1000 back| disp
+ AC -->|insurance slash → DAO| orac
+```
+
+## Operations
+- None at close — it is entirely cron-driven. The only signed op was the original `pm_dispute_create`.
+
+## Virtual operations
+- `pm_dispute_auto_close` — full refund of every active bet, LP principal returned, **disputer fee
+ refunded**, oracle insurance slashed → DAO (`disputes_auto_closed++`, `dispute_responses_missed++`).
+
+## Tokens — forced end (the only path here)
+| Actor | sends | receives | net |
+|-------|-------|----------|-----|
+| A | 100 | 100 refund | **0** |
+| B | 200 | 200 refund | **0** |
+| C | 100 | 100 refund | **0** |
+| maker / LP1 | liquidity | principal back | **0** (no bonus) |
+| disp | dispute_fee 1000 | **1000 back** | **0** |
+| orac | insurance −slash → DAO | — | **− slash** |
+
+This is **not** "the disputer lost": a returned fee (net 0) is different from a forfeited fee
+([disputer-loser](../disputer-loser/disputer-loser.md), net −1000). Nobody profits; the market is voided
+to break the freeze, and the cost falls on the oracle that didn't respond.
+
+## Notes
+- The same void-and-refund shape also covers [oracle missed deadline](../oracle/oracle.md)
+ (`pm_oracle_missed_penalty`) and `pm_no_contest` — all zero-sum refunds with an oracle penalty.
+- Tune `pm_dispute_auto_close_sec` (14 d) vs `pm_dispute_vote_period_sec` (3 d) so honest disputes
+ resolve before the anti-freeze fires.
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| vop `pm_dispute_auto_close` at `auto_close_time` | ✔ | `process_pm_markets` auto-close scan |
+| full bet refund + LP principal + disputer fee returned | ✔ | `refund_all_bets` + `return_liquidity` + fee credit |
+| oracle penalty → DAO, `disputes_auto_closed++` / `dispute_responses_missed++` | ✔ | same |
+| sibling void paths | ✔ | `pm_oracle_missed_penalty`, `pm_no_contest` (same refund shape) |
+
+**Observe via plugin:** `get_dispute` (status → auto-closed), `get_market` (`status`/`payout_status`),
+`get_account_positions` (bets refunded), `get_oracle` (penalty counters).
diff --git a/.qoder/docs/pm-workflow/disputer-loser/disputer-loser.md b/.qoder/docs/pm-workflow/disputer-loser/disputer-loser.md
new file mode 100644
index 0000000000..1c603f9fe0
--- /dev/null
+++ b/.qoder/docs/pm-workflow/disputer-loser/disputer-loser.md
@@ -0,0 +1,52 @@
+# Disputer — fee forfeited (oracle upheld)
+
+Part of [PM Workflow](../README.md). **disp** disputes the oracle's **A**, escrows `pm_dispute_fee 1000`,
+but the verdict **upholds the oracle**. The escrowed fee is **forfeited to the oracle** as compensation,
+and the market settles exactly as originally resolved (A wins).
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ disp -->|pm_dispute_create proposed=B escrow fee 1000| D[(pm_dispute_object)]
+ D --> VOTE{committee vote or account resolve}
+ VOTE ==>|uphold oracle A| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|dispute_fee 1000| orac[oracle compensation]
+ FIN -->|market settles as A| AUTO[[pm_auto_payout]]
+```
+
+## Operations it sends (signed)
+- `pm_dispute_create` — `proposed_outcome = B`, escrows 1000, freezes payout.
+
+## Virtual operations that touch it
+- `pm_dispute_finalize` / `pm_dispute_resolve` — on **uphold**, transfers the dispute fee to the oracle
+ and unfreezes the original payout.
+- `pm_auto_payout` — settles the market on the original outcome A.
+
+## Tokens — disputed resolve, oracle upheld (the "loss")
+| sends | receives | net |
+|-------|----------|-----|
+| dispute_fee **1000** | **0** | **−1000** |
+
+The fee is the disputer's skin-in-the-game: a wrong/frivolous dispute pays the oracle for the trouble.
+Bettors A/C still win, B still loses — identical to the [normal ledger](../README.md#master-ledger--normal-resolve-a-wins).
+
+## Tokens — normal resolve (no dispute filed)
+N/A — no disputer exists without a filing.
+
+## Notes
+- This asymmetry (lose the fee if wrong, win a multiple if right) is what keeps the dispute channel honest.
+- If the dispute is never decided (oracle silent, no quorum), it is force-closed and the fee is
+ **returned** — see [dispute-auto-close](../dispute-auto-close/dispute-auto-close.md). That is different
+ from losing on the merits here.
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| `pm_dispute_create` | ✔ | `pm_dispute_create_evaluator` |
+| uphold → dispute fee transferred to oracle | ✔ | `pm_dispute_finalize` / `pm_dispute_resolve` (uphold branch) |
+| original payout unfrozen + settled (A) | ✔ | `settle_market`, summarised by `pm_auto_payout` |
+| vop on the disputer | — | none directly; the loss is the non-refunded escrow |
+
+**Observe via plugin:** `get_dispute` (status → oracle-right), `get_dispute_votes`, `get_oracle`
+(the oracle's balance gains the fee; `disputes_won++`).
diff --git a/.qoder/docs/pm-workflow/disputer-winner/disputer-winner.md b/.qoder/docs/pm-workflow/disputer-winner/disputer-winner.md
new file mode 100644
index 0000000000..800c95af34
--- /dev/null
+++ b/.qoder/docs/pm-workflow/disputer-winner/disputer-winner.md
@@ -0,0 +1,53 @@
+# Disputer — vindicated (oracle overturned)
+
+Part of [PM Workflow](../README.md). **disp** thinks the oracle's **A** is wrong, files a dispute
+proposing **B**, escrows `pm_dispute_fee 1000`, and the verdict **overturns to B**. disp gets its fee
+back **plus** a reward carved out of the oracle's slashed insurance.
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ disp -->|pm_dispute_create proposed=B escrow fee 1000| D[(pm_dispute_object status open)]
+ D --> VOTE{committee vote or account resolve}
+ VOTE ==>|overturn to B| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|fee 1000 + bonus 2000| disp
+ FIN -.slash 5000 from oracle.-> SPLIT[bonus 2000 → disp 3000 → forfeit_pool → winners]
+```
+
+## Operations it sends (signed)
+- `pm_dispute_create` — `proposed_outcome = B`, escrows `pm_dispute_fee 1000`. Freezes payout (`payout_status = 3`).
+- (committee mode) it does **not** vote on its own — the SHARES electorate does, via `pm_dispute_vote`.
+
+## Virtual operations that touch it
+- `pm_dispute_finalize` (committee) **or** `pm_dispute_resolve` (account) — on overturn, refunds the fee
+ and credits the reward carve-out.
+
+## Tokens — disputed resolve (overturned to B = the "win")
+| sends | receives | net |
+|-------|----------|-----|
+| dispute_fee **1000** | fee 1000 back + **bonus 2000** | **+2000** |
+
+`reward_target = fee × pm_dispute_reward_multiplier (3×) = 3000` → `bonus = 3000 − 1000 = 2000`, capped
+at the slash. The bonus comes from the oracle's **slashed insurance** (5000); the remainder (3000) goes
+to `forfeit_pool` → the new winners. disp risked 1000, walks away **+2000**.
+
+## Tokens — "normal" resolve (no dispute filed)
+N/A — without filing there is no disputer. If disp had **not** disputed, the original A result would
+stand and B-side money would be lost.
+
+## Notes
+- The reward is **capped at the actual slash** — a tiny slash means a small (or zero) bonus, only the fee back.
+- `dispute_penalty_percent < 0` on the market signals a *good-faith* oracle: no slash, the oracle keeps a
+ fee bonus, and the disputer just gets its fee back (no profit). See [resolver-committee](../resolver-committee/resolver-committee.md).
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| `pm_dispute_create` (escrow fee, freeze payout) | ✔ | `pm_dispute_create_evaluator` |
+| overturn → fee refund + reward carve-out (capped at slash) | ✔ | `pm_dispute_finalize` (committee) / `pm_dispute_resolve` (account) |
+| remainder of slash → `forfeit_pool` → winners | ✔ | same |
+| vop `pm_dispute_finalize` / settlement marker `pm_auto_payout` | ✔ | `process_pm_markets` |
+
+**Observe via plugin:** `get_dispute` (status → oracle-wrong), `get_dispute_votes` (committee tally),
+`get_account_positions` (your refunded fee + reward land as balance; the winning bet's new payout).
diff --git a/.qoder/docs/pm-workflow/lazy-pool/lazy-pool.md b/.qoder/docs/pm-workflow/lazy-pool/lazy-pool.md
new file mode 100644
index 0000000000..ca96a2db1d
--- /dev/null
+++ b/.qoder/docs/pm-workflow/lazy-pool/lazy-pool.md
@@ -0,0 +1,89 @@
+# The Lazy Liquidity Pool (system object)
+
+Part of [PM Workflow](../README.md). The lazy pool is a **singleton** `pm_lazy_pool_object` — not an
+account. Depositors fund it once; the pool **auto-allocates** a slice to each accepted market as a
+silent LP (`pm_liquidity_object` with empty `provider`), **funds leverage loans**, and **recalls**
+idle allocations. It earns LP yield + leverage interest, accounted MasterChef-style
+(`reward_per_share × 1e9 / total_shares`). Depositor view: [lp-lazy-pool](../lp-lazy-pool/lp-lazy-pool.md).
+
+## Fields
+`total_shares`, `free_balance`, `allocated_balance`, `earned_balance` (monotonic), `reward_per_share`,
+`leverage_fund_used` (cap on what `free_balance` may lend).
+
+## Interaction diagram
+
+```mermaid
+flowchart TD
+ LZ1 -->|pm_lazy_deposit 1000| POOL[(pm_lazy_pool free 1000 / shares 1000)]
+ POOL ==>|on market accept alloc 20% = 200| ALLOC[(pm_lazy_allocation + pm_liquidity provider=∅)]
+ ALLOC -->|market settles| YLD[route_pool_lp_return principal 200 + yield 20]
+ YLD --> POOL
+ POOL -->|leverage loan 90| Dpos[D position]
+ Dpos -->|close/resolve: 90 + interest 9| POOL
+ POOL -. idle market .-> VR[[pm_lazy_recall]]
+ VR -->|step back to free| POOL
+```
+
+## How money enters / leaves the pool
+| Event | Effect on pool |
+|-------|----------------|
+| `pm_lazy_deposit` (depositor) | `free_balance += amount`, mint shares |
+| market allocation (auto, on accept) | `free → allocated` (becomes a silent LP) |
+| market settles | `route_pool_lp_return`: principal + yield → `free_balance`; yield → `earned_balance` & `reward_per_share` |
+| leverage open (D/E) | `free_balance −= loan`, `leverage_fund_used += loan` |
+| leverage close / resolve / opposing-bet liquidation | `min(cancel_value, obligation) → free_balance`; `cancel_value ≥ loan` ⇒ **never a loss** (loan + R% profit → `earned_balance`) |
+| cancel-bet liquidation (Case B only) | recovers `cancel_value` which **may be < loan** → bounded **bad debt** `−= shortfall` from `free_balance` |
+| `pm_lazy_recall` (cron, idle market) | recalls one 10% step of an idle allocation back to `free_balance` |
+| `pm_lazy_withdraw` (depositor) | burn shares → principal + pending; emergency penalty stays in pool |
+
+## Tokens over the canonical scenario
+| Source | Δ earned / free |
+|--------|-----------------|
+| Market M allocation 200 → yield | **+20** earned |
+| Leverage D interest (`90 × 10%`, settlement) | **+9** earned |
+| Leverage E opposing-bet liquidation (recovers obligation 88 = loan 80 + R% 8) | **+8** earned |
+| **Net pool gain** | **+37** earned |
+
+> **The pool's leverage liquidations are never negative** for opposing bets or settlement force-close:
+> the liquidation runs at pre-bet reserves where `cancel_value ≥ loan`, so `pool_received = min(cv,
+> obligation)` returns at least the loan (strategy doc §6). The **only** path that can charge the pool a
+> bounded shortfall is a same-side **`pm_cancel_bet` (Case B)** — the cancel executes first for fairness,
+> then the cascade may liquidate below the loan (`free_balance −= shortfall`, bounded by `SL%`, rare).
+> See [bettor E](../bettor-e-leverage-liquidated/bettor-e-leverage-liquidated.md) and the
+> `leverage_cancel_bet_cascade_bad_debt` test.
+
+There is no "with/without dispute" branch for the pool itself: as a **market LP** its principal is
+returned unconditionally either way; only its *bonus* yield varies with the market's fee/penalty pool.
+
+## Virtual operations it emits
+- `pm_lazy_recall` — graduated recall of idle allocations (one 10% step per `(result−created)/10`).
+- (leverage) `pm_leverage_liquidate` / `pm_leverage_resolve` settle pool loans.
+
+## Notes
+- The pool serves **both** roles at once from a single `free_balance`: market-LP allocations
+ (`maybe_allocate_lazy`) **and** leverage loans (`leverage_fund_used` caps the latter). The leverage
+ knobs (`pm_leverage_fund_percent`, `…_max_per_position_bp`, `…_max_position_ratio_percent`,
+ `…_min_market_liquidity`, `pm_leverage_enabled`) are all checked **at `pm_leverage_open` time** against
+ the current median — so later property swings only affect *new* opens, never loans already out.
+- `pm_leverage_enabled` is a kill-switch (default **off**). It blocks new opens only; the liquidation
+ cascade that protects the pool from **existing** positions is deliberately **not** gated by it, so
+ flipping the flag off can never leave open loans unprotected.
+- VIZ in the lazy pool is **liquid**, not vested, so it carries **no** validator-scheduling or
+ committee-request weight (those use `effective_vesting_shares` only). **Exception (HF14):** for
+ **PM committee disputes**, a depositor's pool stake **is** counted — converted to vesting-shares via
+ `get_vesting_share_price()` and added to their `pm_dispute_vote` weight, so pooled DAO members aren't
+ disenfranchised. See [resolver-committee](../resolver-committee/resolver-committee.md).
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| singleton created at HF14 | ✔ | `apply_hardfork(CHAIN_HARDFORK_14)` |
+| auto-allocation on accept | ✔ | `maybe_allocate_lazy` |
+| LP yield routed back | ✔ | `route_pool_lp_return` (principal + yield → `free_balance`, `reward_per_share`) |
+| leverage loan / interest / bad debt | ✔ | `pm_leverage_open` / `liquidate_position` |
+| vop `pm_lazy_recall` | ✔ | graduated recall step |
+| vop `pm_leverage_liquidate` | ✔ | mid-market cascade (opposing/cancel) |
+| vop `pm_leverage_resolve` | ✔ | settles pool loans at market resolution (force-close) |
+
+**Observe via plugin:** `get_lazy_pool` (`free_balance`, `allocated_balance`, `earned_balance`,
+`reward_per_share`, `leverage_fund_used`). Per-market allocations have **no** dedicated API method.
diff --git a/.qoder/docs/pm-workflow/lp-lazy-pool/lp-lazy-pool.md b/.qoder/docs/pm-workflow/lp-lazy-pool/lp-lazy-pool.md
new file mode 100644
index 0000000000..d82c7dde46
--- /dev/null
+++ b/.qoder/docs/pm-workflow/lp-lazy-pool/lp-lazy-pool.md
@@ -0,0 +1,65 @@
+# Liquidity provider in the lazy pool (LZ1)
+
+Part of [PM Workflow](../README.md). **LZ1** deposits **1000** into the [lazy pool](../lazy-pool/lazy-pool.md)
+**once** and lets the pool spread it across markets + leverage loans. It earns a share of the pool's
+aggregate yield (MasterChef `reward_per_share`), not any single market's outcome. Two exit paths:
+**planned** (after the lock) and **emergency** (before the lock, with a penalty).
+
+## What ops make you a lazy-pool participant?
+- **`pm_lazy_deposit`** — `amount=1000`. Mints pool shares (first depositor 1:1 → 1000 shares), records
+ `principal`, `reward_snapshot`, and `unlock_time = now + pm_lazy_lock_sec` (7 d).
+- **`pm_lazy_withdraw`** — burns shares for principal + accrued rewards. `emergency=true` exits before
+ `unlock_time` with a penalty on the *profit*.
+
+That is the whole interface — there is **no per-market action**; allocation/recall/leverage are automatic.
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ LZ1 -->|pm_lazy_deposit 1000| DEP[(pm_lazy_deposit_object shares 1000, unlock=+7d)]
+ DEP --> POOL[(lazy pool)]
+ POOL -. yield accrues .-> RPS[reward_per_share ↑]
+ LZ1 -->|pm_lazy_withdraw| OUT{planned or emergency?}
+ OUT -->|planned, t≥unlock| P[principal 1000 + pending 29]
+ OUT -->|emergency, t|pm_add_liquidity 1000| L1[(pm_liquidity_object provider=LP1)]
+ L1 --> M[(market M reserves)]
+ M ==>|settle| SL[[settle_liquidity]]
+ SL -->|principal 1000 + bonus ~16| LP1
+ LP1 -->|pm_withdraw_liquidity after resolution| OUT[principal-safe exit]
+```
+
+## Operations it sends (signed)
+- `pm_add_liquidity` — `amount=1000`. Joins the binary CPMM reserves proportionally; records `deposit_time` (the time-weight basis).
+- `pm_withdraw_liquidity` — principal-safe; **locked** from `betting_expiration` until resolution.
+ `amount=0` withdraws the full position. Pays principal + any accrued `earned_fee`.
+
+## Virtual operations that touch it
+- `pm_auto_payout` / `settle_liquidity` — returns principal + the time-weighted LP-bonus share.
+
+## Tokens — normal resolve (A wins)
+| sends | receives | net |
+|-------|----------|-----|
+| 1000 | **1000 principal + ~16 bonus** | **+16** |
+
+LP bonus pool = `liq_fee 10 + time-penalties 37 = 47`, split by `principal × seconds-in-market` between
+the earlier maker (~31) and the later LP1 (~16). Earlier + larger ⇒ bigger slice.
+
+## Tokens — disputed resolve (overturned to B)
+| sends | receives | net |
+|-------|----------|-----|
+| 1000 | 1000 principal + ~4 bonus | **+4** |
+
+Principal still safe; the bonus is smaller (the B-wins pool generated no winner time-penalties, so the
+LP bonus is only `liq_fee 10`, split with the maker).
+
+## Notes
+- **Principal guarantee** is architectural: the seed/subsidy is returned before any winner is paid; an
+ LP can never lose principal to bettors — only forgo bonus.
+- Withdrawing during the betting window (before `betting_expiration`) is allowed; after it, the
+ position is locked until the market resolves.
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| `pm_add_liquidity` (records `deposit_time`) | ✔ | `pm_add_liquidity_evaluator` |
+| `pm_withdraw_liquidity` (principal-safe, lock gate) | ✔ | `pm_withdraw_liquidity_evaluator` |
+| principal returned + time-weighted bonus | ✔ | `settle_liquidity` → `distribute_lp` (principal × seconds) |
+| dedicated LP vop | — | none; settlement is summarised by the market's `pm_auto_payout` |
+
+**Observe via plugin:** `get_market_liquidity` (your `pm_liquidity_object`: `amount`, `earned_fee`,
+`status`), `get_market_weight_sums` (reserves / `q` for client pricing).
diff --git a/.qoder/docs/pm-workflow/marketmaker/marketmaker.md b/.qoder/docs/pm-workflow/marketmaker/marketmaker.md
new file mode 100644
index 0000000000..84952a8cc7
--- /dev/null
+++ b/.qoder/docs/pm-workflow/marketmaker/marketmaker.md
@@ -0,0 +1,62 @@
+# Market maker (creator + first LP)
+
+Part of [PM Workflow](../README.md) — uses canonical market **M**. The maker creates market M, seeds
+the **2000** liquidity (and so becomes the first `pm_liquidity_object`), and proposes the oracle's
+**offer ceiling**. It does **not** resolve (that's the oracle).
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ maker -->|pm_create_market| M[(pm_market_object status=0)]
+ maker -->|seed 2000| LP0[(pm_liquidity_object provider=maker)]
+ M -. fee 5 .-> DAO[(committee_fund)]
+ orac -->|pm_oracle_accept_market| M2[(M status=1)]
+ M2 -. VIRTUAL .-> VA[[pm_market_accepted]]
+ M2 ==>|pm_auto_payout| RET[principal 2000 + creator_fee + LP bonus]
+ RET --> maker
+```
+
+## Operations it sends (signed)
+- `pm_create_market` — sets `oracle_fee_percent` / `oracle_fixed_fee` as the **offer ceiling**,
+ plus its own `creator_fee_percent` (5%) and `liquidity_fee_percent` (5%). Pays `pm_market_creation_fee` 5 → DAO, locks `liquidity` 2000.
+- `pm_add_liquidity` / `pm_withdraw_liquidity` — optional; principal-safe, locked from
+ `betting_expiration` until resolution.
+
+## Virtual operations that touch it
+- `pm_market_accepted` — emitted when the oracle accepts (or, for a self-oracle, at creation).
+- `pm_auto_payout` — returns the maker's LP principal + its time-weighted LP-bonus share at settlement.
+
+## Tokens — normal resolve (A wins)
+| sends | receives | net |
+|-------|----------|-----|
+| 2000 liquidity + 5 creation-fee (→DAO) | 2000 principal + **creator_fee 10** + **LP-bonus ~31** | **+36** |
+
+LP principal is returned **unconditionally**; `creator_fee = losers_sum × 5% = 10`; LP-bonus =
+its time-weighted slice of (liq_fee + time-penalties + dust) = ~31 of 47.
+
+## Tokens — disputed resolve (overturned to B)
+| sends | receives | net |
+|-------|----------|-----|
+| 2000 + 5 | 2000 principal + creator_fee 10 + LP-bonus ~6 | **+11** |
+
+The creator fee is **still paid** from the frozen market config — the dispute punishes the **oracle**
+(insurance slash), not the maker. LP-bonus is smaller because the disputed pool produced fewer
+time-penalties. If the maker were also the oracle (self-oracle), see [oracle-dispute-loser](../oracle-dispute-loser/oracle-dispute-loser.md).
+
+## Notes
+- Self-oracle variant: `oracle == creator` → market is active at creation, `pm_market_accepted`
+ fires immediately with `self_oracle=true`, and the maker also earns the `oracle_take`.
+- A banned creator cannot open new markets — see [oracle-banned](../oracle-banned/oracle-banned.md) (same `pm_creator_ban_object` mechanism for creators).
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| `pm_create_market` (offer ceiling, fees bp) | ✔ | `pm_create_market_evaluator` |
+| `pm_add_liquidity` / `pm_withdraw_liquidity` | ✔ | their evaluators (principal-safe, lock gate) |
+| vop `pm_market_accepted` | ✔ | on oracle accept / self-oracle create |
+| vop `pm_auto_payout` | ✔ | per-market settle marker; LP principal+bonus via `settle_liquidity` (`adjust_balance`) |
+| creation fee → DAO | ✔ | `committee_fund += pm_market_creation_fee` |
+
+**Observe via plugin:** `get_market` (status, frozen fees), `list_markets_by_creator`,
+`get_market_liquidity` (your LP rows + `earned_fee`), `get_market_meta` (your JSON metadata).
diff --git a/.qoder/docs/pm-workflow/oracle-banned/oracle-banned.md b/.qoder/docs/pm-workflow/oracle-banned/oracle-banned.md
new file mode 100644
index 0000000000..63171fafe6
--- /dev/null
+++ b/.qoder/docs/pm-workflow/oracle-banned/oracle-banned.md
@@ -0,0 +1,55 @@
+# Banned oracle (and banned creator)
+
+Part of [PM Workflow](../README.md). A ban is a **status**, not a transfer: `pm_oracle_object.banned_until`
+(and, for creators, a `pm_creator_ban_object`) blocks the actor from taking on new markets until the
+timestamp passes. It usually rides along with an [overturn slash](../oracle-dispute-loser/oracle-dispute-loser.md),
+but the ban itself moves no tokens.
+
+## Who sets it
+- **Account mode:** `pm_dispute_resolve` with `ban_oracle` + `ban_oracle_until` (and/or
+ `ban_creator` + `ban_creator_until`).
+- **Committee mode:** `pm_dispute_finalize` applies a ban scaled by consensus on an overturn.
+- `banned_until = time_point_sec::maximum()` ⇒ a **permanent** ban.
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ resolver -->|pm_dispute_resolve ban_oracle| O[(pm_oracle_object banned_until = T)]
+ orac -->|pm_create_market / accept| CHK{now < banned_until?}
+ CHK -->|yes| REJ[REJECTED: 'Oracle is banned']
+ CHK -->|no, expired| OK[allowed again]
+ resolver -->|ban_creator| CB[(pm_creator_ban_object)]
+ maker -->|pm_create_market| CHK2{banned?}
+ CHK2 -->|yes| REJ2[REJECTED: 'Creator is banned']
+```
+
+## Operations affected
+- `pm_create_market` — rejected while the **creator** is banned (`pm_creator_ban_object.banned_until > now`).
+- `pm_create_market` (as oracle) / `pm_oracle_accept_market` — rejected while the **oracle** is banned.
+- Existing markets the oracle already serves continue; the ban only blocks **new** engagements.
+
+## Tokens
+| Event | tokens |
+|-------|--------|
+| the ban itself | **0** — pure status change |
+| the slash that usually accompanies it | see [oracle-dispute-loser](../oracle-dispute-loser/oracle-dispute-loser.md) (−5000 insurance) |
+| insurance still locked | refundable on `pm_oracle_update` withdraw **after** the ban lifts and no active markets remain |
+
+## Notes
+- Bans are stored as plain status fields and survive snapshots (`pm_oracle_object`, `pm_creator_ban_object`
+ are both serialized). A re-registration cannot wipe a ban — it is keyed by account.
+- Combine with reputation counters (`bans_received`, `disputes_lost`) that the API plugin folds into the
+ on-read reliability score.
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| oracle `banned_until` set | ✔ | `pm_dispute_resolve` (`ban_oracle`) / `pm_dispute_finalize` |
+| oracle ban enforced on create/accept | ✔ | `pm_create_market_evaluator` (`"Oracle is banned"`) |
+| creator ban via `pm_creator_ban_object` | ✔ | `pm_dispute_resolve` upsert; checked in `pm_create_market` (`"Creator is banned"`) |
+| both objects serialized in snapshot | ✔ | `plugins/snapshot` export/import |
+| no token movement from the ban itself | ✔ | status-only |
+
+**Observe via plugin:** `get_oracle` (`banned_until`, `bans_received`) for an oracle ban;
+**`get_creator_ban(account)`** (`banned_until`, `ban_count`) for a creator ban.
diff --git a/.qoder/docs/pm-workflow/oracle-dispute-loser/oracle-dispute-loser.md b/.qoder/docs/pm-workflow/oracle-dispute-loser/oracle-dispute-loser.md
new file mode 100644
index 0000000000..942c3c33af
--- /dev/null
+++ b/.qoder/docs/pm-workflow/oracle-dispute-loser/oracle-dispute-loser.md
@@ -0,0 +1,56 @@
+# Oracle — overturned + slashed (dispute loser)
+
+Part of [PM Workflow](../README.md). The oracle **orac** resolved **A**; the dispute **overturns to B**
+and the oracle's insurance is **slashed**. The oracle still collects the tiny frozen market fee (the
+fee and the punishment are separate money), but loses a large slice of its bond and reputation.
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ orac -->|pm_resolve_market A| M[(resolved A)]
+ disp -->|pm_dispute_create proposed=B| D[(dispute)]
+ D ==>|overturn to B| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|slash 5000| INS[oracle.insurance ↓]
+ INS --> SPLIT[bonus 2000 → disputer 3000 → forfeit_pool → B]
+ FIN --> AUTO[[pm_auto_payout settles B]]
+ AUTO -->|oracle_take 30| orac
+```
+
+## Operations it sends (signed)
+- `pm_resolve_market` (the disputed call). No further oracle action; the committee/resolver decides.
+
+## Virtual operations that touch it
+- `pm_dispute_finalize` / `pm_dispute_resolve` (overturn) — slashes insurance, sets `disputes_lost++`,
+ optionally sets `banned_until` (→ [oracle-banned](../oracle-banned/oracle-banned.md)).
+- `pm_auto_payout` — settles on the corrected outcome B.
+
+## Tokens — disputed resolve, overturned (the "loss")
+| sends | receives | net (market) |
+|-------|----------|--------------|
+| insurance −**5000 slashed** | oracle_take 30 | **−4970** |
+
+`slash = insurance 5000 × dispute_penalty_percent (100%) × consensus_strength (100%) = 5000`. It is
+**redistributed**, not burned: `bonus 2000 →` disputer, `3000 → forfeit_pool →` the new winners (B).
+The oracle keeping the 30 market fee is deliberate — punishment lives in the bond, not the fee.
+
+## Tokens — normal resolve (no dispute)
+| sends | receives | net (market) |
+|-------|----------|--------------|
+| insurance 5000 (locked) | oracle_take 30 | **+30** |
+
+## Notes
+- Slash scales with **consensus strength** (`winning_rshares / max_rshares`) — a split verdict slashes less.
+- `dispute_penalty_percent < 0` (good-faith) → **no** slash; the oracle even keeps a fee bonus.
+- A slashed oracle is often **banned** too (`ban_oracle` / `banned_until`), blocking new markets until expiry.
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| overturn → insurance slash `× pp × consensus_strength`, `disputes_lost++` | ✔ | `pm_dispute_finalize` / `pm_dispute_resolve` (overturn) |
+| slash split: disputer bonus + `forfeit_pool` | ✔ | same |
+| market fee still paid from frozen config | ✔ | `settle_market` uses `mkt.oracle_fee_percent` |
+| optional `banned_until` set | ✔ | `ban_oracle` (account) / scaled ban (committee) |
+
+**Observe via plugin:** `get_oracle` (`insurance` dropped, `disputes_lost++`, `banned_until`,
+`total_insurance_slashed`), `get_dispute` (status → oracle-wrong).
diff --git a/.qoder/docs/pm-workflow/oracle-dispute-winner/oracle-dispute-winner.md b/.qoder/docs/pm-workflow/oracle-dispute-winner/oracle-dispute-winner.md
new file mode 100644
index 0000000000..2a0abf5f99
--- /dev/null
+++ b/.qoder/docs/pm-workflow/oracle-dispute-winner/oracle-dispute-winner.md
@@ -0,0 +1,52 @@
+# Oracle — upheld in a dispute (dispute winner)
+
+Part of [PM Workflow](../README.md). The oracle **orac** resolved **A**; a disputer challenged it, but
+the verdict **upholds** A. The oracle keeps its normal market fee **and** collects the forfeited
+`dispute_fee` as compensation; its insurance is untouched and `disputes_won` increments.
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ orac -->|pm_resolve_market A| M[(market resolved A)]
+ disp -->|pm_dispute_create| D[(dispute)]
+ D ==>|uphold A| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|dispute_fee 1000| orac
+ FIN --> AUTO[[pm_auto_payout settles A]]
+ AUTO -->|oracle_take 30| orac
+```
+
+## Operations it sends (signed)
+- `pm_resolve_market` (already done). The oracle does not act again during the dispute (committee/account decides).
+
+## Virtual operations that touch it
+- `pm_dispute_finalize` / `pm_dispute_resolve` (uphold) — credits the `dispute_fee`, sets `disputes_won++`.
+- `pm_auto_payout` — credits the normal `oracle_take`.
+
+## Tokens — disputed resolve, upheld (the "win")
+| sends | receives | net (market) |
+|-------|----------|--------------|
+| insurance 5000 (locked, **not** slashed) | oracle_take 30 + **dispute_fee 1000** | **+1030** |
+
+The challenge backfires on the disputer and **pays the oracle**. Compare the slashed case in
+[oracle-dispute-loser](../oracle-dispute-loser/oracle-dispute-loser.md).
+
+## Tokens — normal resolve (no dispute)
+| sends | receives | net (market) |
+|-------|----------|--------------|
+| insurance 5000 (locked) | oracle_take 30 | **+30** |
+
+## Notes
+- Being upheld also improves the oracle's on-read reliability score (more `disputes_won`, no slash).
+- A *good-faith* market (`dispute_penalty_percent < 0`) can hand the oracle a fee bonus even when the
+ outcome is changed — recognising an honest mistake rather than punishing it.
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| uphold → `dispute_fee` credited to oracle, `disputes_won++` | ✔ | `pm_dispute_finalize` / `pm_dispute_resolve` (uphold) |
+| insurance untouched | ✔ | no slash on uphold |
+| settlement on original outcome | ✔ | `settle_market` + `pm_auto_payout` |
+
+**Observe via plugin:** `get_oracle` (`insurance` unchanged, `disputes_won`, higher reliability score),
+`get_dispute` (status → oracle-right).
diff --git a/.qoder/docs/pm-workflow/oracle/oracle.md b/.qoder/docs/pm-workflow/oracle/oracle.md
new file mode 100644
index 0000000000..77acfa61b0
--- /dev/null
+++ b/.qoder/docs/pm-workflow/oracle/oracle.md
@@ -0,0 +1,63 @@
+# Oracle (register → accept/quote → resolve)
+
+Part of [PM Workflow](../README.md) — external oracle **orac** on market **M**. The oracle bonds
+insurance, **quotes** its fee at accept (≤ the maker's offer and ≤ `pm_max_oracle_fee_percent`), and
+resolves the outcome. Its market fee is paid from the losers' pool at settlement; its bond is at risk
+only on a missed deadline or a lost dispute.
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ orac -->|pm_oracle_register insurance 5000| O[(pm_oracle_object)]
+ orac -. reg-fee 10 .-> DAO[(committee_fund)]
+ orac -->|pm_oracle_accept_market quote fee 10% + fixed 10| M[(M status=1)]
+ M -. VIRTUAL .-> VA[[pm_market_accepted]]
+ orac -->|pm_resolve_market A| M3[(M status=3)]
+ M3 ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|oracle_take 30| orac
+```
+
+## Operations it sends (signed)
+- `pm_oracle_register` — locks `insurance` 5000, pays `pm_oracle_registration_fee` 10 → DAO. Sets the
+ **advisory** standing `fee_percent` / `fixed_fee` (its public price list).
+- `pm_oracle_accept_market` — **quotes** `oracle_fee_percent` (10%) + `oracle_fixed_fee` (10), each
+ `≤` the creator's offer on the market and `≤ pm_max_oracle_fee_percent`. Freezes them onto M; market goes live.
+- `pm_resolve_market` — sets `winning_outcome = A`, opens the dispute grace window.
+- `pm_oracle_update` / `pm_no_contest` — optional (top-up/withdraw bond; void a market).
+
+## Virtual operations that touch it
+- `pm_market_accepted` — announces the launch + the oracle's frozen terms.
+- `pm_auto_payout` — credits the `oracle_take` (`oracle_fee + oracle_fixed_paid`) at settlement.
+- `pm_oracle_missed_penalty` — if the oracle never resolves, slashes `pm_oracle_penalty_percent` (5% = 250) of insurance → DAO and refunds all bets.
+
+## Tokens — normal resolve (A wins)
+| sends | receives | net (market) |
+|-------|----------|--------------|
+| insurance 5000 (locked, refundable) + reg-fee 10 (→DAO) | **oracle_take 30** = oracle_fee 20 + fixed 10 | **+30** |
+
+## Tokens — disputed resolve (overturned to B)
+| sends | receives | net (market) |
+|-------|----------|--------------|
+| insurance −**5000 slashed** | oracle_take 30 | **−4970** |
+
+Even when overturned the oracle keeps the small **market fee** (frozen config); the penalty is the
+**insurance slash** (`5000 × dispute_penalty_percent × consensus_strength`). The slash is split into the
+disputer's reward and the winners' `forfeit_pool`. See [oracle-dispute-loser](../oracle-dispute-loser/oracle-dispute-loser.md).
+
+## Notes
+- Quoting **below** the offer is allowed (price = reputation, not a bribe); quoting **above** is rejected.
+- A self-oracle (`oracle==creator`) auto-accepts at creation and additionally earns the `creator_take`.
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| `pm_oracle_register` (insurance lock, reg-fee→DAO, fee≤cap) | ✔ | `pm_oracle_register_evaluator` |
+| `pm_oracle_accept_market` quote (≤ offer, ≤ median cap) + freeze | ✔ | `pm_oracle_accept_market_evaluator` |
+| `pm_resolve_market`, `pm_no_contest`, `pm_oracle_update` | ✔ | their evaluators |
+| vop `pm_market_accepted` | ✔ | emitted on accept |
+| vop `pm_oracle_missed_penalty` | ✔ | `process_pm_markets` missed-deadline scan |
+| `oracle_take` credit at settle | ✔ | inside `settle_market` (`adjust_balance`), summarised by `pm_auto_payout` |
+
+**Observe via plugin:** `get_oracle` (insurance, the 14 counters + computed reliability score),
+`list_oracles` (sorted), `get_market` (frozen `oracle_fee_percent`/`oracle_fixed_fee`).
diff --git a/.qoder/docs/pm-workflow/prototype-frontend-design-document.md b/.qoder/docs/pm-workflow/prototype-frontend-design-document.md
new file mode 100644
index 0000000000..9107dbad86
--- /dev/null
+++ b/.qoder/docs/pm-workflow/prototype-frontend-design-document.md
@@ -0,0 +1,2583 @@
+# Forecaster / Onix — Frontend Design Document
+
+**Purpose of this document.** This is a complete, user-facing specification of the *current working prototype* of the Forecaster prediction market (protocol name **Onix**). It is written for the developers who will build a **new thin client on the VIZ blockchain** (using the `viz-js` library — see the [VIZ node docs](https://github.com/VIZ-Blockchain/viz-cpp-node/tree/master/docs)). Use it as a coverage checklist: every screen, capability, API call and transaction the current app performs is enumerated here so you can confirm the thin client reproduces all of them and maps each server-side API call to the correct on-chain operation / query.
+
+**How to read it.**
+- The prototype is a single-page app (`app.js`, cash/jQuery-style `$`) served by a thin PHP router. It talks to the backend exclusively through `POST`/`GET` calls to `/api//` returning JSON. Every one of those 58 endpoints is documented, with the exact request fields the client sends and the exact response fields the UI consumes.
+- Each functional area has its own section (2–9). Section 10 is the **master coverage matrix** — one row per endpoint — that node developers should tick off against their transaction map.
+- "VIZ thin-client note" call-outs are *suggestions* for how an action might map to a blockchain operation/query. They are hints for the node team, not a description of the current server internals (which are already ported).
+- Internal server mechanics (LMSR/parimutuel pricing, fixed-point math, DB schema, chain settlement) are **out of scope** — this document only covers the client contract and UX.
+
+> Terminology note: the codebase uses **Onix Binary** for two-outcome (Yes/No) markets and **Onix Multi** for categorical markets with 2–10 outcomes. Binary now settles parimutuel, same as Multi.
+
+---
+
+## Contents
+
+1. [Scope, roles, navigation & glossary](#1-scope-roles-navigation--glossary)
+2. [App shell, authentication, session, profile & role registration](#2-app-shell-authentication-session-profile--role-registration)
+3. [Browsing markets: list, catalog & filtering](#3-browsing-markets-list-catalog--filtering)
+4. [Market detail & binary betting (bet, commit-reveal, cancel, transfer)](#4-market-detail--binary-betting-bet-commit-reveal-cancel-transfer)
+5. [Multi-outcome markets (create, bet, cancel, liquidity, resolve)](#5-multi-outcome-markets-create-bet-cancel-liquidity-resolve)
+6. [Creating a market (creator role)](#6-creating-a-market-creator-role)
+7. [Oracle role: acceptance, insurance, pending queue & resolution](#7-oracle-role-acceptance-insurance-pending-queue--resolution)
+8. [Disputes & DAO / committee governance](#8-disputes--dao--committee-governance)
+9. [Wallet, balance, history, liquidity pools & leverage](#9-wallet-balance-history-liquidity-pools--leverage)
+10. [Master API / transaction coverage matrix](#10-master-api--transaction-coverage-matrix)
+
+> **⚠ Contract gaps found during this audit** (see §5 for detail) — the node team should treat these as known bugs to fix rather than reproduce: (1) the "Add liquidity" button always calls the binary `add-liquidity` even on multi markets — `add-liquidity-multi` / `withdraw-liquidity-multi` have **no client caller**; (2) multi resolution sends field `outcome` but `resolve-market-multi` reads `winning_outcome`; (3) the multi-cancel success toast reads `data.returned` while the endpoint returns `return_amount`.
+
+---
+
+## 1. Scope, roles, navigation & glossary
+
+### 1.1 Actors / roles
+
+The app recognises the following roles (About screen, `about.role_*`). A single account may hold several roles at once; role capability gates specific screens and API calls.
+
+| Role | In-app name | What they do | Gated by |
+|------|-------------|--------------|----------|
+| **Player / Bettor** | Player (`about.role_player`) | Buys outcome tokens (bets), cancels, transfers positions, claims payouts | Any authenticated user |
+| **Market Creator** | Market creator (`about.role_creator`) | Creates markets, sets rules/oracle/committee/fees | `user.create_market == 1` (must **register as creator**) |
+| **Oracle** | Oracle (`about.role_oracle`) | Accepts/rejects assigned markets, resolves outcome, no-contest, funds insurance | Registered oracle (`register-oracle`) |
+| **Liquidity Provider (LP)** | Liquidity provider (`about.role_lp`) | Adds/withdraws liquidity, lazy-pool deposits, leverage/boost | Any authenticated user |
+| **Disputer** | (a Player who disputes) | Opens a dispute against an oracle's resolution | Any bettor on the market, within grace window |
+| **Oracle-as-respondent** | Oracle | Responds to a dispute raised against them | The market's oracle |
+| **Arbitrator / Committee / DAO resolver** | Arbitrator "Judge" (`about.role_judge`) | Votes/decides disputes (oracle right/wrong + justification), can slash, unban | Committee member / DAO voter / private resolver |
+
+### 1.2 Authentication
+
+Two entry paths (see §2):
+1. **Telegram Mini App** — `window.Telegram.WebApp.initData` is POSTed to `check-session` → returns a session token + user.
+2. **Website / browser** — `auth-code` returns a one-time code the user pastes into the Telegram bot to bind a session.
+
+Session token is persisted in a cookie and `localStorage`; on every load `load-session` re-hydrates the user object.
+
+### 1.3 Navigation map
+
+The shell has a **4-item bottom tab bar** (`template/index.tpl`). Visibility is role-gated by CSS classes `v1`/`v3`/`v4` set in `update_user()`:
+
+- **v1** — not logged in: only *Markets* + *Profile* (login).
+- **v3** — logged in, not a creator: *Markets*, *Balance*, *Profile*.
+- **v4** — logged in creator: *Markets*, *Create*, *Balance*, *Profile*.
+
+Routing is hash-based: `#tab//` (`select_tab()` / `check_hash()` / `hashchange`). Full screen map:
+
+| Hash | Tab | Screen | Section |
+|------|-----|--------|---------|
+| `#tab/market` | Markets | Market **list** — status filter (all/active/resolved/closed) + "show risky" + category/subcategory/tags/sort filters | §3 |
+| `#tab/market/about` | Markets | **About / landing** page (default when no hash) | §1.5 |
+| `#tab/market/` | Markets | **Market detail** (odds, bet form, my bets, resolution, dispute blocks) | §4, §5, §7, §8 |
+| `#tab/market/oracle/` | Markets | **Oracle public profile** (reliability, stats) | §7 |
+| `#tab/create` | Create | **Create market** form (creator only) | §6 |
+| `#tab/history` | Balance | **Balance / wallet**: top-up, withdraw, transaction history | §9 |
+| `#tab/profile` | Profile | **Profile**: identity, balances, role registration, preferences, oracle settings | §2 |
+| `#s/` | — | Session-injection deep link → redirects to Profile | §2 |
+
+### 1.4 System settings the UI reads
+
+`load-settings` returns a config object; the client reads these keys via `get_setting()`:
+
+| Key | Used for |
+|-----|----------|
+| `dispute_grace_hours` | Grace window (countdown) before a resolution is final / disputable |
+| `min_risk_score_listing` | Threshold below which a market is "risky" (hidden unless *show risky*) |
+| `min_risk_score_betting` | Threshold below which betting is risk-blocked (mandatory risk confirm) |
+| `max_time_penalty` | Cap on time-penalty display in market detail |
+| `commit_no_reveal_penalty_permille` | Penalty (‰) applied to committed-but-unrevealed hidden bets |
+| `lmsr_min_outcomes` / `lmsr_max_outcomes` | Min/max outcomes for Onix Multi creation |
+
+### 1.5 About / landing screen
+
+Default landing (`render_about_page`, shown when no hash). Static, no API calls. Content (`about.*`): title + lead, the 5 **platform roles** (Player, Creator, Oracle, LP, Arbitrator/Judge), a "how it works in 5 steps" block, **market types** (Onix Binary = Yes/No; Onix Multi = 2–10 outcomes), FAQ, copyright and Onix protocol credit. Purely informational — the thin client can render it statically.
+
+### 1.6 Glossary of on-screen money/label conventions
+
+- Balances are integers in base units; the UI divides by `price_precision_ratio` (VIZ precision = 3) and renders with `render_number()`. Currency glyph is **Ƶ** (VIZ).
+- Profile shows up to four sub-balances: **available** (`balance`), **bets** (`bets_balance`), **liquidity** (`liquidity_balance`), **oracle** (`oracle_balance`).
+- **Reliability / Trust index** — an oracle score 0–100 rendered as a coloured badge with class buckets: excellent / good / average / new / poor / unreliable (`reliability_score_class` / `_label`, `oracle.score_*`).
+## 2. App shell, authentication, session, profile & role registration
+
+This section documents the application shell (HTML container, bottom tab bar, hash routing, message area), the two identity flows (Telegram WebApp vs. website one-time code), session persistence, the Profile screen, and the Oracle / Creator / preferences registration forms. All field names below are taken verbatim from `template/index.tpl`, `app.js`, `module/api.php`, and the English labels from `i18n/en.json`.
+
+> **Note on transport.** The client always POSTs/GETs JSON to `/api//` (trailing slash). The generic helper `api_post(endpoint, body)` (app.js ~2044) does `fetch('/api/'+endpoint+'/', {method:'POST', body: JSON.stringify(body)})`, parses JSON, and **throws `new Error(data.error || 'Error')` on any non-2xx** — so every `api_post` caller shows the server's `error` string. Some shell calls use raw `fetch` with GET; those are noted per endpoint. Session is NOT sent in the JSON body for `api_post` calls — the server resolves the user from the `forecaster_session` cookie (except `load-session`, which sends `{session}` explicitly).
+
+---
+
+### 2.1 Application shell (`template/index.tpl`)
+
+The whole app is a single HTML page. Server-side placeholders (`{title}`, `{head_addon}`, `{meta_image}`, `{content}`, `*_change_time`) are filled by PHP at render time; the SPA logic is entirely in `app.js` + `market_math.js`, using `cash.min.js` (the `$` shim) and Telegram's `telegram-web-app.js`.
+
+Structure the thin client must reproduce:
+
+- **`.container`** — white card holding all screens.
+- **`.messages`** (hidden by default) — the toast/alert area. `show_message(type, text, timeout)` appends `…
` where `type` ∈ `success | danger | warning`. Default timeout 5000 ms; pass `false` to make a message sticky (used for the browser-login code and the risk confirmation). `check_messages()` toggles the container's visibility based on child count.
+- **Four tab-content panels**, each `id` mapped to a bottom-nav tab:
+ | Panel `id` | Bottom-nav `data-tab` | Label (en.json) |
+ |---|---|---|
+ | `market` | `market` | `nav.markets` = "Markets" |
+ | `create` | `create` | `nav.create` = "Create" |
+ | `history` | `history` | `nav.balance` = "Balance" (shows live balance `` + `Ƶ`) |
+ | `profile` | `profile` | `nav.profile` = "Profile" |
+
+ Note the **`history` panel/tab is the "Balance" screen** — internal id is `history`, user-facing label is "Balance".
+- **`.tabs`** — fixed bottom bar. `update_nav_labels()` (app.js 74) rewrites the four tab captions from i18n after locale load; the Balance tab is special-cased to preserve the inner `.balance` span and re-append `" Ƶ"`.
+
+**Tab width classes `v1`/`v2`/`v3`/`v4`** (index.tpl CSS 364–379) set each tab to 100 % / 50 % / 33 % / 25 % width. `update_user()` (app.js 236) sets exactly one class based on auth/role so only visible tabs occupy the bar:
+- Not authenticated → `.tabs.v1`; only **Markets** is shown (Create/Balance/Profile hidden via `display:none`).
+- Authenticated **without** `create_market` → `.tabs.v3`; Markets + Balance + Profile visible; Create hidden.
+- Authenticated **with** `create_market == 1` → `.tabs.v4`; all four tabs visible.
+
+(`v2` exists in CSS but is not assigned by `update_user()`.)
+
+> **VIZ thin-client note (suggestion):** the tab-gating is purely a UI concern driven by two account flags (`create_market`, and implicitly auth). On VIZ these map to on-chain/account state the thin client can read once at boot (e.g., "is this account a registered creator?") and cache; there is no per-render server round-trip needed just to decide tab visibility.
+
+---
+
+### 2.2 Hash routing scheme `#tab//`
+
+Routing is driven by `location.hash`. Three functions cooperate:
+- `check_hash()` (app.js 216) — runs on boot and after `update_user()`.
+- `hashchange` handler (app.js 442) — runs on every hash change.
+- `select_tab(name)` (app.js 814) — the router body that renders a tab's content based on the remaining hash segments.
+
+Hash grammar and enumerated subpaths (from `select_tab`, splitting `hash` on `/`):
+
+| Hash | Screen rendered | Notes |
+|---|---|---|
+| *(empty)* | redirects to `#tab/market/about` | default landing = About page |
+| `#s/` | sets `session=<…>`, replaces to `#tab/profile`, selects Profile | **login-by-link**: session token passed in the URL fragment |
+| `#tab/market` | Markets list | status filter tabs, category filter, `load_markets_list(-1,0)` |
+| `#tab/market/about` | About page (`render_about_page()`) | also reached by clicking the logo |
+| `#tab/market/oracle/` | Oracle public profile | `load_oracle_profile(id)` → `load-oracle-profile` |
+| `#tab/market/` | Single market detail | `load_market_detail(id)` (any numeric 3rd segment that isn't `about`/`oracle`) |
+| `#tab/create` | Create-market form | role-gated on `create_market`; loads oracles + committees |
+| `#tab/history` | Balance screen (deposit/withdraw + history table) | `load-history` |
+| `#tab/profile` | Profile screen | rendered by `update_user()` + `render_profile_extra()` |
+
+`navigate(hash)` pushes a new history entry (no-op if unchanged); `navigate_replace(hash)` replaces the current entry (used to normalize `#s/…` and to strip subpaths after rendering Create/Balance). Tabs are also clickable directly: `app_mouse` (app.js 3018) intercepts clicks on `.tab` and calls `navigate('#tab/'+data-tab)`; clicking `.logo-clickable` navigates to `#tab/market/about`.
+
+---
+
+### 2.3 Boot sequence & the two login flows
+
+On `$(function(){…})` (app.js 456) the client:
+1. `load_system_settings()` (see 2.7) and `init_i18n()` → then `update_nav_labels()` + `load_categories()`.
+2. Reads the session token from the **`forecaster_session` cookie**, falling back to **`localStorage['forecaster_session']`**, else from a `#s/…` hash.
+3. If a token exists → `try_load_session()`. On error, clears the token and falls through to `try_check_session()`. On success, sets `auth=true`, `user=result`, adopts `user.lang` if present, and calls `update_user()`.
+4. If no token → `try_check_session()`.
+
+**Flow A — Telegram WebApp (automatic).** `try_check_session()` (app.js 373) checks `window.Telegram.WebApp.initData`. If non-empty it POSTs it to `check-session`. On success it calls `save_session(result, update_user)` which stores `session`, sets the cookie to expire at `result.expiration`, mirrors to localStorage, sets `auth=true`, `user=result.user`.
+
+**Flow B — Website one-time auth code.** If there is no Telegram initData, `try_check_session()` GETs `auth-code`, then shows a **sticky warning** `auth.browser_login` = *"Sign in via browser through Telegram bot"* with a deep link `https://t.me/viz_forecaster_bot?start=`. The user opens the bot, which (out of band) issues a session; the bot hands the session back to the site via a `#s/` link, closing the loop into Flow A's persistence.
+
+**Session persistence** (`save_session` app.js 352, `save_session_simple` app.js 339): the token lives in the `forecaster_session` cookie (path `/`, 30-day expiry for the simple path, or server `expiration` for the full path) **and** mirrored to `localStorage`. Logout (`logout-action` in `app_mouse` 3124) sets `session=false; auth=false; user={}` then `save_session_simple()` which deletes both cookie and localStorage entry and re-renders as logged-out.
+
+> **VIZ thin-client note (suggestion):** On VIZ the identity is a keypair, not a server session cookie. The thin client would replace both flows with local key management: derive/import the VIZ account key, and instead of a server-issued `session` token, sign each state-changing request (a broadcast) with the account's active key. The Telegram-initData verification and the one-time-code bridge are server-auth artifacts that disappear; what must be preserved is the *account identity* and the *balance/role fields* the UI reads from `user`.
+
+#### `load-session`
+- **Method:** POST · **When:** boot, whenever a stored session token exists; also after any role/pref change via `update_user_data()`. · **Auth:** the token itself.
+- **Request**
+ | Field | Type | Meaning |
+ |---|---|---|
+ | `session` | string | session hash from cookie/localStorage/hash |
+- **Response:** the full `users` row (with `data` JSON-decoded). UI consumes (see Profile 2.5): `data.username`, `data.first_name`, `data.last_name`, `data.photo_url`, `create_market`, `balance`, `bets_balance`, `bets_count`, `liquidity_balance`, `oracle_balance`, `oracle`, `oracle_insurance`, `oracle_rules`, `oracle_fee`, `oracle_fixed_fee`, `account`, `jurisdiction`, `lang`, `memo`.
+- **Errors:** throws `Unknown session` / `Unknown user from session` (non-2xx) → boot shows `auth.session_error` and falls back to `try_check_session`.
+
+#### `check-session`
+- **Method:** POST · **When:** running inside Telegram WebApp with non-empty `initData`. · **Auth:** Telegram HMAC signature (validated server-side against `bot_key`).
+- **Request**
+ | Field | Type | Meaning |
+ |---|---|---|
+ | `data` | string | raw `window.Telegram.WebApp.initData` querystring |
+- **Response**
+ | Field | Type | UI use |
+ |---|---|---|
+ | `session` | string | stored as the session token; cookie/localStorage |
+ | `expiration` | int (unix s) | cookie expiry |
+ | `user` | object | full users row → becomes `user`, drives Profile & tabs |
+- **Errors:** `Unknown tg init data`, `Time range from tg init data exceed 10 min range`, `Hash from tg init data is different` → boot renders logged-out.
+
+#### `auth-code`
+- **Method:** GET · **When:** website context (no Telegram initData). · **Auth:** none.
+- **Request:** none.
+- **Response**
+ | Field | Type | UI use |
+ |---|---|---|
+ | `code` | string (sha256) | inserted into the `auth.browser_login` deep-link `?start=` shown as a sticky warning |
+- **Errors:** on failure the client throws and renders logged-out.
+
+---
+
+### 2.4 The Markets / Create / Balance tabs (routing entry points)
+
+Only the routing side is in scope here (screen bodies are documented in later sections). From `select_tab`:
+- **`create`** — if `!auth`, shows `market.not_logged_in`; if `auth` but `create_market != 1`, shows `market.cannot_create` = *"You cannot create markets."*; if `create_market == 1`, renders the full create form and eagerly GETs `load-oracles` and `load-committees` to populate the Oracle/Committee ``s.
+- **`history`** (Balance) — if `!auth`, `market.not_logged_in`; else shows available balance, Top-up / Withdraw toggles, and a 100-row history table via GET `load-history`. History type labels come from `balance.history_type_0..16`.
+- **`market`** — default view: status filter chips (`filter.all` / `filter.active` / `filter.resolved` / `filter.closed`, `data-status` = -1/1/3/2), a **"Show risky markets"** checkbox (`filter.show_risky`), the category filter, then the list.
+
+---
+
+### 2.5 Profile screen (`update_user()` + `render_profile_extra()`)
+
+Rendered into `#profile .section`. When **not authenticated**, shows `profile.not_logged_in` = *"You are not logged in… sign in via the Telegram bot."* and applies `.tabs.v1`.
+
+When **authenticated**, `update_user()` (app.js 236) builds, in order:
+1. **Avatar** — `.avatar` div background = `user.data.photo_url` (if present).
+2. **Username** — `@` as `.title`; also written into the Profile tab caption.
+3. **Full name** — `user.data.first_name` + optional `last_name` as `.subtitle`.
+4. **Create-tab gating** — if `user.create_market == 1` → `.tabs.v4` + show Create tab; else `.tabs.v3`.
+5. **Live balance in the Balance tab** — `user.balance` (integer minor units; `price_precision_ratio` scales it) is abbreviated (k / kk) into `.tab[data-tab="history"] .balance`.
+6. **Balance lines** (each shown only if the value is `> 0`), using `render_number()` (integer → `/1000`, 3 dp):
+ | Line | i18n key | Label |
+ |---|---|---|
+ | available | `profile.available_balance` | "Available balance: %%AMOUNT%% Ƶ" (always shown) |
+ | in-market bets | `profile.bets_balance` | "In market bets: %%AMOUNT%% Ƶ" |
+ | bets count | `profile.bets_count` | "Bets placed: %%COUNT%%" |
+ | in liquidity | `profile.liquidity_balance` | "In liquidity: %%AMOUNT%% Ƶ" |
+ | oracle collateral | `profile.oracle_balance` | "Oracle collateral: %%AMOUNT%% Ƶ" |
+7. **Log out** button — `.logout-action`, label `profile.logout` = "Log out".
+8. **`.profile-extra`** container → filled by `render_profile_extra()`.
+
+`render_profile_extra()` (app.js 2787) appends (all only when authenticated):
+- **User Preferences** form (2.6): language `` (`en`/`ru`, prefilled from `current_lang`) + jurisdiction ` ` prefilled from `user.jurisdiction`, and **Save preferences** button (`profile.save_preferences`).
+- **Oracle section** — branches on `user.oracle`:
+ - If `oracle == 1`: shows insurance fund (`profile.oracle_insurance_display` with `user.oracle_insurance`), a deposit/withdraw insurance form (Deposit/Withdraw → `oracle-deposit-insurance` / `oracle-withdraw-insurance`, out of scope here), and an **Oracle settings** edit form (name / rules / fee ‰ / fixed fee) that re-submits `register-oracle`. Also renders pending markets awaiting the oracle's accept/reject (`load-pending-markets`).
+ - Else: the **"Become an oracle"** registration form (2.6).
+- **Creator section** — if `create_market != 1`: the **"Become a market creator"** form (2.6).
+- **My positions** (`load-positions`) and **My payouts** (`load-payouts`) tables, and a boost-positions container (documented in later sections).
+
+> **VIZ thin-client note (suggestion):** Everything on this screen is read-model state of a single account (balances split into available/bets/liquidity/oracle, counts, role flags, oracle metadata). On VIZ these become derived balances/asset holdings plus custom account metadata; the thin client can render this profile from account state + indexed positions rather than one monolithic `user` object.
+
+---
+
+### 2.6 Role registration & preferences endpoints
+
+#### `register-oracle`
+- **Method:** POST (`api_post`) · **When:** clicking **Register** in "Become an oracle" (`register_oracle_action`, app.js 2917) OR **Save settings** in the Oracle-settings edit form (`oracle_update_action`, app.js 2932). · **Auth:** required (server: "Auth required").
+- **Required role:** any authenticated user may become an oracle (charged a registration fee); existing oracles use the same endpoint to update settings free of charge.
+- **Request** (registration path sends the first three; settings-edit path adds `oracle_fixed_fee`):
+ | Field | Type | Meaning |
+ |---|---|---|
+ | `account` | string | oracle display name (required; server throws "Oracle name is required" if empty) |
+ | `oracle_rules` | string | operating rules / statement of intent |
+ | `oracle_fee` | int | fee in ‰ (per-mille) taken from the market |
+ | `oracle_fixed_fee` | float (VIZ) | fixed reward; server multiplies ×1000; ≤0 defaults to 5000 (=5 VIZ). *(sent only from the settings-edit form)* |
+- **Response**
+ | Field | Type | UI use |
+ |---|---|---|
+ | `status` | bool | success flag |
+ | `updated` | bool | present when editing an existing oracle (no fee charged) |
+- **UI states:** loading text `bet.sending` = "Sending…" / `profile.saving` = "Saving…"; success → `profile.registered_success` / `profile.settings_saved` (green); on throw shows `common.error_prefix` + message (red). On success calls `update_user_data()` (re-runs `load-session`, re-renders Profile).
+- **Errors surfaced:** "Insufficient balance for registration fee" (new registration when `balance < oracle_registration_fee`), "Registration failed", "Profile update failed".
+
+#### `register-creator`
+- **Method:** POST (`api_post`) · **When:** clicking **Register** in "Become a market creator" (`register_creator_action`, app.js 2948). · **Auth:** required.
+- **Request**
+ | Field | Type | Meaning |
+ |---|---|---|
+ | `creator_rules` | string | creator's operating rules |
+- **Response**
+ | Field | Type | UI use |
+ |---|---|---|
+ | `status` | bool | success flag |
+- **Server side-effects:** sets `create_market=1` **and** `add_liquidity=1`, stores `creator_rules`, deducts `creator_registration_fee` from balance. On success the UI calls `update_user_data()`, which flips the account to `.tabs.v4` and reveals the **Create** tab.
+- **UI states:** loading `bet.sending`; success `profile.registered_success` (green); error `common.error_prefix` + message (red).
+- **Errors surfaced:** "Already registered as market creator", "Insufficient balance for registration fee", "Registration failed", "Auth required".
+
+#### `update-user-preferences`
+- **Method:** POST (`api_post`) · **When:** clicking **Save preferences** (`save_preferences_action`, app.js 2974). · **Auth:** required.
+- **Request**
+ | Field | Type | Meaning |
+ |---|---|---|
+ | `lang` | string | UI language; server whitelists to `en`/`ru` (anything else → `en`) |
+ | `jurisdiction` | string | country code; server uppercases, strips non-A–Z, truncates to 5 chars |
+- **Response**
+ | Field | Type | UI use |
+ |---|---|---|
+ | `status` | bool | success flag |
+ | `lang` | string | normalized value echoed back |
+ | `jurisdiction` | string | normalized value echoed back |
+- **Client after success:** if `lang` changed, sets `current_lang`, persists `localStorage['user_lang']`, `await load_i18n(new_lang)`, `update_nav_labels()`; updates `user.jurisdiction` / `user.lang` in memory. The jurisdiction value later gates market visibility (`is_market_jurisdiction_banned`, app.js 2997) and triggers the `jurisdiction.warning` modal.
+- **UI states:** loading `bet.sending`; success `common.success` = "Success!" (green); error `common.error_prefix` + message (red).
+- **Errors surfaced:** `{status:false, error:'Not authenticated'}` when unauthenticated.
+
+#### `load-oracle-profile`
+- **Method:** POST (`api_post` via `load_oracle_profile`) · **When:** navigating to `#tab/market/oracle/`, or clicking an `.oracle-profile-link`. · **Auth:** none required (public read).
+- **Request**
+ | Field | Type | Meaning |
+ |---|---|---|
+ | `oracle_id` | int | target oracle's user id |
+- **Response** (consumed by the Oracle public-profile screen; labels under the `oracle.*` i18n namespace):
+ | Field | Type | UI use |
+ |---|---|---|
+ | `id` | int | oracle id |
+ | `name` | string | display name (`oracle.profile_title`) |
+ | `rules` | string | shown under `oracle.rules_label` |
+ | `fee` | int | ‰ fee (`oracle.fee_label`) |
+ | `fixed_fee` | int | fixed reward (`oracle.fee_fixed`) |
+ | `insurance` | int | insurance fund (`oracle.insurance_fund_display`) |
+ | `banned` | int/bool | shows `oracle.banned_label` |
+ | `ban_until` | int | `oracle.banned_until` / `oracle.banned_forever` |
+ | `reliability_score` | int (0-100) | behavior score → reliability badge classes |
+ | `is_new` | 0/1 | shows `oracle.hint_new` / "New oracle" |
+ | `risk_score` | float | coverage = insurance ÷ active bets (`oracle.coverage`) |
+ | `trust_score` | int (0-100) | composite Trust Index (`oracle.trust_index`) |
+ | `metrics{}` | object | detailed stat table: `markets_accepted`, `markets_resolved`, `markets_no_contest`, `markets_missed`, `disputes_received`, `disputes_lost`, `disputes_won`, `disputes_auto_closed`, `dispute_responses_missed`, `total_volume_resolved`, `total_insurance_slashed`, `avg_resolution_time`, `bans_received`, `active_since`, `last_active_time` → the `oracle.stat_*` rows |
+ | `derived{}` | object | `resolution_rate`, `dispute_loss_rate`, `no_contest_rate`, `deadline_miss_rate`, `dispute_response_rate` → the `oracle.rate_*` indicator rows |
+- **Errors:** server throws `Oracle not found` → the detail area shows `oracle.load_error`.
+
+> **VIZ thin-client note (suggestion):** Oracle registration, creator registration and preference updates are all account-metadata + fee mutations. On VIZ these become signed operations (e.g., an account custom-metadata/JSON op plus a token transfer for the registration fee) broadcast by the account key; the reliability/trust metrics in `load-oracle-profile` are indexer-computed aggregates the thin client would read from an off-chain indexer rather than the chain itself.
+
+---
+
+### 2.7 System settings the UI reads (`load-settings`)
+
+#### `load-settings`
+- **Method:** GET (raw `fetch` in `load_system_settings`, app.js 142) · **When:** first thing at boot. · **Auth:** none.
+- **Request:** none.
+- **Response:** a flat JSON object `{ key: value, … }` (every row of the server `settings` table). Stored in `system_settings`; read via `get_setting(key, default)` (app.js 149).
+
+Keys the client actually reads (all `get_setting(...)` call sites in app.js):
+| Setting key | Read at (app.js) | What the UI does with it |
+|---|---|---|
+| `lmsr_max_outcomes` | 726, 744 | max number of outcomes allowed when creating an Onix Multi market (`market.max_outcomes`) |
+| `lmsr_min_outcomes` | 733, 745 | min number of outcomes for Onix Multi (`market.min_outcomes`) |
+| `dispute_grace_hours` | 1225 | grace-period countdown before payout / dispute window (`status.grace_countdown`) |
+| `min_risk_score_listing` | 1236 | threshold below which a market is treated as "risky" and hidden unless "Show risky" is on |
+| `min_risk_score_betting` | 1237, 1713 | threshold below which betting shows the risk warning / requires explicit confirmation (`bet.risk_detail`) |
+| `max_time_penalty` | 1686 | caps the displayed late-bet penalty max % |
+| `commit_no_reveal_penalty_permille` | 2131 | penalty (‰) shown for commit-reveal bets that are never revealed |
+
+> **VIZ thin-client note (suggestion):** these are global protocol parameters, not per-user data. On VIZ they would either be chain-consensus constants or values published by the protocol's governing account; the thin client should fetch them once at boot (as here) and cache. None of them require auth.
+
+---
+
+### 2.8 Session/identity summary for the porting team
+
+- **Session token name:** `forecaster_session` (cookie **and** `localStorage`).
+- **The `user` object** returned by `load-session` / `check-session.user` is the single source of truth for: identity (`data.username/first_name/last_name/photo_url`), balances (`balance`, `bets_balance`, `liquidity_balance`, `oracle_balance`), counts (`bets_count`), role flags (`create_market`, `oracle`, `add_liquidity`), oracle metadata (`account`, `oracle_rules`, `oracle_fee`, `oracle_fixed_fee`, `oracle_insurance`), preferences (`lang`, `jurisdiction`), and the deposit `memo`.
+- **Role gates in the UI:** Create tab ⇔ `create_market == 1`; oracle settings/insurance UI ⇔ `oracle == 1`; the "Become …" forms appear only when the corresponding flag is unset.
+## 3. Browsing markets: list, catalog & filtering
+
+This section documents the **Markets** browsing screen (the default landing tab, `nav.markets` = "Markets"), reached at hash `#tab/market` with no trailing id. It covers the full filter/sort/status/risk/category UI, the two list-fetch endpoints, jurisdiction hiding, pagination ("load more"), and every field of the market **card** rendered by `render_market_card`.
+
+All list state lives in module-level JS variables (app.js 1166–1171):
+
+| Variable | Default | Meaning | Sent as request field |
+|---|---|---|---|
+| `current_market_filter` | `-1` | Status tab (-1 all / 1 active / 3 resolved / 2 closed) | `status` |
+| `current_show_risky` | `0` | "Show risky markets" checkbox state (0/1) | `show_risky` |
+| `current_category` | `''` | Selected category chip id | `category` (only if non-empty) |
+| `current_subcategory` | `''` | Subcategory dropdown value | `subcategory` (only if non-empty) |
+| `current_tag` | `''` | Active hot-tag chip | `tag` (only if non-empty) |
+| `current_sort` | `'newest'` | Sort selector value | `sort` (only if not `newest`) |
+
+Note: the user's **country** is `user.jurisdiction` (set via profile preferences, uppercased ISO code). It is applied **client-side** in `load_markets_list` — the list endpoints do NOT receive jurisdiction as a param; the client hides banned markets after fetch (see below).
+
+---
+
+### Screen layout (`select_tab('market')`, app.js 1017–1051)
+
+When `#tab/market` has no `path[2]` (no market id / not `about` / not `oracle`), the list view is built:
+
+1. **Logo header** (clickable, returns to markets), then ` `.
+2. **Status filter tabs** (`.market-filters` row): four buttons and one checkbox:
+ - `filter.all` "All" — `data-status="-1"` (starts `active`)
+ - `filter.active` "Active" — `data-status="1"`
+ - `filter.resolved` "Resolved" — `data-status="3"`
+ - `filter.closed` "Closed" — `data-status="2"`
+ - Checkbox `.show-risky-toggle` with label `filter.show_risky` "Show risky markets".
+3. **Category filter container** (`.category-filter-container`) = output of `render_category_filter()` (see below).
+4. **Markets list** (`.markets-list`) — initially shows `filter.loading` "Loading…", then filled by `load_markets_list(-1, 0)` on first render.
+
+#### Category filter block (`render_category_filter`, app.js 94–139)
+
+Rendered from `categories_cache` (populated by `load_categories()` on startup → `load-categories` endpoint). Contents:
+
+- **Category chips** (`.category-chips`): an "All categories" chip (`filter.all_categories`, `data-cat=""`) plus one chip per category showing `icon + localized label + (count)`. Active chip gets class `active` when `current_category` matches.
+- **Subcategory dropdown** (`.subcategory-select`): only shown when a category is selected AND it has subcategories. First option `filter.all_subcategories` "All subcategories"; each option shows localized subcategory label + `(count)`.
+- **Hot tags** (`.tag-chips`): label `filter.hot_tags` "Tags:" followed by one `.tag-chip` per hot tag showing `tag (count)`. Active tag toggled.
+- **Sort selector** (`.sort-select`): label `filter.sort_label` "Sort:" with three options — `filter.sort_newest` "Newest" (value `newest`), `filter.sort_volume` "Volume" (value `volume`), `filter.sort_expiration` "Ending soon" (value `expiration`).
+
+**Filter interactions** (delegated handlers):
+
+| User action | Handler location | Effect |
+|---|---|---|
+| Click category chip | app.js 3204–3213 (`app_mouse`) | sets `current_category`, clears `current_subcategory` + `current_tag`, re-renders filter block, reloads list from offset 0 |
+| Change subcategory dropdown | app.js 566–570 | sets `current_subcategory`, reloads list |
+| Click hot-tag chip | app.js 3215–3222 | toggles `current_tag` (click again clears), re-renders filter, reloads |
+| Change sort dropdown | app.js 571–575 | sets `current_sort`, reloads |
+| Click status tab | app.js 3224–3230 | moves `active` class, reloads with new `status` |
+| Toggle "show risky" | app.js 3232–3236 | reloads with current filter (checkbox re-read inside `load_markets_list`) |
+| Click "Load more" | app.js 3238–3242 | reloads passing the last card's id as `last_market_id` (append mode) |
+| Click a card (`.clickable-market`) | app.js 3192–3196 | navigates to `#tab/market/` (market detail) |
+
+---
+
+### The market list fetch (`load_markets_list`, app.js 1173–1217)
+
+Builds the request body, POSTs to **`load-markets-catalog`** (NOT `load-markets` — see note under that endpoint), then for each returned item:
+- Skips it if `is_market_jurisdiction_banned(market.tags)` is true (client-side jurisdiction ban).
+- Otherwise appends `render_market_card(market)`.
+
+If `last_market_id==0` (fresh load): replaces `.markets-list` html; if empty result, shows `filter.no_markets` "No markets found". If `last_market_id>0` (load more): removes the old load-more button and **appends** the new cards.
+
+**Pagination:** `limit` is hard-coded to **10**. If the returned array length ≥ limit, a `.load-more-btn` (`filter.load_more` "Load more") is appended carrying `data-last` = id of the last returned market. On error: `.markets-list` shows `filter.load_error` "Error loading markets" (non-ok) or `common.error_prefix + err.message` (exception).
+
+#### Jurisdiction handling (`is_market_jurisdiction_banned`, app.js 2997–3004)
+
+Returns false if the user has no `jurisdiction` or the market has no `tags`. Otherwise it builds the ban tag `jurisdiction-ban:` (user's jurisdiction uppercased) and returns true if that exact tag is present in `market.tags`. Banned markets are silently omitted from the list. (On the market **detail** screen, app.js 1563, a banned market instead triggers `show_jurisdiction_warning` — a modal with `jurisdiction.warning` text, `jurisdiction.proceed` "I understand, proceed" and `jurisdiction.go_back` "Go back".) The market-creation form collects `jurisdiction_relevant` and `jurisdiction_banned` free-text inputs (app.js 873–874) which become these tags server-side.
+
+---
+
+#### `load-markets-catalog`
+
+- **Method:** POST — `/api/load-markets-catalog/`
+- **Fires when:** every markets-list load/refresh — initial render, any filter/sort/status/risky change, and "Load more". This is the endpoint the browsing screen actually calls.
+- **Role/auth:** none (public).
+
+**Request fields** (api.php 213–243):
+
+| Field | Type | Meaning |
+|---|---|---|
+| `last_market_id` | int | Pagination cursor; adds `AND id < last_market_id`. `0`/absent = first page. |
+| `limit` | int | Page size, clamped 1–100 (client sends 10). |
+| `status` | int | `-1` all (default → status ≥ 1, excludes pending); `0` pending, `1` active, `2` closed, `3` resolved. |
+| `category` | string | Exact category slug filter (omitted when empty). |
+| `subcategory` | string | Exact subcategory slug filter. |
+| `tag` | string | Tag filter via INNER JOIN on `market_tags`. |
+| `sort` | string | `newest` (default, `id DESC`), `volume` (`bets_sum DESC`), `expiration` (`betting_expiration ASC`). |
+
+Note: `show_risky` IS put in the body by the client but this lightweight endpoint does NOT read/apply it (risk filtering only exists in `load-markets`). Multi-outcome markets are fully priced here.
+
+**Response:** a JSON **array** of market rows. Fields consumed by `render_market_card`:
+
+| Field | Type | Card usage |
+|---|---|---|
+| `id` | int | Card `rel` (click → detail); last id used as `data-last` for load-more. |
+| `status` | int | Status badge: 1 active / 2 closed / 3 resolved / else failed; label from `status.market_` ("Active"/"Closed"/"Resolved"/"Awaiting oracle"). |
+| `q` | string | Market question, shown as `.title` (html-escaped). |
+| `a`, `b` | string | Binary outcome A/B labels (answer lines). |
+| `category`, `subcategory` | string | (returned; not directly drawn on card). |
+| `metadata` | string/json | Multi-language title/labels source (not parsed by card). |
+| `reserve_a`, `reserve_b`, `k` | int | Binary AMM reserves → `calculate_bet` + `implied_probability` for price/odds/probability lines. |
+| `liquidity_sum` | int | Binary "Liquidity: X Ƶ" line (`market.liquidity_label`, divided by `price_precision_ratio`). |
+| `a_bets_sum`, `b_bets_sum` | int | Binary bet-distribution bar percentages (`.bets-fill-a`/`-b`). |
+| `bets_sum` | int | (volume sort key server-side). |
+| `betting_expiration` | unixtime | "Betting until:" datetime (`market_detail.betting_until`). |
+| `result_expiration` | unixtime | "Result deadline:" datetime (`market_detail.result_deadline`). |
+| `market_type` | int | 0 = binary, 1 = multi (Onix Multi badge + multi bar). |
+| `outcome_count` | int | Multi badge text `market.type_multi_badge` "Onix Multi (N outcomes)". |
+| `lmsr_b`, `lmsr_subsidy` | int | Multi depth line `market.depth_line` "Depth (b): … | Subsidy: … Ƶ". |
+| `outcomes[]` | array | Multi only: each `{outcome_index,label,q,bets_sum,bets_count,price}`. `price` (0–10000) → `%` shown per outcome, colored left border. |
+| `payout_status` | int | With status 3, `1` = awaiting payout → grace-period countdown badge. |
+| `update` | unixtime | Grace-period countdown base (`update + dispute_grace_hours`). |
+| `time` | unixtime | Creation time (returned; not drawn). |
+| `tags[]` | array | Used by `is_market_jurisdiction_banned` to hide market. |
+
+**UI states/errors:** empty array → "No markets found"; array length ≥ limit → "Load more" button; grace countdown badge (`market.payout_in` "Payout in …") for resolved-awaiting-payout markets.
+
+> **VIZ thin-client note (suggestion):** The catalog is a paginated, filterable query over on-chain market objects. A thin client would query the node's market index by `status`, `category`, `subcategory`, `tag`, ordered by newest/volume/expiration, page by `id` cursor. Risk/oracle enrichment is not needed for the list; the client should carry `tags` so it can locally hide `jurisdiction-ban:` markets for the logged-in user.
+
+---
+
+#### `load-markets`
+
+- **Method:** POST — `/api/load-markets/`
+- **Fires when:** *(legacy/enriched variant — the current browsing screen calls `load-markets-catalog` instead; documented here because it is the risk-and-oracle-enriched list contract and shares the card fields.)*
+- **Role/auth:** none (public).
+
+**Request fields** (api.php 124–157):
+
+| Field | Type | Meaning |
+|---|---|---|
+| `last_market_id` | int | Pagination cursor (`AND id < last_market_id`). |
+| `limit` | int | Page size, clamped to max 100. |
+| `status` | int | Same semantics as catalog (-1 all→≥1; 0/1/2/3 exact). |
+| `category` | string | Sanitized `[a-z0-9_-]`, lowercased. |
+| `subcategory` | string | Sanitized `[a-z0-9_-]`, lowercased. |
+| `tag` | string | Sanitized `[a-z0-9_:-]`; INNER JOIN on `market_tags`. |
+| `show_risky` | int | 0/1. When 0, active markets whose `risk_score < min_risk_score_listing` (default 2.5) are dropped from the list. |
+
+**Response:** array of enriched market rows. In addition to all catalog fields above, this endpoint adds the fields the card uses for creator/oracle meta and risk badges:
+
+| Field | Type | Card usage |
+|---|---|---|
+| `creator_data` | object | `{username,first_name,…}` → creator name in `market.meta_creator_oracle` "Creator: … · Oracle: …". |
+| `account` | string | Oracle account name (fallback `#`). |
+| `oracle` | int | Oracle id (fallback name). |
+| `oracle_reliability_score` | int (0–100) | Feeds `render_reliability_badge`. |
+| `oracle_is_new` | int (0/1) | "New" badge variant. |
+| `risk_score` | float | If present: `< min_risk_score_betting` (1.5) → red `market.risk_high` "⚠ High risk (S)"; `< min_risk_score_listing` (2.5) → amber `market.risk_warning` "⚠ Risk (S)". |
+| `risk_score_low` | int (0/1) | Server flag driving the `show_risky` filter. |
+
+**UI states/errors:** same list-level states as catalog; risky active markets suppressed unless `show_risky=1`.
+
+> **VIZ thin-client note (suggestion):** This is the catalog plus per-oracle reliability + coverage (risk) enrichment. A thin client would additionally read each market's oracle account, its reliability metrics and insurance-vs-open-bets coverage from the node, compute the badge locally, and gate "risky" markets behind the user's `show_risky` preference.
+
+---
+
+#### `load-categories`
+
+- **Method:** POST — `/api/load-categories/`
+- **Fires when:** app startup (`load_categories()`, app.js 88–92); result cached in `categories_cache` and used by `render_category_filter()` and the create-market category dropdown.
+- **Role/auth:** none.
+- **Request:** empty body `{}`.
+
+**Response** (object, api.php 387–413):
+
+| Field | Type | UI usage |
+|---|---|---|
+| `categories[]` | array | Each `{id, icon, i18n, count, subcategories[], tags[]}`. `id`→chip `data-cat`; `icon`+localized `i18n`→chip label; `count`→`(n)` on chip. |
+| `categories[].subcategories[]` | array | Each `{id, i18n, count}` → subcategory dropdown options. |
+| `hot_tags[]` | array | Each `{tag, count}` (top 20 active tags, excluding `jurisdiction%`) → hot-tag chips. |
+
+> **VIZ thin-client note (suggestion):** Categories are a fixed taxonomy (`get_market_categories`) plus live counts. A thin client can hold the taxonomy statically and derive per-category/subcategory/tag counts by aggregating active markets from the node.
+
+---
+
+#### `load-oracles`
+
+- **Method:** GET — `/api/load-oracles/`
+- **Fires when:** opening the **create-market** form (`select_tab('create')`, app.js 897–918) to populate the oracle ``. (Not part of the browsing list, but drives oracle labels/reliability used in the create flow.)
+- **Role/auth:** none to fetch (creating requires `user.create_market==1`).
+- **Request:** none.
+
+**Response** (array, api.php 437–443): each `{id, fee, name, rules, insurance, reliability_score, is_new}`. The form option shows `name [Fee X%, resolution: ]` with `value=id`, `rel=fee`. On non-ok → `market.oracle_load_error` warning toast.
+
+> **VIZ thin-client note (suggestion):** List accounts flagged as oracles from the node with their fee, insurance and computed reliability score for selection when creating a market.
+
+---
+
+#### `load-committees`
+
+- **Method:** GET — `/api/load-committees/`
+- **Fires when:** opening the **create-market** form (app.js 921–935) to populate the dispute-committee ``.
+- **Role/auth:** none to fetch.
+- **Request:** none.
+
+**Response** (array, api.php 500–515): each `{id, name}` (name resolved from account, else first/last name, else `@username`, else `User #id`). Rendered as options `name [#id]`, first option `market.committee_not_selected`.
+
+> **VIZ thin-client note (suggestion):** List accounts flagged as committees for optional dispute-resolution assignment at market creation.
+
+---
+
+### Reliability badge (`render_reliability_badge` / `reliability_score_class` / `reliability_score_label`, app.js 595–614)
+
+Used inline on the card's creator/oracle meta line and on the market-detail oracle line. Given a numeric `score` (0–100) and `is_new` flag it renders `score — `:
+
+| Condition | CSS class | Label (i18n) |
+|---|---|---|
+| `is_new` | `reliability-new` | `oracle.score_new` "New" |
+| `score ≥ 80` | `reliability-excellent` | `oracle.score_excellent` "Excellent" |
+| `score ≥ 60` | `reliability-good` | `oracle.score_good` "Good" |
+| `score ≥ 40` | `reliability-average` | `oracle.score_average` "Average" |
+| `score ≥ 20` | `reliability-poor` | `oracle.score_poor` "Poor" |
+| else | `reliability-unreliable` | `oracle.score_unreliable` "Unreliable" |
+
+---
+
+### Boost auto-close banner (card, app.js 1246–1248)
+
+If `boost_active_loaded` and the market id is in `boost_active_market_ids` (populated from a cached leverage-info call), the card shows a `.boost-banner` with `boost.banner_text` "⚠️ Your boost auto-closes in %%TIME%%" and a live countdown. This is per-user leveraged-position state, not part of the list endpoints.
+
+> **VIZ thin-client note (suggestion):** Auto-close timers derive from the user's open leveraged positions vs. each market's betting expiration; the client can compute the banner locally from position + market data queried per user.
+## 4. Market detail & binary betting (bet, commit-reveal, cancel, transfer)
+
+This section documents the single-market detail screen and the full binary-betting lifecycle (place, batch-queue, hidden commit-reveal, cancel, transfer). Everything is rendered client-side by `load_market_detail(market_id, jurisdiction_confirmed)` (app.js 1555–2041) from one enriched fetch (`load-market-enriched`). Amounts are shown/entered in VIZ (`Ƶ`) but the wire format is **milli-VIZ integers** (VIZ × 1000). Token/weight values, fee per-mille, and reserves are also integers. Fee/penalty "precision-6" values are divided by `price_precision_ratio` (10000) or `/10000` for display.
+
+> Note: this screen also renders Boost/leverage, add-liquidity, dispute, oracle-resolve and public liquidity/payout tables. Those are covered in their own sections; here we describe only the market header, the bet form, the "My bets" table, and the bet/cancel/transfer endpoints.
+
+---
+
+### Screen: Market detail
+
+Route: hash `#/market/` (the code re-derives `market_id` from `document.location.hash.split('/')[2]` after actions). On entry, if the user is authenticated the client first calls `pm_process_pending_reveals()` to re-send any commit-reveal reveals left pending in `localStorage` (resilience — see commit-reveal below), then fetches `load-market-enriched`.
+
+**Jurisdiction gate.** After the fetch, if `!jurisdiction_confirmed && is_market_jurisdiction_banned(m.tags)` the client aborts the render and calls `show_jurisdiction_warning(market_id)` instead, showing `jurisdiction.warning` ("This market is restricted in your jurisdiction …") with buttons `jurisdiction.proceed` ("I understand, proceed" → re-enters `load_market_detail` with `jurisdiction_confirmed=true`) and `jurisdiction.go_back` ("Go back"). The banned check is driven by `m.tags` (array of tag strings from the enriched response).
+
+The detail body (built into `.market-detail`) is composed top-to-bottom of:
+
+1. **Question & status** — `m.q` as title; a status badge `status.market_` (0 Awaiting oracle / 1 Active / 2 Closed / 3 Resolved; a 4th "failed" style is used for anything else). If `m.payout_status>0` a second badge `status.payouts_label` + `payout_status_labels[m.payout_status]`.
+2. **Grace countdown** — only when `status==3 && payout_status==1 && m.grace_period_end` and the end is in the future: badge `status.grace_countdown` ("Payout in (if no dispute)") with a live `#grace-countdown` element driven by `start_grace_countdown()` / `format_countdown()` (h:mm:ss; flips to `market.payout_available` "Payout available" at 0).
+3. **Boost auto-close banner** — if `data.my_leveraged_positions` has an open position (`status===0 && countdown_seconds>0`): `boost.banner_text`.
+4. **Odds / probabilities bar** — binary only (multi handled separately): a two-tone `.bets-bar` split by `a_bets_sum` vs `b_bets_sum` percentages, then two `.answer-line` rows showing side name (`m.a`, `m.b`), implied probability via `implied_probability(reserve_a, reserve_b)` = `{a: reserve_b/(ra+rb)*100, b: reserve_a/(ra+rb)*100}`, and `market_detail.price_odds` ("(Price: Ƶ, Odds: )") where price comes from `calculate_bet(side, 1000, reserve_a, reserve_b, k)[0]` (CPMM probe of a 1-VIZ trade) and odds = `1/price`.
+5. **Liquidity line** — `market.liquidity_label` ("Liquidity: Ƶ") from `m.liquidity_sum`.
+6. **Resolution result** — when `status==3 && resolved_outcome>=0`: `resolution.result_label` + winning side name; optional `m.decision` as sub-note.
+7. **Settings/time block** — `market_detail.created` (`m.time`), `market_detail.betting_until` (`m.betting_expiration`), `market_detail.result_deadline` (`m.result_expiration`), all rendered as localized datetimes; then `resolution.resolution_label` + `m.resolution` (the resolution rules text).
+8. **Creator** — `market_detail.creator` + `@username`/`first_name` from `data.creator.data`.
+9. **Oracle + reliability** — `market.oracle_name_label` with a clickable `.oracle-profile-link` (`data.oracle.account`), a reliability badge from `render_reliability_badge(data.oracle.reliability_score, data.oracle.is_new)`, and `market_detail.oracle_fee_display` ("(fee: X% from losing …)") using `data.oracle.oracle_fee/price_precision_ratio` plus optional fixed-fee suffix from `m.oracle_fixed_fee`. Hints: `market_detail.oracle_new_hint` if new, else `market_detail.oracle_low_hint` if score<40. `data.oracle.oracle_rules` shown via `market_detail.oracle_rules` if present.
+10. **Key parameters block** (`market_detail.params_title`) — LP fee (`m.liquidity_fee`), oracle fee (`m.oracle_fee`), optional fixed fee, creator fee (`m.creator_fee`), late-bet penalty (`m.time_penalty_value` / `m.time_penalty_type` / `m.penalty_curve_type`, capped by `max_time_penalty` setting), `market_detail.early_resolution` (`m.allow_early_resolution`), `market_detail.cancel_possible`/`cancel_impossible` (`m.allow_cancellation`), an `market_detail.instant_disabled` warning when `m.allow_instant_bet==0`, committee id (`m.committee`), and total bets (`m.bets_sum`).
+
+#### `load-market-enriched` — the single data source for this screen
+
+- **Method:** POST `/api/load-market-enriched/`
+- **Fires:** on every open/refresh of the market detail screen (and again ~1–1.2s after any bet/cancel/transfer via `setTimeout(load_market_detail,…)`).
+- **Auth/role:** none required to view; user-specific blocks (`my_bets`, `my_liquidity`, `my_leveraged_positions`) only populate when authenticated.
+
+**Request**
+
+| field | type | meaning |
+|---|---|---|
+| `market_id` | int | market to load |
+
+**Response** (top-level keys the UI consumes)
+
+| field | type | UI use |
+|---|---|---|
+| `market` (`m`) | object | all header/param fields (see below) |
+| `oracle` | object | `id`, `account`, `oracle_fee`, `oracle_fixed_fee`, `oracle_rules`, `oracle_insurance`, `reliability_score`, `is_new` → oracle block + reliability badge |
+| `creator` | object | `{id, data:{username?,first_name?}}` → creator line |
+| `my_bets` | array | drives "My bets" table (auth only) |
+| `my_liquidity` | array | "My liquidity" cards (auth only) |
+| `my_leveraged_positions` | array | boost auto-close banner (`status`, `countdown_seconds`) |
+| `all_bets` | array | public bets table (`user`,`user_data`,`side`,`outcome_index`,`amount`,`weight`,`time_penalty`,`status`,`resolved_amount`) |
+| `all_liquidity` | array | public LP table |
+| `all_payouts` | array | public payouts table (`user`,`type`,`amount`,`status`) |
+| `dispute` | object/null | dispute card |
+| `log` | array | market log (last 50): `time`,`action`,`details` |
+
+**`market` (`m`) fields the detail/bet UI reads** (grounded in the render):
+
+| field | type | UI use |
+|---|---|---|
+| `id`,`q` | int/str | market id, question title |
+| `status` | int | 0/1/2/3 badge; gates bet form (`==1`) |
+| `payout_status`,`grace_period_end` | int | payout badge + grace countdown |
+| `market_type` | int | 0 = binary (this section), 1 = multi |
+| `a`,`b` | str | binary side labels |
+| `a_bets_sum`,`b_bets_sum` | int | probability bar + parimutuel preview |
+| `a_weight_sum`,`b_weight_sum` | int | winning-weight totals for parimutuel payout estimate |
+| `reserve_a`,`reserve_b`,`k` | int | CPMM price/odds, live preview, cancel preview |
+| `liquidity_sum` | int | liquidity line |
+| `bets_sum` | int | total bets |
+| `resolved_outcome`,`decision` | int/str | resolution result card |
+| `time`,`betting_expiration`,`result_expiration` | int (unix) | dates; `betting_expiration` gates the whole bet form & action buttons |
+| `resolution` | str | resolution rules text |
+| `oracle_fee`,`liquidity_fee`,`creator_fee`,`oracle_fixed_fee` | int (per-mille / precision-6) | params + `data-fee-permille` (sum of oracle+creator+liquidity) |
+| `time_penalty_value`,`time_penalty_type`,`penalty_curve_type` | int | late-bet penalty description |
+| `allow_early_resolution`,`allow_cancellation` | int | param flags |
+| `allow_instant_bet` | int/null | `null`→treat as 1 (legacy); `0`→force batch mode, warning shown |
+| `allow_batch` | int/null | `null`→treat as 1; enables batch/hidden mode checkboxes |
+| `committee` | int | committee id line |
+| `risk_score`,`risk_score_betting_blocked` | float/int | drives risk warning + mandatory confirm checkbox |
+| `tags` | array | jurisdiction ban check |
+| `outcomes` | array | multi only (`label`,`q`,`bets_sum`,`weight_sum`,`price`); ignored in binary |
+
+> The legacy `load-market` endpoint (POST `/api/load-market/`, request `{market_id}`) returns the same shape and is a drop-in fallback; enriched adds oracle reliability metrics, risk score, tags, and `my_leveraged_positions`. For binary it also computes `a_weight_sum`/`b_weight_sum` (SUM of `weight` over active `status=0` bets per side) that the client needs for parimutuel estimates.
+
+**VIZ thin-client note (suggestion):** Replace this aggregate with node queries — read the market object + AMM reserves + per-side active-bet weight sums from chain state, and the oracle account's reliability/insurance. `my_bets`/`my_leveraged_positions` become "positions owned by the signed-in account on this market." The public tables (`all_bets`, `all_liquidity`, `all_payouts`, `log`) map to iterating on-chain operation history for the market id.
+
+---
+
+### Screen section: Bet form (binary)
+
+Rendered only when `status==1 && auth && time_now() < betting_expiration`. The `.bet-form` element carries data-attributes the live preview reads without another request: `data-reserve-a/-b`, `data-k`, `data-risk-blocked`, `data-market-type`, `data-a-bets-sum`, `data-b-bets-sum`, `data-a-weight-sum`, `data-b-weight-sum`, `data-fee-permille`, `data-bets-sum`.
+
+**Controls**
+- **Risk warning + mandatory checkbox** — only if `risk_score_betting_blocked==1`: shows `bet.risk_warning` ("⚠ Warning: oracle insurance fund is insufficient!") + `bet.risk_detail` (score vs `min_risk_score_betting`), and a required checkbox `bet.risk_confirm` ("I understand all risks and want to continue"). Submitting without it shows `bet.risk_must_confirm` and aborts client-side.
+- **Side selector** — `` with options `m.a` (value 0) and `m.b` (value 1). Title = `bet.place_title` ("Place bet").
+- **Amount** — ` ` placeholder `bet.amount_placeholder` ("Amount VIZ").
+- **Mode checkboxes** (only if binary and `allow_batch==1`):
+ - `bet.mode_batch` ("Protect from front-running (batch)"), hint `bet.mode_batch_hint`. If `allow_instant_bet==0` this checkbox is force-checked+disabled and an `bet.instant_disabled_notice` note appears (instant is disabled → every order must be batched/committed).
+ - `bet.mode_hidden` ("Hide my bet (commit-reveal)"), hint `bet.mode_hidden_hint`.
+ - Default (neither checked) = instant.
+- **Live preview** (`.bet-preview`) — recomputed on every amount/side change, no server call. For binary it shows:
+ - Probability shift `prob_now% → prob_new%` (from `implied_probability` before/after the CPMM trade).
+ - `bet.preview_est_payout` ("≈ ** Ƶ** if wins") — a **parimutuel** estimate via `parimutuel_payout(tokens, amt, win_bets, a_bets+b_bets, win_weight+tokens, fee_permille)`.
+ - `bet.preview_share` ("your share: ") — CPMM tokens from `calculate_bet(side, amount, ra, rb, k)`.
+ - `bet.preview_parimutuel_note` — floating-payout disclaimer.
+ - `market_detail.price_odds` + `bet.preview_slippage` ("Slippage: X%") when slippage>0; if slippage>5% adds red `bet.preview_slippage_high` ("⚠ High slippage! Insufficient liquidity for this amount.").
+- **Submit** — `.place-bet-action` button `bet.place_button`; status line `.bet-loading` shows `bet.sending`/`bet.queued_success`/`bet.placed_success`/`bet.commit_reveal_success` or `common.error_prefix + message`.
+
+**Submit dispatch** (`place_bet_action`, app.js 2051): reads amount (VIZ string, sent as-is — server multiplies ×1000), side, `risk_confirm` (1 if the blocked checkbox is ticked). Then:
+- **hidden** checked → `pm_commit_reveal(...)` (two endpoints, below); no `place-bet` call.
+- **batch** checked → `place-bet` with `mode:1, min_tokens:0`; on success shows `bet.queued_success`.
+- otherwise → `place-bet` with `mode:0`.
+On success it calls `update_user_data()` and reloads the detail after ~1s.
+
+#### `place-bet` — place / queue a binary bet
+
+- **Method:** POST `/api/place-bet/`
+- **Fires:** instant submit (`mode:0`) or batch submit (`mode:1`) from the bet form.
+- **Auth/role:** authenticated user; sufficient `balance`.
+
+**Request**
+
+| field | type | meaning |
+|---|---|---|
+| `market_id` | int | target market |
+| `side` | int | 0 = A, 1 = B (validated; else "Invalid side") |
+| `amount` | number (VIZ) | bet size; server does `floor(amount*1000)` (must be >0) |
+| `risk_confirm` | int (0/1) | required 1 when the market's risk score < `min_risk_score_betting`, else rejected |
+| `mode` | int | 0 = instant CPMM (default), 1 = batch/uniform-price queue |
+| `min_tokens` | int | optional slippage floor (tokens); batch passes it, instant checks `tokens_received>=min_tokens` |
+
+**Response — instant (`mode:0`)**
+
+| field | type | UI use |
+|---|---|---|
+| `status` | bool | success flag |
+| `bet_id` | int | new bet id |
+| `tokens` | int | shown in `bet.placed_success` ("Bet placed! Tokens: …") |
+| `price` | int | per-token price (precision-6) — not displayed directly |
+| `time_penalty` | int | late-bet penalty applied (precision-6) |
+
+**Response — batch (`mode:1`)**
+
+| field | type | UI use |
+|---|---|---|
+| `status`,`queued` | bool | success; `bet.queued_success` shown |
+| `bet_id` | int | queued bet id (appears as status "Queued (batch)") |
+| `epoch` | int | batch epoch it joined |
+| `settle_after` | int (unix) | when the epoch settles |
+
+**Errors surfaced** (as `common.error_prefix + message`): "Market not found", "Market is not active", "Betting period expired", "Insufficient balance", risk-score message ("Market risk score too low … Send risk_confirm=1 …"), "Batch betting is not enabled for this market", "Amount below minimum for batch betting", "Instant betting is disabled for this market — submit with mode=1 …", "Trade too small", "Tokens received (X) below minimum (Y)".
+
+**VIZ thin-client note (suggestion):** an instant bet is a signed `place_bet`/AMM-buy operation (market_id, side, amount, optional min_tokens for slippage). Batch mode maps to submitting into a per-epoch batch/queue operation; the client would read the epoch length and `settle_after` from chain params.
+
+---
+
+### Screen section: Hidden bet (commit-reveal, binary only)
+
+Chosen via the **`bet.mode_hidden`** checkbox. The client hashes the bet locally, escrows funds with `commit-bet`, then immediately calls `reveal-bet`; the bet settles in the next batch epoch. Reveals are persisted to `localStorage` (`pm_pending_reveals`) and retried on next screen load so a dropped reveal doesn't forfeit the escrow.
+
+**Client hashing (`pm_commit_reveal`, app.js 2123):**
+- `salt` = 16 random bytes hex (`pm_random_salt`).
+- `preimage = market_id:user_id:side:-1:amount_milli:min_tokens:salt` (the `-1` is the binary outcome_index placeholder).
+- `commitment = SHA-256(preimage)` hex (`pm_sha256hex`) — **must exactly match the server's canonical preimage**, which is the load-bearing contract:
+ ```
+ market:user:side:-1:amount:min_tokens:salt
+ ```
+- `no_reveal_fee_permille` = `get_setting('commit_no_reveal_penalty_permille', 200)`; the server rejects the commit unless this equals the current chain value.
+
+Flow: show `bet.committing` → `commit-bet` → store pending reveal → show `bet.revealing` → `reveal-bet` → remove pending → show `bet.commit_reveal_success` ("Committed & revealed — settles at the next batch epoch."). On error: `common.error_prefix + message`.
+
+**Resilient re-reveal (`pm_process_pending_reveals`, app.js 2114):** on every authenticated detail load, iterates stored `{commit_id, side, amount, min_tokens, salt, reveal_deadline}`; drops entries past `reveal_deadline`, otherwise re-calls `reveal-bet` and removes on success.
+
+#### `commit-bet` — commit-reveal phase 1
+
+- **Method:** POST `/api/commit-bet/`
+- **Fires:** hidden-mode submit (first call).
+- **Auth/role:** authenticated; balance ≥ escrow.
+
+**Request**
+
+| field | type | meaning |
+|---|---|---|
+| `market_id` | int | target market (binary only) |
+| `commitment` | hex str (64 chars) | SHA-256 of the canonical preimage; server strips non-hex, requires length 64 |
+| `escrow_amount` | number (VIZ) | escrowed funds (`floor×1000`), ≥ `min_batch_bet` |
+| `no_reveal_fee_permille` | int | must equal chain `commit_no_reveal_penalty_permille` |
+
+**Response**
+
+| field | type | UI use |
+|---|---|---|
+| `status` | bool | success |
+| `commit_id` | int | stored for the reveal + pending-reveal record |
+| `reveal_deadline` | int (unix) | stored; pending reveals past it are dropped |
+| `no_reveal_fee_permille` | int | echoed chain value |
+
+**Errors:** "Invalid commitment hash", "Invalid escrow amount", "Market not found/not active", "Betting period expired", "Batch betting is not enabled for this market", "Commit-reveal supported for binary markets only", "Commit-reveal is disabled", "Escrow below minimum", "no_reveal_fee_permille must equal current chain value (X)", "Insufficient balance".
+
+#### `reveal-bet` — commit-reveal phase 2
+
+- **Method:** POST `/api/reveal-bet/`
+- **Fires:** immediately after `commit-bet`, and on retry from `pm_process_pending_reveals`.
+- **Auth/role:** authenticated; must own the commitment.
+
+**Request**
+
+| field | type | meaning |
+|---|---|---|
+| `commit_id` | int | commitment to reveal |
+| `side` | int | 0/1 |
+| `amount` | number (VIZ) | `floor×1000`; must be >0 and ≤ escrow (surplus refunded) |
+| `min_tokens` | int | slippage floor carried into the batch bet |
+| `salt` | str | the salt used in the preimage |
+
+**Response**
+
+| field | type | UI use |
+|---|---|---|
+| `status` | bool | success |
+| `bet_id` | int | resulting queued bet |
+| `epoch` | int | batch epoch |
+| `settle_after` | int (unix) | epoch settlement time |
+
+**Errors:** "Commitment not found", "Not your commitment", "Commitment already revealed or forfeited", "Reveal deadline passed", "Invalid side/amount", "Amount exceeds escrow", "Reveal does not match commitment", "Market no longer accepting bets".
+
+**VIZ thin-client note (suggestion):** implement as a two-op flow — a `commit` op carrying the SHA-256 commitment + escrow, then a `reveal` op with `(side, amount, min_tokens, salt)`. Keep the exact preimage `market:user:side:-1:amount:min_tokens:salt` and persist the salt/deadline client-side for retry. The no-reveal penalty per-mille must be signed as the current consensus value.
+
+---
+
+### Screen section: "My bets" table
+
+Rendered when `auth && data.my_bets.length>0`, heading `bet.my_bets` ("My bets"). Columns: `bet.outcome_header` (Outcome), `bet.amount_header` (Amount), `bet.tokens_header` (Tokens), `bet.status_header` (Status), `bet.payout_if_win_header` ("If win (≈)"), plus an actions column.
+
+Per-row from each `my_bets` entry:
+- **Outcome** — binary: `m.a`/`m.b` by `b.side`; multi: `outcomes[b.outcome_index].label`.
+- **Amount** — `b.amount` (rendered VIZ).
+- **Tokens** — `b.weight`.
+- **Status** — indexed by `b.status`: `0`=`bet.status_active` (Active), `1`=`bet.status_canceled` (Canceled), `2`=`bet.status_refund` (Refund), `3`=`bet.status_resolved` (Resolved), `4`=`bet.status_transferred` (Transferred), `5`&`6`=`bet.status_queued` ("Queued (batch)") — status 5 = batch-queued, 6 = revealed-pending.
+- **If win (≈)** — resolved (`status==3 && resolved_amount>0`): exact `b.resolved_amount` Ƶ. Active (`status==0`): `≈` parimutuel estimate from `parimutuel_estimate(m, side/outcome, b.weight, b.amount, false)`. Otherwise `—`.
+- **Actions** — only when `b.status==0 && m.status==1 && time_now() Multi-outcome markets use `.cancel-bet-multi-action` → modal `bet.cancel_multi_title` → `cancel-bet-multi` `{bet_id}`; return is server-computed via LMSR (`bet.cancel_lmsr_note`). Documented here for completeness; the field-level contract below is for the binary `cancel-bet`.
+
+#### `cancel-bet` — cancel an active binary bet
+
+- **Method:** POST `/api/cancel-bet/`
+- **Fires:** cancel confirmation.
+- **Auth/role:** authenticated; must own the bet; market active and within betting window.
+
+**Request**
+
+| field | type | meaning |
+|---|---|---|
+| `bet_id` | int | bet to cancel |
+| `min_return` | int (milli-VIZ) | slippage floor; server rejects if `amount_returned` / `outcome_b_` inputs (placeholder `market.outcome_label_lang` = `"Outcome %%N%% (%%LANG%%)"`). In multi mode the A/B per-language inputs are omitted — outcome labels come from the flat `.multi-outcomes-list` inputs instead.
+
+#### Outcome list editor (multi only)
+
+The `.multi-outcomes` block contains `.multi-outcomes-list` (list of `input[name="outcome_"]`), Add/Remove buttons, and a `.multi-info` note. Initial render seeds it (app.js 858-865) and shows `market.initial_odds_info` = `"Initial odds: 1/%%COUNT%% = %%PERCENT%%% per outcome"` for COUNT=3, 33.3%.
+
+- **`add_outcome()`** (724-730) — button label `market.add_outcome` = **"➕ Add outcome"** (class `.add-outcome-btn`). Appends `input[name="outcome_"]` with placeholder `market.outcome_n_placeholder` = `"Outcome %%N%%"`. If already at max (`lmsr_max_outcomes`, default **10**) it aborts with warning `market.max_outcomes` = `"Maximum %%MAX%% outcomes"`.
+- **`remove_outcome()`** (731-738) — button label `market.remove_outcome` = **"➖ Remove"** (class `.remove-outcome-btn`). Removes the last input. If at min (`lmsr_min_outcomes`, default **3**) it aborts with warning `market.min_outcomes` = `"Minimum %%MIN%% outcomes"`.
+- **`update_multi_odds_info()`** (739-748) — recomputes `.multi-info` to `1/COUNT = (100/COUNT)%` and disables the Add button at max / Remove button at min (via `.disabled` class).
+
+`lmsr_min_outcomes` / `lmsr_max_outcomes` come from client `get_setting(...)`; the server re-validates them.
+
+**`get_multi_outcomes()`** (759-765) collects all non-empty `.multi-outcomes-list input` values into a string array — this becomes the `outcomes` payload field.
+
+#### Submit — `create_market()`
+
+`create_market()` (app.js 1392+) reads `market_type` then validates shared fields. **Note (client quirk):** it still validates the hidden `a` and `b` inputs (1408-1423) even in multi mode; the multi template auto-populates a legacy hidden `q`/A/B so submission proceeds. The POST body is chosen by `market_type` (app.js 1484-1499): multi sends `outcomes: get_multi_outcomes()` and `market_type: 1` and omits `a`/`b` and `allow_instant_bet`.
+
+VIZ thin-client note (suggestion): outcome labels are just an ordered string array; index positions (`outcome_index`) are 0-based and become the canonical outcome IDs used by every later call. A thin client should broadcast a single "create multi market" op carrying `q`, `resolution`, the ordered `outcomes[]`, liquidity/fee params and expirations, and read back the assigned `market_id` + `outcome_count`.
+
+#### `create-market-multi`
+
+- **Method:** POST `/api/create-market-multi/`
+- **Fires when:** user submits the Create form with type = Onix Multi.
+- **Auth/role required:** authenticated; `user_arr['create_market']==1`; must not be creator-banned.
+
+**Request fields:**
+
+| Field | Type | Meaning |
+|---|---|---|
+| `q` | string | Market question/title (legacy field, still required) |
+| `outcomes` | string[] | Ordered outcome labels; server assigns `outcome_index` = array index. Count must be within `lmsr_min_outcomes`..`lmsr_max_outcomes`; each label trimmed & non-empty |
+| `resolution` | string | Resolution rules text |
+| `liquidity` | number (VIZ) | LP subsidy; ×1000 server-side; **min 100 VIZ** (100000 milli) |
+| `liquidity_fee` | int (permille) | LP fee taken from losers |
+| `oracle_id` | int | Oracle user id (must have `oracle=1`) |
+| `oracle_fee` | int (permille) | Must be ≥ oracle's minimum |
+| `oracle_fixed_fee` | int | Passed but server overrides from oracle record |
+| `creator_fee` | int (permille) | Clamped 0..100 |
+| `betting_expiration` | int (unix) | Clamped ≤ `result_expiration` |
+| `result_expiration` | int (unix) | Result deadline |
+| `allow_early_resolution` | 0/1 | |
+| `allow_cancellation` | 0/1 | Enables `cancel-bet-multi` later |
+| `allow_instant_bet` | 0/1 | **Ignored** — multi has no batch path, server forces `=1` (api.php 2787-2789) |
+| `time_penalty_type` | int (0/1) | |
+| `time_penalty_value` | int | |
+| `penalty_curve_type` | int (0/1) | |
+| `committee_id` | int | Optional dispute committee |
+
+**Response fields:**
+
+| Field | Type | UI use |
+|---|---|---|
+| `status` | bool | Success flag |
+| `error` | string[] | Array of validation errors shown to user (e.g. "Minimum N outcomes required", "Insufficient balance (need X VIZ)", "Liquidity too low for N outcomes (min b=…)") |
+| `market_id` | int | New market id (client navigates to detail) |
+| `market_type` | int | `1` |
+| `lmsr_b` | int | Computed depth parameter |
+| `outcome_count` | int | N |
+
+**Errors/edge states:** "No permission to create markets"; "Creator is banned…"; "Outcomes must be an array"; min/max outcome count; empty outcome label; "Minimum liquidity is 100 VIZ"; "Oracle not found"; "Oracle fee must be >= oracle minimum"; "Insufficient balance…"; "Liquidity too low for N outcomes (min b=…)"; self-oracle "Insufficient oracle insurance". **Contrast with binary `create-market`:** binary sends `a`/`b` labels (+ per-language A/B) and honours `allow_instant_bet`/batch; multi sends `outcomes[]`, ignores instant-bet, and additionally enforces the LMSR `min_b` floor per outcome count.
+
+---
+
+### Screen: Market detail — multi rendering (`load_market_detail`)
+
+`load_market_detail(market_id)` (app.js 1555+) branches on `is_multi = parseInt(m.market_type)==1`.
+
+**Header badge** (1262-1264): multi shows a purple badge `market.type_multi_badge` = `"Onix Multi (%%COUNT%% outcomes)"` (COUNT=`outcome_count`).
+
+**Outcomes bar / detail list** (1266-1279 list card, 1592-1607 detail): iterates `m.outcomes[]`, each colored from a 10-color palette, showing:
+- `answer-prob` = `(o.price/10000).toFixed(1)`% — the LMSR implied probability.
+- outcome `o.label`.
+- detail card adds `market_detail.multi_bets_detail` = `"(bets: %%AMOUNT%% Ƶ, q: %%Q%%)"` using `o.bets_sum` and `o.q/1000`.
+- if resolved (`m.status==3` and `resolved_outcome==oi`) a ✅ winner badge is appended.
+- a depth line: `market.depth_line_full` = `"Depth (b): %%DEPTH%% | Subsidy: %%SUBSIDY%% Ƶ | Liquidity: %%LIQUIDITY%% Ƶ"` from `lmsr_b`, `lmsr_subsidy`, `liquidity_sum`.
+
+Binary instead renders the A/B bets-bar, per-side price/odds via `calculate_bet`, and `market.liquidity_label`.
+
+**Resolution info** (1625-1636): if resolved, the winner name is `m.outcomes[resolved_outcome].label` for multi (vs `m.a`/`m.b` for binary), shown under `resolution.result_label` = "Result:".
+
+The market params card, fees, penalties, creation/expiration timestamps (1638-1699) render identically for both types.
+
+---
+
+### Screen: Place a bet (multi bet form)
+
+Rendered inside market detail when `m.status==1`, user authed, and before `betting_expiration` (app.js 1702-1739). The bet form `div.bet-form` carries multi state in data-attributes (1710): `data-market-type`, `data-lmsr-b`, `data-bets-sum`, and CSV `data-oc-q` / `data-oc-bets` / `data-oc-weight` (per-outcome q, bets_sum, weight_sum) so the client can price bets locally.
+
+- Title `bet.place_title` = "Place bet".
+- **Outcome selector** (1716-1721, multi): `select[name="bet_outcome"]` with one `option value=""` per outcome label. (Binary uses `select[name="bet_side"]` with A/B.)
+- Amount input `input[name="bet_amount"]` (placeholder `bet.amount_placeholder` = "Amount VIZ").
+- **No batch/hidden mode controls** for multi — the opt-in front-running toggles are rendered only for binary (`!is_multi`, 1726).
+- Live preview `.bet-preview`.
+
+**Live preview** (app.js 1949-1971, multi branch): on input change it LMSR-prices the amount client-side via `lmsr_tokens_for_amount(q, lmsr_b, outcome_i, amt)`, then estimates a parimutuel payout via `parimutuel_payout(...)`. It shows:
+- `bet.preview_est_payout` = `"≈ %%PAYOUT%% Ƶ if %%SIDE%% wins"` (SIDE = selected outcome label).
+- `bet.preview_share` = `"your share: %%TOKENS%%"` (LMSR tokens).
+- `bet.preview_parimutuel_note` = `"≈ Estimate. Winners split the losers' pool in proportion to their share — your final payout depends on all bets placed by the time the market resolves."` — this is the **parimutuel disclaimer** shown for both types, emphasising the estimate is non-final.
+
+**Submit** — `place_bet_action` (app.js 2054-2092). It reads `mtype = form.data('market-type')`; for multi (2069-2071) it reads `outcome_index` from `select[name=bet_outcome]` and calls `api_post('place-bet-multi', {market_id, outcome_index, amount, risk_confirm})`. (Binary branch instead reads `side` and may route to hidden/batch flows.) If a risk-score warning is shown, the user must tick `.risk-confirm-check` or the form aborts with `bet.risk_must_confirm`. On success the detail view reloads after ~1s.
+
+#### `place-bet-multi`
+
+- **Method:** POST `/api/place-bet-multi/`
+- **Fires when:** user clicks "Place bet" (`bet.place_button`) on a multi market.
+- **Auth/role required:** authenticated.
+
+**Request fields:**
+
+| Field | Type | Meaning |
+|---|---|---|
+| `market_id` | int | Target market (must be `market_type=1`, `status=1`) |
+| `outcome_index` | int | Chosen outcome; must be `0 <= idx < outcome_count` |
+| `amount` | number (VIZ) | Spend; ×1000 server-side; must be > 0 and ≤ balance |
+| `min_tokens` | int | Slippage floor (tokens). Client currently does **not** send this (defaults 0). |
+| `risk_confirm` | 0/1 | Sent by client when risk warning present (not consumed by this endpoint's core logic) |
+
+**Response fields:**
+
+| Field | Type | UI use |
+|---|---|---|
+| `status` | bool | success |
+| `bet_id` | int | new bet id |
+| `tokens` | int | LMSR tokens acquired (`weight`) |
+| `cost` | int | actual VIZ cost (may be < `amount`) |
+| `refund` | int | unspent amount (excess capped) |
+| `prices` | number[] | updated per-outcome LMSR prices |
+
+*(Client success handler shows `bet.canceled_success`-style success and reloads; note the success toast in the shared handler reads `data.returned` for cancels — for the bet it simply reloads the detail.)*
+
+**Errors:** "Market not found"; "Not a multi-outcome market"; "Market is not active"; "Invalid outcome index"; "Amount must be positive"; "Insufficient balance"; "Instant betting is disabled for this market" (if `allow_instant_bet=0`, which blocks all multi betting since there is no batch path); "Bet amount too small for any tokens"; "Slippage exceeded: would get X tokens, min=…". **Contrast with binary `place-bet`:** binary takes `side` (0/1) and supports batch/hidden (commit-reveal) modes; multi takes `outcome_index`, is always instant, LMSR-prices the cost, and returns `tokens`/`cost`/`refund`/`prices`.
+
+VIZ thin-client note (suggestion): the thin client would sign a "buy outcome tokens" op with `{market_id, outcome_index, amount, min_tokens}` and read back tokens/cost/refund and the new price vector to update the UI. `min_tokens` is the on-chain slippage guard — the current web client leaves it 0, but the node client should expose it.
+
+---
+
+### Action: Cancel a multi bet
+
+In the **My bets** table (app.js 1757-1790), for an active bet on an active market before expiration, multi bets render a `.cancel-bet-multi-action` button (1781) carrying `data-bet`, `data-amount`, `data-weight`, `data-outcome`, `data-market`. (Binary uses `.cancel-bet-action` with reserve/k data.) Button label `bet.cancel_button` = "Cancel".
+
+**`cancel_bet_multi_action(el)`** (app.js 2213-2229) opens a confirm modal:
+- Title `bet.cancel_multi_title` = **"Confirm bet cancellation (Onix Multi)"**.
+- `bet.cancel_multi_preview` = `"Bet: %%AMOUNT%% Ƶ, Tokens: %%TOKENS%%"`.
+- `bet.cancel_lmsr_note` = **"Return is calculated by server via LMSR. The return amount may differ from the original bet."** — the LMSR-return disclaimer (distinct from the binary cancel note).
+- Confirm button `bet.cancel_confirm_button` = "Confirm cancellation" (`.cancel-multi-confirm-yes`); dismiss `bet.cancel_button_label` = "Cancel" (`.cancel-multi-confirm-no`).
+
+**`cancel_bet_multi_confirmed(el)`** (app.js 2231-2246) calls `api_post('cancel-bet-multi', {bet_id})` (note: **no `min_return` sent** by the client, defaults 0). On success shows `bet.canceled_success` = `"Bet canceled. Returned: %%AMOUNT%% Ƶ"` using `data.returned`, then reloads. **(Client quirk:** the endpoint returns `return_amount`, not `returned`, so the toast AMOUNT is currently blank — see below.)
+
+#### `cancel-bet-multi`
+
+- **Method:** POST `/api/cancel-bet-multi/`
+- **Fires when:** user confirms cancellation of a multi bet.
+- **Auth/role required:** authenticated; must own the bet.
+
+**Request fields:**
+
+| Field | Type | Meaning |
+|---|---|---|
+| `bet_id` | int | Bet to cancel (must be owner's, `status=0`) |
+| `min_return` | int | Slippage floor on VIZ returned. Client does **not** send it (0). |
+
+**Response fields:**
+
+| Field | Type | UI use |
+|---|---|---|
+| `status` | bool | success |
+| `return_amount` | int | VIZ returned (LMSR sell). *UI toast reads `data.returned` — mismatch, shows empty* |
+| `original_amount` | int | original bet amount |
+| `slippage` | int | `original_amount - return_amount` |
+
+**Errors:** "Bet not found"; "Not your bet"; "Bet is not active"; "Market not found"; "Not a multi-outcome market"; "Market is not active"; "Cancellation not allowed for this market" (needs `allow_cancellation=1`); "Slippage exceeded: would get X, min=…". **Contrast with binary `cancel-bet`:** binary reverses the CPMM reserves and the client reads `data.returned`; multi does an LMSR sell (`lmsr_sell_return`) and returns `return_amount`/`slippage`. Cancellation returns can be **less than the original bet** (LMSR price impact) — hence the explicit note.
+
+VIZ thin-client note (suggestion): a "sell/cancel outcome position" op keyed by `bet_id` with a `min_return` guard; read back the actual VIZ returned. The node client should send `min_return` and read the correct return field.
+
+---
+
+### Action: Add / Withdraw liquidity (multi)
+
+**Client status:** the market-detail **Add liquidity** form (app.js 1747-1755) is rendered for any active market where the user has `add_liquidity` permission, regardless of type. Its handler **`add_liquidity_action`** (2290-2303) always calls the **binary** `add-liquidity` endpoint — it does **not** branch on `market_type`. There is **no** UI caller for `add-liquidity-multi` or `withdraw-liquidity-multi` in `app.js`. These two endpoints exist server-side and are the correct ones for multi markets, but the current web client never invokes them. **This is a known gap the VIZ thin client must close** (call the `-multi` variants when `market_type==1`).
+
+Form labels: title `liquidity.add_title` = "Add liquidity", input placeholder `liquidity.amount_placeholder` = "Amount VIZ", button `liquidity.add_button` = "Add", success `liquidity.added_success` = "Liquidity added!".
+
+#### `add-liquidity-multi`
+
+- **Method:** POST `/api/add-liquidity-multi/`
+- **Fires when:** (server-supported) LP adds subsidy to a multi market. *No current app.js caller.*
+- **Auth/role required:** authenticated (server does not re-check `add_liquidity` in this branch, unlike the UI gate).
+
+**Request fields:**
+
+| Field | Type | Meaning |
+|---|---|---|
+| `market_id` | int | Target multi market (`market_type=1`, `status=1`) |
+| `amount` | number (VIZ) | Subsidy added; ×1000; > 0 and ≤ balance |
+
+**Response fields:**
+
+| Field | Type | UI use |
+|---|---|---|
+| `status` | bool | success |
+| `liquidity_id` | int | new LP record id (needed later for withdraw) |
+| `delta_b` | int | increase in `lmsr_b` from this deposit |
+| `new_b` | int | resulting `lmsr_b` |
+
+**Errors:** "Market not found"; "Not a multi-outcome market"; "Market is not active"; "Amount must be positive"; "Insufficient balance". **Contrast with binary `add-liquidity`:** binary adds symmetric CPMM reserves; multi increases the LMSR `b` depth (`delta_b`) and `lmsr_subsidy`.
+
+#### `withdraw-liquidity-multi`
+
+- **Method:** POST `/api/withdraw-liquidity-multi/`
+- **Fires when:** (server-supported) LP withdraws subsidy from a multi market. *No current app.js caller.*
+- **Auth/role required:** authenticated; must own the liquidity record.
+
+**Request fields:**
+
+| Field | Type | Meaning |
+|---|---|---|
+| `liquidity_id` | int | LP record to withdraw (owner's, `status=0`) |
+
+**Response fields:**
+
+| Field | Type | UI use |
+|---|---|---|
+| `status` | bool | success |
+| `return_amount` | int | principal + fee share returned |
+| `principal` | int | original LP amount |
+| `fee_share` | int | time-discounted fee share earned |
+
+**Errors:** "Liquidity record not found"; "Not your liquidity"; "Liquidity is not active"; "Market not found"; "Not a multi-outcome market"; "Market is not active"; "Cannot withdraw: would drop b below minimum (X VIZ)" (LMSR `min_b` floor). **Contrast with binary withdraw:** the multi floor is on the LMSR `b` (removing `lmsr_b_share` must keep `b >= lmsr_min_b_parameter`), and early withdrawers get a time-discounted fee share.
+
+VIZ thin-client note (suggestion): expose two ops keyed on `market_id`/`liquidity_id`. Add returns `liquidity_id` + `delta_b`/`new_b`; withdraw enforces the `min_b` floor and returns principal + fee_share. The node client must select the `-multi` variant based on `market_type` — the reference web client's shared button wrongly hits the binary path.
+
+---
+
+### Screen: Oracle resolution (multi)
+
+The **Submit result** form (app.js 1911-1928) is shown when the authed user is the market's oracle, the market is active/closed (`status` 1 or 2) and not yet paid out (`payout_status==0`). The form `div.resolve-form` carries `data-market-type`.
+
+- Title `resolution.submit_title` = "Submit result (oracle)".
+- **Winning-outcome selector** `select[name="resolve_outcome"]`: for multi (1916-1919) one `option value=""` per `m.outcomes[oi].label`; binary shows A/B.
+- Justification input `input[name="resolve_decision"]` (placeholder `resolution.justification` = "Justification").
+- Confirm button `resolution.confirm_button` = "Confirm result".
+
+**`resolve_market_action(el)`** (app.js 2755-2770): reads `mtype`, `outcome` (selected value), `decision`; picks `endpoint = mtype==1 ? 'resolve-market-multi' : 'resolve-market'`; calls `api_post(endpoint, {market_id, outcome, decision})`; on success shows `resolution.submitted_success` = "Result submitted!" and reloads.
+
+**Client quirk (important for the node devs):** the client sends the winning index as **`outcome`**, but the server's `resolve-market-multi` reads it from **`winning_outcome`** (api.php 3155). As written these keys don't match — the thin client must send `winning_outcome` (and may also send `decision_url`).
+
+#### `resolve-market-multi`
+
+- **Method:** POST `/api/resolve-market-multi/`
+- **Fires when:** oracle confirms the winning outcome.
+- **Auth/role required:** authenticated; `market.oracle == user.id`; oracle not banned.
+
+**Request fields:**
+
+| Field | Type | Meaning |
+|---|---|---|
+| `market_id` | int | Multi market to resolve (`market_type=1`) |
+| `winning_outcome` | int | Winning `outcome_index`; must be `0 <= x < outcome_count`. *(Web client mislabels this as `outcome`.)* |
+| `decision` | string | Oracle justification |
+| `decision_url` | string | Optional evidence URL (client does not send it) |
+
+**Response fields:**
+
+| Field | Type | UI use |
+|---|---|---|
+| `status` | bool | success → toast + reload |
+| `winning_outcome` | int | echoes winner index |
+| `losers_sum` | int | total staked on losing outcomes |
+| `oracle_fee` | int | oracle fee taken from losers |
+| `creator_fee` | int | creator fee taken from losers |
+| `winners_pool` | int | net pool distributed to winners |
+
+**Errors:** "Market not found"; "Not a multi-outcome market"; "You are not the oracle"; "Oracle is banned"; "Market cannot be resolved in current status"; "Invalid winning outcome"; "Betting period not expired and early resolution not allowed" (when `status=1`, `allow_early_resolution!=1`, before `betting_expiration`). **Contrast with binary `resolve-market`:** binary resolves on `side` 0/1; multi resolves on `winning_outcome` index. Settlement is **parimutuel** in both — losers' stake (minus oracle/creator/LP fees, plus forfeit pool) is split among winners in proportion to their **tokens (`weight`)**, minus each winner's time penalty; if no one bet the winning outcome, `winners_pool` flows to LPs. Resolution flips `market.status=3`, sets `resolved_outcome`, and queues bettor notifications (dispute grace window).
+
+VIZ thin-client note (suggestion): the resolution op is signed by the oracle account and carries `{market_id, winning_outcome, decision, decision_url}`. Ensure the key is `winning_outcome` (the reference web UI sends `outcome`, which the multi endpoint ignores) and surface the returned `losers_sum` / fee / `winners_pool` figures for the resolution receipt.
+## 6. Creating a market (creator role)
+
+This section documents the **Create** tab: the full market-creation form, every field in the order it is rendered, the supporting picker/lookup calls (`load-oracles`, `load-oracle-profile`, `load-committees`), and the `create-market` / `create-market-multi` submission contracts. All field names below are taken verbatim from `app.js` (`select_tab` create branch, lines 814–942; `create_market`, lines 1392–1525; helpers 616–810) and `module/api.php` (lines 437–776, 2728–2896). UI label text is quoted from `i18n/en.json`.
+
+---
+
+### Screen: Create market (Create tab)
+
+**Route:** `#tab/create` (rendered by `select_tab()` when `path[1]=='create'`). On entry the hash is normalised via `navigate_replace('#tab/'+name)`.
+
+**Title:** `market.create_title` → **"Create prediction market"**.
+
+**Access gating (rendered before any form):**
+- If the user is **not authenticated** (`!auth`): only a paragraph `market.not_logged_in` → **"You are not logged in."** is shown. No form, no lookups fire.
+- If authenticated but `user.create_market != 1`: only a paragraph `market.cannot_create` → **"You cannot create markets."** is shown. No form, no lookups fire.
+- If authenticated **and** `user.create_market == 1`: the full form (`.create-market`) is rendered, then two GET lookups fire immediately (`load-oracles`, `load-committees`) to populate the oracle and committee dropdowns.
+
+**Required role/auth:** authenticated session **with creator role** (`user.create_market == 1`). The server re-checks this (`create-market` throws `No permission to create markets` if `create_market != 1`) and additionally blocks banned creators (`creator_banned == 1` and either `creator_ban_until == 0` or still in the future → throws `Creator is banned from creating markets`). The banned state is **not** surfaced client-side before submit; it appears only as a thrown error on submit.
+
+#### Form fields, in render order
+
+All inputs live inside `.create-market`. Fields marked *(hidden)* are populated programmatically, not shown to the user.
+
+| # | Field (name attr) | Control / i18n label | Meaning & behaviour |
+|---|---|---|---|
+| 1 | `market_type` | ``; options `market.type_binary` → **"Onix Binary (A/B)"** (value `0`, default) and `market.type_multi` → **"Onix Multi (multiple outcomes)"** (value `1`) | `onchange="toggle_market_type();render_lang_fields()"`. Binary shows `.binary-outcomes` + `.only-binary`; multi shows `.multi-outcomes` and hides the instant-bet toggle. **Note:** the caption reuses `market.penalty_type_label` (a label-key mismatch in the prototype). |
+| 2 | `category` | ``, label `market.category_label` → **"Category"** | `onchange="render_subcategory_options()"`. Options built from `categories_cache.categories` (each rendered as `icon + localized label`, value = category `id`). First option is `—` (empty). |
+| 3 | `subcategory` | ``, label `market.subcategory_label` → **"Subcategory"** | Populated by `render_subcategory_options()` from the selected category's `subcategories[]` (value = subcategory `id`, label via `resolve_i18n`). First option `—`. |
+| 4 | `tags` | ` `, label `market.tags_label` → **"Tags"**, placeholder `market.tags_placeholder` → **"Add tags (comma separated)"** | Free text; server runs `validate_tags`. |
+| 5 | `default_lang` | ``, label `market.default_lang_label` → **"Default language"**; options `English` (`en`) / `Русский` (`ru`) | Chooses which language's title/outcome/rules become the legacy `q`/`a`/`b`/`resolution` fallback server-side. |
+| 6 | *(per-language blocks)* `.lang-fields-container` | Rendered by `render_lang_fields()` | One `.lang-block` per language in `create_form_langs` (starts `['en']`). Each block has: `title_` (`market.title_label` → **"Title (LANG)"**), `description_` textarea (`market.description_label` → **"Description (LANG)"**), `resolution_rules_` textarea (`market.resolution_rules_label` → **"Resolution rules (LANG)"**), and — **only for binary** (`market_type==0`) — `outcome_a_` and `outcome_b_` (`market.outcome_label_lang` → **"Outcome A/B (LANG)"**). Each block after the first shows a `✕` remove button (`remove-lang-btn`, `data-lang`). |
+| 7 | `add-lang-btn` | `` `market.add_language` → **"➕ Add language"** | Calls `add_create_lang()` → adds next unused of `['en','ru']`, re-renders lang blocks. Cap is those two languages only. |
+| 8 | `q` *(hidden)* | — | Legacy question. Rendered as hidden, empty. |
+| 9 | `a`, `b` *(hidden)* | — | Legacy binary outcome labels. Hidden, empty. |
+| 10 | `resolution` *(hidden)* | — | Legacy resolution text. Hidden, empty. |
+| 11 | `.multi-outcomes` block (hidden unless multi) | — | Contains `.multi-outcomes-list` with N ` ` (`market.outcome_n_placeholder` → **"Outcome N"**), initially 3 (`outcome_0..2`). Buttons: `add-outcome-btn` (`market.add_outcome`) and `remove-outcome-btn` (`market.remove_outcome`, starts disabled). Note line `.multi-info` shows `market.initial_odds_info` → **"Initial odds: 1/COUNT = PERCENT% per outcome"**. |
+| 12 | `liquidity` | ` `, placeholder `market.liquidity_placeholder` → **"Initial liquidity (VIZ)"**, hint `market.liquidity_min_hint` → **"(minimum amount 100 VIZ)"** | Initial liquidity in VIZ. |
+| 13 | `resolution_url` | ` `, label `market.resolution_url_label` → **"Resolution URL"**, placeholder `https://...` | Optional evidence source URL. **Sent only implicitly** — see the payload note: `create_market()` does **not** include this key in its request body (it is defined in the server handler but not sent by the current client). |
+| 14 | `jurisdiction_relevant` | ` `, label `market.jurisdiction_relevant_label` → **"Relevant jurisdictions"**, placeholder `US, GB, DE...` | Country list. **Not sent** by `create_market()`. |
+| 15 | `jurisdiction_banned` | ` `, label `market.jurisdiction_banned_label` → **"Restricted jurisdictions"**, placeholder `CN, KP...` | Country list. **Not sent** by `create_market()`. |
+| 16 | `oracle` | ``, label `market.oracle_label` → **"Oracle:"**; first option value `0` = `market.oracle_not_selected` → **"Not selected"** | `onchange="select_oracle()"`. Options loaded via `load-oracles` (see below). Each option carries `rel=""`. |
+| 17 | `liquidity_fee` *(hidden)* | shown as `market.lp_reward` → **"LP reward: 0.005%"** | Hidden input, hard-coded `value="5"` (‰). |
+| 18 | `oracle_fee` *(hidden)* | shown as `market.oracle_reward` → **"Oracle reward:"** + `` | Hidden input default `value="5"`. Updated by `select_oracle()` to the chosen oracle's minimum fee (`rel`), and the span shows `fee/price_precision_ratio + "%"`. |
+| 19 | `creator_fee` | ` ` min 0 max 100 step 1, `value="5"`, placeholder `market.creator_fee_placeholder` → **"Creator reward (‰, 0-100)"**, hint `market.creator_fee_hint` → **"(creator receives X‰ from losing bets)"** | Creator reward in ‰ (0–100). |
+| 20 | `oracle_fixed_fee` | ` ` step 0.001 min 0 `value="0"`, placeholder `market.oracle_fixed_fee_placeholder` → **"Fixed oracle reward (VIZ)"**, hint `market.oracle_fixed_fee_hint` → **"(one-time payment when market is accepted)"** | **Sent** in the payload, but the server **overrides** it with the oracle's own `oracle_fixed_fee` from the oracle profile — the client value is informational only. |
+| 21 | `betting_expiration` | ` `, label `market.betting_expiration_label` → **"Betting deadline:"**, hint `market.betting_expiration_hint` → **"(local time)"** | Deadline for accepting bets. Converted to a unix seconds integer on submit (`new Date(v).getTime()/1000|0`). |
+| 22 | `result_expiration` | ` `, label `market.result_expiration_label` → **"Result submission deadline:"**, hint `market.result_expiration_hint` → **"(local time)"** | Deadline for the oracle to submit a result. Converted to unix seconds on submit. |
+| 23 | `timezone_offset` *(hidden)* | shown via `market.timezone_label` → **"Local timezone:"** + `.local-timezone` | Hidden input set to `timezone_offset()`. **Not sent** by `create_market()` (commented out both client- and server-side). |
+| 24 | `allow_early_resolution` | checkbox, checked by default, label `market.early_resolution_label` → **"Early resolution"**, hint `market.early_resolution_hint` → **"(if result is known earlier)"** | Allows resolving before `result_expiration`. |
+| 25 | `allow_cancellation` | checkbox, checked by default, label `market.allow_cancel_label` → **"Allow event cancellation"**, hint `market.allow_cancel_hint` | Allows cancel/refund on event postponement/cancellation. |
+| 26 | `allow_instant_bet` | checkbox (`.only-binary`, hidden for multi), checked by default, label `market.allow_instant_bet_label` → **"Allow instant betting"**, hint `market.allow_instant_bet_hint` (binary only; uncheck forces batch/commit-reveal, anti-MEV) | Binary only. For multi the server forces this to `1` regardless. |
+| 27 | `time_penalty_type` | ``, label `market.penalty_type_label` → **"Late bet penalty type:"**; options `market.penalty_type_percent` → **"% of duration"** (value `1`, default) and `market.penalty_type_fixed` → **"Fixed seconds"** (value `0`) | Late-bet penalty mode. |
+| 28 | `time_penalty_value` | ` ` `value="10"`, placeholder `market.penalty_value_placeholder` → **"Penalty value (% or sec.)"**, hint `market.penalty_value_hint` → **"(penalty quadratic/linear from 0% to X%)"** | Penalty magnitude. |
+| 29 | `committee_id` | ``, label `market.committee_label` → **"Dispute judge:"**; first option value `0` = `market.committee_not_selected` → **"Not selected"** | Dispute committee/DAO. Options loaded via `load-committees`. |
+| 30 | `create-market-action` | `` `market.create_button` → **"Create market"** | Submit button. Click → `create_market()` (button gets `disabled` while in flight). |
+| — | `.loading` | `market.creating_wait` → **"Please wait…"** | Status line reused for validation errors, progress, and success. |
+| — | `.info` | `market.create_info_note` → note about oracle non-acceptance refunding liquidity minus penalty | Static informational note. |
+
+#### Multi-outcome outcome management (client helpers)
+
+- **`add-outcome-btn`** → `add_outcome()`: appends ` `. Blocked at `lmsr_max_outcomes` (setting, default 10) with warning `market.max_outcomes` → **"Maximum MAX outcomes"**.
+- **`remove-outcome-btn`** → `remove_outcome()`: removes the last outcome input. Blocked at `lmsr_min_outcomes` (setting, default 3) with warning `market.min_outcomes` → **"Minimum MIN outcomes"**.
+- `update_multi_odds_info()` recomputes the odds hint (`100/count`%) and disables the add/remove buttons at the caps.
+- `get_multi_outcomes()` collects non-empty outcome input values into an array (sent as `outcomes`).
+
+#### Client-side validation (in `create_market()`, before the request)
+
+Each required field, when empty, focuses the field, re-enables the submit button, and writes an error into `.loading`:
+- `q`, `a`, `b`, `liquidity`, `resolution`, `betting_expiration`, `result_expiration` empty → `form.fill_form` → **"Fill out the form"**.
+- `oracle` still `0` → `form.select_oracle` → **"Select an oracle"**.
+
+> **Prototype caveat (must be preserved or fixed by the new client):** the hidden `q`/`a`/`b`/`resolution` inputs are rendered **empty and never auto-populated** from the per-language title/outcome/rules fields in the code paths read. As written, `create_market()`'s empty-check on `q`/`a`/`b`/`resolution` will fail unless those hidden inputs are filled elsewhere. The new thin client should map: default-language title → `q`, outcome A/B labels → `a`/`b`, resolution rules → `resolution` (the server applies exactly this fallback from `title_` / `outcome_*_` / `resolution_rules_` when `q`/`a`/`b`/`resolution` are absent — see `create-market` lines 568–571). Prefer sending the localized fields and letting the server derive legacy fields.
+
+> **Prototype caveat (multi routing):** `create_market()` **always POSTs to `/api/create-market/`**, adding `market_type:1` to the body for multi markets. The server's `create-market` handler does **not** branch on `market_type` and would treat the submission as binary (empty `a`/`b`). The dedicated `create-market-multi` endpoint (documented below) exists and is the correct target for multi markets, but the current client never calls it. **The new client should POST multi markets to `create-market-multi`.**
+
+---
+
+#### `load-oracles`
+
+- **Method:** GET (`/api/load-oracles/`)
+- **When:** immediately after the create form is rendered (creator role), to populate the `oracle` ``.
+- **Role/auth:** none enforced on this endpoint (public list of oracle users).
+- **Request:** none.
+- **Response:** JSON **array**, each element:
+
+| Field | Type | UI use |
+|---|---|---|
+| `id` | int | `` |
+| `fee` | int (‰×precision) | option `rel` attr + used by `select_oracle()` to prefill `oracle_fee`; displayed as `fee/price_precision_ratio + "%"` |
+| `name` | string (account) | option text (escaped) |
+| `rules` | string | (not shown in the option list) |
+| `insurance` | int | (not shown in the option list) |
+| `reliability_score` | int (0–100) | shown in option label; falls back to `50` if missing |
+| `is_new` | 0/1 | (not shown in the option list) |
+
+- **Option label format:** `name [oracle.fee_label , oracle.rate_resolution: ]` → e.g. **"@alice [Fee: 0.5%, resolution rate: 72]"**.
+- **Error state:** on non-OK response → `show_message('warning', market.oracle_load_error)` → **"Error: failed to load oracles"**; oracle select stays at "Not selected".
+
+> **VIZ thin-client note (suggestion):** replace this with a chain/index query returning the set of registered oracle accounts and their advertised minimum fee + reliability metrics. No signing needed — read-only.
+
+---
+
+#### `load-oracle-profile`
+
+- **Method:** POST (`api_post('load-oracle-profile', {oracle_id})`)
+- **When:** rendered on the standalone oracle profile route (`#tab/market/oracle/`) via `load_oracle_profile()`. (Not embedded in the create form itself, but it is the detail view a creator opens to vet an oracle before selecting it.)
+- **Role/auth:** none enforced.
+- **Request:**
+
+| Field | Type | Meaning |
+|---|---|---|
+| `oracle_id` | int | Oracle user id to inspect |
+
+- **Response (object):**
+
+| Field | Type | UI use |
+|---|---|---|
+| `id` | int | fallback name `#id` |
+| `name` | string | header (`oracle.profile_title`) |
+| `rules` | string | shown as `oracle.rules_label` if present |
+| `fee` | int | shown as `fee/price_precision_ratio %` (`oracle.fee_label`) |
+| `fixed_fee` | int | if >0, appended as `+ Ƶ` (`oracle.fee_fixed`) |
+| `insurance` | int | `oracle.insurance_fund` amount |
+| `banned` | 0/1 | if 1, shows `oracle.banned_label` |
+| `ban_until` | int (unix) | if >0 → `oracle.banned_until` date, else `oracle.banned_forever` |
+| `reliability_score` | int 0–100 | `oracle.reliability_label` `/100` |
+| `is_new` | 0/1 | drives "new oracle" label + `oracle.hint_new` |
+| `risk_score` | float (or ≥999 = "—") | financial coverage `×n` (`oracle.coverage`) |
+| `trust_score` | int 0–100 | big composite score card |
+| `metrics` | object | detailed stats table (see below) |
+| `derived` | object | derived-rate table (see below) |
+
+ `metrics` object fields (all int unless noted): `markets_accepted`, `markets_resolved`, `markets_no_contest`, `markets_missed`, `disputes_received`, `disputes_lost`, `disputes_won`, `disputes_auto_closed`, `dispute_responses_missed`, `total_volume_resolved`, `total_insurance_slashed`, `avg_resolution_time` (sec; shown as hours), `bans_received`, `active_since` (unix), `last_active_time` (unix).
+
+ `derived` object fields (0..1 floats, shown as %): `resolution_rate`, `dispute_loss_rate`, `no_contest_rate`, `deadline_miss_rate`, `dispute_response_rate`.
+
+- **Summary block** shows top signals: `oracle.resolved` (resolution_rate), `oracle.disputes_lost` (dispute_loss_rate), `oracle.volume` (total_volume_resolved), `oracle.coverage` (risk). Context hints: `oracle.hint_new`, `oracle.hint_underfunded` (risk<1 & reliability≥60), `oracle.hint_low_reliability` (reliability<40).
+- **Error state:** on throw → `.market-detail` shows `oracle.load_error` + message.
+
+> **VIZ thin-client note (suggestion):** oracle reputation is entirely derived from on-chain history (markets accepted/resolved/disputed, insurance slashed). The thin client can compute trust/risk from indexed chain events; only `oracle_id` is needed as input. Read-only.
+
+---
+
+#### `load-committees`
+
+- **Method:** GET (`/api/load-committees/`)
+- **When:** immediately after the create form renders, to populate the `committee_id` `` (dispute judge).
+- **Role/auth:** none enforced.
+- **Request:** none.
+- **Response:** JSON **array**, each element:
+
+| Field | Type | UI use |
+|---|---|---|
+| `id` | int | `` |
+| `name` | string | option text; server derives display name from account, else `first_name last_name`, else `@username`, else `User #id` |
+
+- **Option label format:** `name [#id]`.
+- **Error state:** if not OK, the committee select simply keeps only "Not selected" (no explicit warning is shown).
+
+> **VIZ thin-client note (suggestion):** query the set of accounts flagged as dispute committees/DAOs. Read-only.
+
+---
+
+#### `create-market` (binary — Onix Binary)
+
+- **Method:** POST (`/api/create-market/`)
+- **When:** user clicks **"Create market"** (`create-market-action`) → `create_market()`, with `market_type == 0`.
+- **Role/auth:** authenticated + creator role (`user.create_market == 1`); server also blocks banned creators.
+- **Request body (exact keys sent for binary):**
+
+| Field | Type | Meaning |
+|---|---|---|
+| `q` | string | Question (from hidden `q`; server falls back to `title_`/`title_en` if empty) |
+| `a` | string | Outcome A label (fallback: `outcome_a_`) |
+| `b` | string | Outcome B label (fallback: `outcome_b_`) |
+| `resolution` | string | Resolution rules text (fallback: `resolution_rules_`) |
+| `liquidity` | string/number | Initial liquidity in VIZ (server ×1000; **min 100 VIZ / 100000 units**) |
+| `liquidity_fee` | int (‰) | LP reward, clamped ≥0 |
+| `oracle_id` | int | Selected oracle user id |
+| `oracle_fee` | int (‰×precision) | Must be ≥ oracle's own minimum, else error |
+| `oracle_fixed_fee` | number | Sent but **overridden** server-side by oracle's profile value |
+| `creator_fee` | int (‰) | Creator reward, clamped 0–100 |
+| `betting_expiration` | int (unix) | Bet deadline |
+| `result_expiration` | int (unix) | Result deadline (server clamps `betting_expiration` down if it exceeds this) |
+| `allow_early_resolution` | 0/1 | Early resolution toggle |
+| `allow_cancellation` | 0/1 | Allow cancellation toggle |
+| `allow_instant_bet` | 0/1 | Instant vs batch/commit-reveal (binary only) |
+| `time_penalty_type` | int (0 fixed / 1 percent) | Late-bet penalty mode |
+| `time_penalty_value` | int | Penalty magnitude |
+| `committee_id` | int | Dispute judge (0 = none; validated as a committee user if >0) |
+
+ *Additional keys the server reads but the current client does not send:* `category`, `subcategory`, `tags`, `default_lang`, `title_en`/`title_ru`, `description_en`/`ru`, `resolution_rules_en`/`ru`, `resolution_url`, `image_url_en`/`ru`, `outcome_a_en`/`ru`, `outcome_b_en`/`ru`, `penalty_curve_type`. The new client should send the localized metadata fields (the form collects them) so the server can build `metadata` and derive legacy `q`/`a`/`b`/`resolution`.
+
+- **Success response:**
+
+| Field | Type | UI use |
+|---|---|---|
+| `status` | bool | `true` = success; `false` triggers the error path |
+| `market_id` | int | On success: `.loading` shows `market.redirect_success` → **"Redirecting to the created market..."**, then `update_user_data()` + `navigate('#tab/market/'+market_id)` |
+| `liquidity_id` | int | (informational) |
+| `error` | array/string | Present on failure; if `status==0` but market created hidden, contains a "waiting for oracle" notice |
+
+- **Server-side validation errors (returned in `error[]`, surfaced by the client as `common.error_prefix + err`):**
+ - Liquidity < 100 VIZ → "Минимальная ликвидность для создания рынка 100 viz".
+ - Oracle not found → "Оракул не найден".
+ - `oracle_fee` below oracle minimum → "Комиссия оракулу не должна быть меньше…".
+ - Insufficient balance (needs liquidity + market creation fee) → "Insufficient balance (need … VIZ: … liquidity + … creation fee)".
+ - Committee id set but not a committee user → "Committee user not found".
+ - `betting_expiration + 86400 > result_expiration` and early resolution off → must leave ≥24h between betting close and result deadline.
+ - Self-oracle (creator == oracle) with insurance below `min_oracle_insurance` → "Insufficient oracle insurance (min: … VIZ)".
+ - DB failures → "Рынок не создан…", "Ликвидность не была добавлена…", etc. (rolled back).
+
+- **Status semantics:** newly created market status is `0` (**hidden, awaiting oracle acceptance**) unless the creator is also the oracle and passes the insurance check, in which case status `1` (accepted immediately). When status is `0` the server adds error note "Рынок создан и скрыт, ждем подтверждение от оракула" — but `status` is still `true`, so the client still redirects to the market.
+
+- **Client error UI:** on any thrown error the submit button is re-enabled, `.loading` gets `negative-info` and shows `common.error_prefix + ' ' + err`.
+
+> **VIZ thin-client note (suggestion):** market creation should become a signed `create_market` operation broadcast by the creator's key, carrying: question/outcome/resolution metadata (or an IPFS/content hash), oracle account, committee account, fee parameters (‰), expirations (unix), and the toggles. The initial-liquidity deposit + anti-spam creation fee should be a transfer/stake bundled in the same transaction (creation fee → DAO fund account). The node should reject if oracle_fee < oracle minimum, liquidity < min, or insurance insufficient for self-oracle — mirroring the checks above.
+
+---
+
+#### `create-market-multi` (Onix Multi — LMSR, multiple outcomes)
+
+- **Method:** POST (`/api/create-market-multi/`)
+- **When:** intended for `market_type == 1` submissions. **See prototype caveat above:** the current `create_market()` does not target this endpoint (it posts to `create-market` with `market_type:1`). The new client should route multi markets here.
+- **Role/auth:** authenticated + creator role; banned creators blocked (same checks as binary).
+- **Request body:**
+
+| Field | Type | Meaning |
+|---|---|---|
+| `q` | string | Question |
+| `resolution` | string | Resolution rules text |
+| `outcomes` | array | Outcome labels; must be an array, each non-empty (server trims + escapes). Count must be within `lmsr_min_outcomes`..`lmsr_max_outcomes` |
+| `liquidity` | string/number | Initial liquidity (min 100 VIZ) |
+| `liquidity_fee` | int (‰) | LP reward, clamped ≥0 |
+| `oracle_id` | int | Oracle user id (must exist as oracle) |
+| `oracle_fee` | int | Must be ≥ oracle minimum |
+| `creator_fee` | int (‰) | Clamped 0–100 |
+| `betting_expiration` | int (unix) | Bet deadline |
+| `result_expiration` | int (unix) | Result deadline |
+| `allow_early_resolution` | 0/1 | Early resolution toggle |
+| `allow_cancellation` | 0/1 | Allow cancellation toggle |
+| `allow_instant_bet` | 0/1 | **Ignored — server forces `1`** (multi has no batch settlement yet) |
+| `time_penalty_type` | int (0/1) | Late-bet penalty mode |
+| `time_penalty_value` | int | Penalty magnitude |
+| `committee_id` | int | Dispute judge |
+
+ *(`oracle_fixed_fee` is taken from the oracle profile, not the request; `penalty_curve_type` optional.)*
+
+- **Success response:**
+
+| Field | Type | UI use |
+|---|---|---|
+| `status` | bool | success flag |
+| `market_id` | int | redirect target (`#tab/market/`) |
+| `market_type` | int | `1` |
+| `lmsr_b` | int | LMSR liquidity parameter (informational) |
+| `outcome_count` | int | number of outcomes created |
+
+- **Validation errors (thrown as `Exception` → surfaced as `common.error_prefix + err`):**
+ - `outcomes` not an array → "Outcomes must be an array".
+ - Fewer than `lmsr_min_outcomes` → "Minimum N outcomes required"; more than `lmsr_max_outcomes` → "Maximum N outcomes allowed".
+ - Empty outcome label → "Outcome label cannot be empty".
+ - Liquidity < 100 VIZ → "Minimum liquidity is 100 VIZ".
+ - Oracle not found / `oracle_fee` too low / insufficient balance / self-oracle insufficient insurance (same family as binary).
+ - Liquidity too low for N outcomes (derived `lmsr_b` < `lmsr_min_b_parameter`) → "Liquidity too low for N outcomes (min b=… VIZ)".
+ - The whole insert (market row, `market_outcomes`, LP row, balance deduction, creation fee to DAO, self-oracle accept increment) runs in a DB transaction; any failure rolls back and throws "Market creation failed: …".
+
+- **Status semantics:** same as binary — status `0` (hidden, awaiting oracle) unless self-oracle with sufficient insurance → status `1`.
+
+> **VIZ thin-client note (suggestion):** identical to the binary create op but with an `outcomes[]` label array and no `allow_instant_bet` (force batch/instant per protocol rules). The node computes the LMSR `b` parameter from liquidity + outcome count and should reject if below the minimum. One signed transaction: create op + liquidity deposit + creation fee transfer.
+## 7. Oracle role: acceptance, insurance, pending queue & resolution
+
+This section documents everything an **Oracle**-role user sees and does, from the client's point of view: the oracle profile / trust screen, the insurance fund, the pending-markets queue (accept / reject), and market resolution (binary, multi, and the "no-contest" refund path). Disputes are covered in section 8 and are excluded here.
+
+**Role gate (client-side):** The Oracle UI lives inside the **Profile** tab (`nav.profile`). The oracle blocks in `render_profile_extra()` only render when `user.oracle == 1`. The register-oracle form renders in the `else` branch. All oracle endpoints additionally require `auth` server-side and re-check `user_arr['oracle']==1` (except registration).
+
+**Money / precision convention:** All VIZ amounts are integers in the protocol at precision 3 (1 VIZ = 1000 units). The client sends human VIZ (e.g. `"5.000"`) and the server does `floor(floatval(amount)*1000)`. Fees expressed in **‰ (permille)** are integers `0..1000` (server divides by 1000); the UI displays them via `fee/price_precision_ratio + '%'`. The currency glyph shown everywhere is `Ƶ`.
+
+---
+
+### Screen: Oracle profile / Trust Index (read view)
+
+**Where:** Opened by `load_oracle_profile(oracle_id)` (app.js 616-702), rendered into `.market-detail`. This is the public view of any oracle (also shown when picking an oracle during market creation), and it is what a self-oracle sees about their own reputation. It fires `load-oracle-profile` (POST) with `{oracle_id}`.
+
+**What the user sees** (all labels from the `oracle.*` namespace):
+
+- **Title** — `oracle.profile_title` → "Oracle profile: %%NAME%%" (uses `data.name` or `#`+`data.id`).
+- **Big trust score card** — `data.trust_score` rendered large, colored by score class, with a label and the sublabel `oracle.trust_index` ("Trust Index"). If `data.is_new==1` the label becomes `oracle.new_oracle` ("New oracle").
+- **Trust summary rows**:
+ - `oracle.resolved` ("Resolved") → `derived.resolution_rate*100` %
+ - `oracle.disputes_lost` ("Disputes lost") → `derived.dispute_loss_rate*100` %
+ - `oracle.volume` ("Volume") → `metrics.total_volume_resolved` Ƶ
+ - `oracle.coverage` ("Coverage (insurance/bets)") → `×`+`data.risk_score`, or `—` when `risk_score>=999` (treated as "effectively infinite / no bets to cover").
+- **Contextual hint** (one of):
+ - `oracle.hint_new` if `is_new`
+ - `oracle.hint_underfunded` if `risk_score<1` and `reliability_score>=60`
+ - `oracle.hint_low_reliability` if `reliability_score<40`
+- **Info block** (`oracle.info_label`): `oracle.fee_label` + `data.fee` as %, plus `+ Ƶ oracle.fee_fixed` when `data.fixed_fee>0`; `oracle.insurance_fund` + `data.insurance` Ƶ; `oracle.rules_label` + `data.rules`; **banned state** (`oracle.banned_label` "⛔ Banned" + `oracle.banned_until {DATE}` or `oracle.banned_forever`) when `data.banned==1`; `oracle.active_since` (from `metrics.active_since`); `oracle.last_active` (from `metrics.last_active_time`); a `` line with `oracle.reliability_label` `reliability_score/100` and `oracle.coverage_label`.
+- **Expandable "Detailed statistics"** (`oracle.toggle_details`), containing two tables:
+ - **Statistics** (`oracle.statistics_title`): `stat_accepted` (markets_accepted), `stat_resolved` (markets_resolved), `stat_no_contest` (markets_no_contest), `stat_missed` (markets_missed), `stat_disputes_received`, `stat_disputes_lost`, `stat_disputes_won`, `stat_disputes_auto` (disputes_auto_closed), `stat_responses_missed` (dispute_responses_missed), `stat_volume_resolved` (Ƶ), `stat_insurance_slashed` (total_insurance_slashed, Ƶ), `stat_avg_resolution` (avg_resolution_time/3600, in `stat_hours` "h."), `stat_bans` (bans_received).
+ - **Indicators** (`oracle.indicators_title`): `rate_resolution`, `rate_dispute_loss`, `rate_no_contest`, `rate_deadline_miss`, `rate_dispute_response` — each `derived.*100` to 1 decimal.
+
+**Score classes / labels** (`reliability_score_class` / `reliability_score_label`, app.js 595-610). Given `score` (0-100) and `is_new`:
+
+| Condition | CSS class | Label key | English |
+|---|---|---|---|
+| `is_new` | `reliability-new` | `oracle.score_new` | New |
+| `score>=80` | `reliability-excellent` | `oracle.score_excellent` | Excellent |
+| `score>=60` | `reliability-good` | `oracle.score_good` | Good |
+| `score>=40` | `reliability-average` | `oracle.score_average` | Average |
+| `score>=20` | `reliability-poor` | `oracle.score_poor` | Poor |
+| else | `reliability-unreliable` | `oracle.score_unreliable` | Unreliable |
+
+`render_reliability_badge(score,is_new)` (app.js 611-615) renders a `score — label ` used inline elsewhere (e.g. oracle selection lists).
+
+**Displayed metrics come from `compute_oracle_reliability_score` server-side** (api.php 1-81). Node devs only need the *displayed* fields; the formula is already ported. Metrics that feed the UI: markets accepted/resolved/no_contest/missed; disputes received/lost/won/auto_closed; dispute responses missed; total volume resolved; insurance slashed; average resolution time; bans received; active_since; last_active_time. Derived rates shown: resolution_rate, dispute_loss_rate, no_contest_rate, deadline_miss_rate, dispute_response_rate. `is_new` is true when fewer than 5 resolved+no_contest+missed outcomes exist.
+
+#### `load-oracle-profile`
+- **Method:** POST · **When:** oracle profile opened (creation flow oracle picker, market detail oracle link). · **Auth:** none required for reading.
+
+| Request field | Type | Meaning |
+|---|---|---|
+| `oracle_id` | int | User id of the oracle to inspect |
+
+Response (fields the UI consumes; nested objects `metrics` and `derived`):
+
+| Response field | Type | UI use |
+|---|---|---|
+| `id` / `name` | int / string | Title, fallback `#id` |
+| `trust_score` | int | Big score card + class |
+| `reliability_score` | int | "Reliability" line, hint thresholds |
+| `risk_score` | number/string | "Coverage" (`×value`, `—` if `>=999`), underfunded hint |
+| `is_new` | 0/1 | New-oracle label + hint |
+| `fee` | int (‰) | Fee % display |
+| `fixed_fee` | int (units) | Fixed reward display |
+| `insurance` | int (units) | Insurance fund display |
+| `rules` | string | Rules line |
+| `banned` | 0/1 | Banned badge |
+| `ban_until` | int (unixtime) | Ban expiry / "forever" if 0 |
+| `metrics.{markets_accepted, markets_resolved, markets_no_contest, markets_missed, disputes_received, disputes_lost, disputes_won, disputes_auto_closed, dispute_responses_missed, total_volume_resolved, total_insurance_slashed, avg_resolution_time, bans_received, active_since, last_active_time}` | int | Statistics table |
+| `derived.{resolution_rate, dispute_loss_rate, no_contest_rate, deadline_miss_rate, dispute_response_rate}` | float 0..1 | Indicators table + summary |
+
+**UI states/errors:** on failure `.market-detail` shows `oracle.load_error` + `err.message`.
+
+> **VIZ thin-client note (suggestion):** the trust/reputation numbers are aggregate counters maintained off-chain by the settlement layer. The thin client should treat this as a **read-only query** against the node/indexer (e.g. a `get_oracle_profile` custom API), not a signed action. Only `oracle_id`, the counters, and the derived rates need to be exposed.
+
+---
+
+### Screen: Oracle settings (self, edit) & registration
+
+**Where:** Profile tab, `render_profile_extra()` (app.js 2787-2865).
+
+**If `user.oracle==1`** the client shows an **Oracle settings** editor (`profile.oracle_settings_title`):
+- Header `profile.oracle_title` "Oracle: %%NAME%%" and the current insurance via `profile.oracle_insurance_display`.
+- Fields: `account` (`profile.oracle_name`), `oracle_rules` textarea (`profile.oracle_rules`), `oracle_fee_reg` number (`profile.oracle_fee` "Fee (‰ from market)", default `user.oracle_fee||5`), `oracle_fixed_fee` number (`profile.oracle_fixed_fee` "Fixed reward (VIZ)", default `(user.oracle_fixed_fee||5000)/1000`).
+- Button `profile.save_settings` → `oracle_update_action()` (app.js 2932-2946) which calls **`register-oracle`** with `{account,oracle_rules,oracle_fee,oracle_fixed_fee}`. On success shows `profile.settings_saved` and refreshes user data. (Same endpoint, no fee re-charged because already an oracle.)
+
+**If `user.oracle!=1`** the client shows a **"Become an oracle"** form (`profile.become_oracle`): `account`, `oracle_rules`, `oracle_fee_reg`, button `profile.register` → `register_oracle_action()` (app.js 2917-2930) calling **`register-oracle`** with `{account,oracle_rules,oracle_fee}` (no `oracle_fixed_fee` sent on first registration → server defaults it to 5 VIZ). On success `profile.registered_success`.
+
+#### `register-oracle`
+- **Method:** POST · **When:** "Register" (become oracle) or "Save settings" (edit). · **Auth:** required.
+
+| Request field | Type | Meaning |
+|---|---|---|
+| `account` | string | Oracle display name (required; server rejects empty with "Oracle name is required") |
+| `oracle_rules` | string | Free-text operating rules / statement of intent |
+| `oracle_fee` | int (‰) | Percentage fee taken from losing pool |
+| `oracle_fixed_fee` | float VIZ | One-time fee charged to creator on accept; server `floor(*1000)`, defaults to 5000 units (5 VIZ) if `<=0`. Omitted on first registration. |
+
+| Response field | Type | UI use |
+|---|---|---|
+| `status` | bool | Success flag |
+| `updated` | bool (edit path only) | Present when profile was updated vs. first registration |
+
+**Behavior:** First registration debits a `oracle_registration_fee` (settings) from balance; edit path charges nothing. **UI errors:** "Oracle name is required", "Insufficient balance for registration fee", "Registration failed" / "Profile update failed" — surfaced inline via the loading paragraph with `common.error_prefix`.
+
+> **VIZ thin-client note (suggestion):** registration = an on-chain account flag + a fee transfer. The thin client would sign a custom operation (e.g. `oracle_register`) carrying name/rules/fee/fixed_fee, and pay the registration fee via a transfer to the protocol account. Editing settings is the same op without the fee.
+
+---
+
+### Screen: Insurance fund (deposit / withdraw)
+
+**Where:** Profile tab, inside the oracle block (`render_profile_extra` 2807-2813). Shows current insurance (`profile.oracle_insurance_display`), an amount input (`form.amount_placeholder`, `step=0.001`), and two buttons: `oracle.insurance_deposit` ("Deposit", `.oracle-deposit-ins-action`) and `oracle.insurance_withdraw` ("Withdraw", `.oracle-withdraw-ins-action`). Both call `oracle_insurance_action(type)` (app.js 2961-2972), which posts the raw `amount` string and on success shows `common.success` and refreshes user data.
+
+The insurance fund is a **gate for accepting markets** (see accept below) and the pool that gets **slashed** on no-contest / lost disputes.
+
+#### `oracle-deposit-insurance`
+- **Method:** POST · **When:** "Deposit" button. · **Auth:** required; must be oracle.
+
+| Request field | Type | Meaning |
+|---|---|---|
+| `amount` | float VIZ | Amount to move from balance into insurance; server `floor(*1000)`, must be `>0` |
+
+| Response field | Type | UI use |
+|---|---|---|
+| `status` | bool | Success |
+
+**Errors:** "Not an oracle", "Invalid amount", "Insufficient balance", "Deposit failed".
+
+#### `oracle-withdraw-insurance`
+- **Method:** POST · **When:** "Withdraw" button. · **Auth:** required; must be oracle.
+
+| Request field | Type | Meaning |
+|---|---|---|
+| `amount` | float VIZ | Amount to move from insurance back to balance; must be `>0` and `<= oracle_insurance` |
+
+| Response field | Type | UI use |
+|---|---|---|
+| `status` | bool | Success |
+
+**Errors:** "Not an oracle", "Invalid amount", "Insufficient insurance balance", **"Cannot withdraw insurance while you have active markets"** (blocked when the oracle has any market in status 0 or 1), "Withdraw failed".
+
+> **VIZ thin-client note (suggestion):** insurance is escrow tied to the oracle account. Deposit/withdraw are two signed transfers between the oracle's balance and a locked insurance sub-balance. The node must enforce the "no active markets" lock on withdraw, and expose the current insurance amount for the accept-gate check.
+
+---
+
+### Screen: Pending markets queue (accept / reject)
+
+**Where:** Profile tab, `.oracle-pending-markets` region, rendered by `render_profile_oracle_section()` (app.js 2867-2881). It fires **`load-pending-markets`** (POST, empty body). Only markets where the current user is the assigned oracle AND `status==0` (Awaiting oracle) are returned.
+
+**What the user sees:** header `profile.pending_markets` ("Awaiting your confirmation:") then, per market, an `.outcome-card` with the question text `m.q` and two buttons:
+- `resolution.accept_button` ("Accept", `.oracle-accept-btn` carrying `data-mid`) → `oracle_accept_action(market_id)` (app.js 2772-2778).
+- `resolution.reject_button` ("Reject", `.oracle-reject-btn`) → `oracle_reject_action(market_id)` (app.js 2779-2785).
+
+Empty state: `profile.no_pending_markets` ("No pending markets"). Load error: `profile.load_error`.
+
+#### `load-pending-markets`
+- **Method:** POST · **When:** oracle section of profile renders / after an accept/reject. · **Auth:** required. Returns `[]` if the user is not an oracle.
+
+**Request:** empty `{}`.
+
+**Response:** a JSON **array** of market rows (`SELECT * FROM markets ... status=0 ORDER BY id DESC LIMIT 50`). The UI consumes:
+
+| Response field | Type | UI use |
+|---|---|---|
+| `id` | int | button `data-mid` |
+| `q` | string | market question shown on the card |
+
+(Full market rows are returned; only `id` and `q` are read by this view.)
+
+#### `oracle-accept-market`
+- **Method:** POST · **When:** "Accept". · **Auth:** required; caller must be the market's oracle; market must be `status==0`.
+
+| Request field | Type | Meaning |
+|---|---|---|
+| `market_id` | int | Market to accept |
+
+| Response field | Type | UI use |
+|---|---|---|
+| `status` | bool | Success → `resolution.market_accepted`, re-render queue |
+
+**Server-side gating the client must anticipate (all surface as `err.message`):**
+- "Market not found"; "You are not the oracle for this market"; "Market is not waiting for oracle approval" (status ≠ 0).
+- **Ban check:** "Oracle is banned from accepting markets" when `oracle_banned==1` and ban not expired.
+- **Insurance minimum:** "Insufficient oracle insurance (min: X VIZ)" when `oracle_insurance < min_oracle_insurance`.
+- **Creator fixed-fee funding:** "Creator has insufficient balance for oracle fixed fee (X VIZ)".
+- Generic "Accept failed".
+
+**Side effects (context only):** market → `status=1` (Active), fixed fee moved creator→oracle, oracle's `markets_accepted` incremented, and a possible Lazy-Pool auto-allocation of liquidity. The client just needs the `status:true`.
+
+#### `oracle-reject-market`
+- **Method:** POST · **When:** "Reject". · **Auth:** required; caller must be the market's oracle; market must be `status==0`.
+
+| Request field | Type | Meaning |
+|---|---|---|
+| `market_id` | int | Market to reject |
+
+| Response field | Type | UI use |
+|---|---|---|
+| `status` | bool | Success → `resolution.market_rejected`, re-render queue |
+
+**Behavior:** returns the creator's provided liquidity in full and **deletes** the market row. **Errors:** "Market not found", "You are not the oracle for this market", "Market is not waiting for oracle approval", "Reject failed".
+
+> **VIZ thin-client note (suggestion):** accept/reject are signed oracle operations referencing `market_id`. The node must, on accept, verify the ban window and `insurance >= min_oracle_insurance`, and atomically transfer the creator's fixed fee to the oracle; on reject, refund liquidity and cancel the market. The thin client should pre-check insurance/ban locally (from the profile query) to avoid a failed broadcast, but the node stays authoritative.
+
+---
+
+### Screen: Resolution (submit result)
+
+**Where:** Market detail page, `.resolve-form` (app.js 1911-1928). Rendered **only** when `auth && data.oracle && user.id==data.oracle.id && (m.status==1 || m.status==2) && m.payout_status==0` — i.e. the current user is this market's oracle and the market is Active (1) or Closed (2) and not yet paid out.
+
+**What the user sees** (`resolution.*`):
+- Title `resolution.submit_title` ("Submit result (oracle)").
+- A `select[name=resolve_outcome]`:
+ - **Binary:** two options `0 → m.a`, `1 → m.b`.
+ - **Multi** (`is_multi && m.outcomes`): one option per outcome, `value=oi`, label `m.outcomes[oi].label`.
+- A text input `resolve_decision` (`resolution.justification`).
+- Button `resolution.confirm_button` ("Confirm result", `.resolve-market-action`) → `resolve_market_action(el)` (app.js 2755-2770).
+
+**Client dispatch logic:** `resolve_market_action` reads `market_id`, `mtype` (from `data-market-type`), the selected `outcome`, and `decision`. It picks the endpoint by type: `mtype==1 → resolve-market-multi`, else `resolve-market`. It posts `{market_id, outcome, decision}` for BOTH. On success it shows `resolution.submitted_success` and reloads the market detail after ~1s.
+
+> ⚠️ **Field-name mismatch the node devs MUST handle (real code):** the multi endpoint (`resolve-market-multi`, api.php 3152) reads `$request['winning_outcome']`, but the client (`resolve_market_action`) sends the key `outcome`, not `winning_outcome`. It also does not send `decision_url` (the server reads an optional `decision_url` but the form has no such field). A faithful port should either accept `outcome` as an alias for `winning_outcome` on the multi endpoint, or the new client should send `winning_outcome` for multi. Binary (`resolve-market`) correctly reads `outcome`.
+
+#### `resolve-market` (binary)
+- **Method:** POST · **When:** "Confirm result" on a binary market. · **Auth:** required; caller must be the market's oracle.
+
+| Request field | Type | Meaning |
+|---|---|---|
+| `market_id` | int | Market to resolve |
+| `outcome` | int (0 or 1) | Winning side; 0→outcome A, 1→outcome B. Server rejects any other value ("Invalid outcome"). |
+| `decision` | string | Free-text justification (stored) |
+| `decision_url` | string (optional) | Evidence URL; **not sent by current UI** but read if present |
+
+| Response field | Type | UI use |
+|---|---|---|
+| `status` | bool | Success → `resolution.submitted_success`, reload detail |
+
+**Gating / errors (surface as `err.message`):**
+- "Invalid outcome" (outcome ∉ {0,1}); "Market not found"; "You are not the oracle".
+- **Ban:** "Oracle is banned from resolving markets".
+- **Status:** "Market cannot be resolved in current status" (only status 1 or 2 allowed).
+- **Early-resolution timing:** when `status==1` and `allow_early_resolution!=1`, resolving before `betting_expiration` fails with "Betting period not expired and early resolution not allowed".
+- "Resolution failed: …".
+
+**Effect (context):** market → `status=3` (Resolved), `resolved_outcome`, `payout_status=1` (Calculated), parimutuel payouts created (winners split losers' pool by weight, minus oracle/creator/LP fees; time penalties; LP principal+fee). Bettors are notified of a **dispute grace window** (`dispute_grace_hours`). The detail view then starts a grace countdown if `m.grace_period_end` is present (app.js 1945-1947, label `status.grace_countdown`).
+
+#### `resolve-market-multi` (multi-outcome)
+- **Method:** POST · **When:** "Confirm result" on a multi market (`market_type==1`). · **Auth:** required; caller must be the market's oracle.
+
+| Request field | Type | Meaning |
+|---|---|---|
+| `market_id` | int | Market to resolve |
+| `winning_outcome` | int | Winning outcome index, `0 <= idx < outcome_count`. **(Client currently sends this value under the key `outcome` — see mismatch note above.)** |
+| `decision` | string | Justification |
+| `decision_url` | string (optional) | Evidence URL |
+
+| Response field | Type | UI use |
+|---|---|---|
+| `status` | bool | Success → `resolution.submitted_success`, reload |
+| `winning_outcome` | int | (echo) |
+| `losers_sum` | int (units) | (echo) |
+| `oracle_fee` | int (units) | (echo) |
+| `creator_fee` | int (units) | (echo) |
+| `winners_pool` | int (units) | (echo) |
+
+The current UI ignores the extra echo fields and only branches on success/`err.message`.
+
+**Gating / errors:** "Not a multi-outcome market", "You are not the oracle", "Oracle is banned", "Market cannot be resolved in current status", "Invalid winning outcome" (out of `[0,outcome_count)`), early-resolution timing (same rule as binary), "Resolution failed: …".
+
+**Result-expiration timing shown to user (context):** market detail displays the **result submission deadline** (`market_detail.result_deadline`, from `market.result_expiration`) and the **early-resolution** flag (✅/❌ `market_detail.early_resolution`, app.js 1692). These tell the oracle whether they may resolve before betting closes and by when they must resolve. Missing the result deadline is what drives the `markets_missed` / auto-refund penalty path (server-side cron; no dedicated client action).
+
+> **VIZ thin-client note (suggestion):** resolution is the core signed oracle op: `resolve_market(market_id, winning_outcome, decision, decision_url?)`. The node must enforce (a) caller == assigned oracle, (b) ban window, (c) market status ∈ {active, closed}, (d) `allow_early_resolution` vs. `betting_expiration`, (e) valid outcome index, then run the (already-ported) parimutuel settlement and open the dispute grace window. Recommend the new client always send a single canonical key (`winning_outcome`) for both binary and multi to avoid the legacy `outcome`/`winning_outcome` split.
+
+---
+
+### "No contest" (oracle refund resolution)
+
+`oracle-no-contest` (api.php 1912-2027) lets an oracle declare it cannot verify the outcome: it behaves like a resolution with `resolved_outcome=-1`, refunding every bet (full stake) and every non-lazy-pool LP (principal), slashing part of the oracle's insurance as a penalty distributed pro-rata to participants, and opening the same dispute grace window.
+
+> ⚠️ **Not wired in the current front-end.** A full search of `app.js` finds **no** button, form, or `api_post('oracle-no-contest', …)` call. The endpoint exists and is reachable, and the profile screen *displays* the resulting counters (`oracle.stat_no_contest` / `oracle.rate_no_contest`), but there is no UI to trigger it in this prototype. The new client should **add** a "No contest" action on the resolution screen.
+
+#### `oracle-no-contest`
+- **Method:** POST · **When:** (intended) an oracle chooses "cannot verify" instead of picking a side. · **Auth:** required; caller must be the market's oracle; market status must be 1 (Active) or 2 (Closed).
+
+| Request field | Type | Meaning |
+|---|---|---|
+| `market_id` | int | Market to declare no-contest |
+| `reason` | string | Required explanation (server rejects empty: "Reason is required") |
+
+| Response field | Type | UI use |
+|---|---|---|
+| `status` | bool | Success |
+| `penalty` | int (units) | Insurance slashed (capped at oracle's insurance; `oracle_no_contest_penalty_percent` of `dispute_fee`, default 50%) |
+| `refund_payouts_created` | int | Count of refund payouts created (bets + LP) |
+| `grace_period_hours` | int | Dispute grace window length |
+
+**Errors:** "Reason is required", "Market not found", "You are not the oracle", "Market is not in a state that allows no-contest", "No-contest failed: …".
+
+**Effect (context):** all active bets → refunded at full stake (pending payouts type 0); personal LPs refunded principal (type 1); lazy-pool principal returned to pool; oracle insurance slashed and distributed proportionally to participants' stakes (immediate balance credit + payout type 6); market → `status=3`, `resolved_outcome=-1`, `decision="NO-CONTEST: "`; oracle `markets_no_contest`++ and `total_insurance_slashed`+=penalty; participants notified they may dispute during the grace period.
+
+> **VIZ thin-client note (suggestion):** model as a distinct signed op `oracle_no_contest(market_id, reason)`. The node computes the insurance slash (`no_contest_penalty_percent` × `dispute_fee`, capped at insurance), refunds all stakes/LP principal, distributes the slash pro-rata, and opens the grace window. Since the legacy UI never exposed this, the new client should add the button; the response's `penalty` / `refund_payouts_created` / `grace_period_hours` are useful to confirm the action to the user.
+## 8. Disputes & DAO / committee governance
+
+This section documents the full dispute lifecycle and the governance surfaces (committee / DAO arbitrator, unban) exactly as the current PHP + `app.js` prototype implements them. It is written from the user's point of view: what each role sees on the market-detail screen, which button fires which API call, the exact request payload the client sends, and the exact response fields the UI consumes.
+
+All dispute UI lives **inside the market-detail screen** (`load_market_detail` in `app.js`, roughly lines 1866–1909). There is no separate "disputes" page. The blocks that appear are gated by market status, the caller's role, and whether a dispute object was returned. The three action handlers are `create_dispute_action` (`app.js` 2710), `oracle_respond_action` (2725), and `resolve_dispute_action` (2739).
+
+Key data the market-detail response must carry for this section to render:
+- `m.status` — market lifecycle status. `3` = resolved. Dispute affordances only appear at `status==3`.
+- `m.payout_status` — payout lifecycle. `1` = calculated/disputable (`status.payout_1` = "Calculated"), `3` = disputed (`status.payout_3` = "Disputed"). The dispute-open form only renders when `payout_status==1`.
+- `m.committee` — id of the committee/arbitrator assigned at market creation. A dispute is impossible if this is `0` (server: "No committee assigned"). Shown in detail via `market_detail.committee_id` = "Committee: #%%ID%%".
+- `m.update` — last-update unixtime; combined with the `dispute_grace_hours` setting (default 12) it drives the grace-period countdown badge (`status.grace_countdown` = "Payout in %%TIME%% (if no dispute)").
+- `data.oracle` — `{ id, ... }` of the market's oracle. Used to decide whether to show the oracle-response form (`user.id == data.oracle.id`).
+- `data.dispute` — the dispute record (present once one exists). Fields consumed by the UI: `id`, `status` (0/1/2), `claim_url`, `oracle_response`, `committee_decision`.
+- `user.committee` — `1` if the logged-in user is a committee member / arbitrator. Gates the resolution form.
+
+### Screen: Market detail — dispute affordances (bettor / disputer view)
+
+When a market is resolved and still in the disputable window (`auth && m.status==3 && m.payout_status==1`), the client renders the **"Dispute result"** block (`dispute.open_title`):
+
+- Heading: **"Dispute result"**.
+- A single text input `claim_url` with placeholder **"Claim link"** (`dispute.claim_placeholder`). This is the evidence/claim URL — the only user input for opening a dispute. There is no separate free-text claim body; the "claim" is expressed as a link.
+- A red button **"Open dispute"** (`dispute.open_button`, class `create-dispute-action`).
+- A `.dispute-loading` status line that shows `bet.sending` while in-flight, then `dispute.opened_success` ("Dispute opened!") on success or `common.error_prefix` + message on failure.
+
+The button is a soft double-submit guard: the click handler adds a `disabled` class before firing (`app.js` 3267). On success the client calls `update_user_data()` (to reflect the debited fee) and re-loads the market detail after 1s.
+
+The client does **not** pre-check eligibility beyond rendering the form when `status==3 && payout_status==1`. All the real gating (must have a bet, within grace, committee assigned, no open dispute, sufficient balance) is enforced server-side and surfaced as an error string in `.dispute-loading`.
+
+Note on cost/bond: there is **no bond input in the UI**. The dispute costs a fixed fee read from the `dispute_fee` setting (the About FAQ, `about.faq_a8`, states "1000 VIZ"). The fee is debited from the user's balance by the server; the disputer never types an amount.
+
+#### API: `create-dispute`
+
+- **Method:** POST `/api/create-dispute/`
+- **Fires when:** user clicks **"Open dispute"** (`create_dispute_action`, `app.js` 2710).
+- **Role/auth:** authenticated user who **has at least one bet on the market** (server checks `bets` count > 0). Any bettor may dispute; there is no separate "disputer" role.
+
+**Request fields**
+
+| Field | Type | Meaning |
+|---|---|---|
+| `market_id` | int | Market being disputed (`form.data('market')`). |
+| `claim_url` | string | The claim/evidence link. **Required** — server rejects empty with "Claim URL is required". Server `htmlspecialchars`-escapes it. |
+
+**Server preconditions (each returns a distinct error string shown in `.dispute-loading`)**
+
+| Condition | Error message |
+|---|---|
+| Market missing | "Market not found" |
+| `status != 3` | "Market is not resolved" |
+| `payout_status != 1` | "Market payouts not in disputable state" |
+| `committee == 0` | "No committee assigned to this market, dispute not possible" |
+| `now > update + dispute_grace_hours*3600` | "Dispute grace period expired" |
+| caller has no bet | "You have no bets on this market" |
+| an open dispute already exists (`status==0`) | "Dispute already open for this market" |
+| balance < `dispute_fee` | "Insufficient balance for dispute fee" |
+
+**Response fields**
+
+| Field | Type | UI use |
+|---|---|---|
+| `status` | bool `true` | Success flag. |
+| `dispute_id` | int | New dispute id. Not directly rendered; the client re-loads the market detail which returns the full `data.dispute`. |
+
+**Server side effects (for reviewer context, not client contract):** debits `dispute_fee` from the disputer, sets market `payout_status=3` ("Disputed"), increments oracle's `oracle_disputes_received`, writes a `history` row (type `12` = "Dispute payment") and a `market_log` `dispute` entry.
+
+**VIZ thin-client note (suggestion):** A dispute would be a signed `custom`/`account_metadata`-style operation from the disputer's account referencing the market id and claim URL, escrowing the `dispute_fee` bond (e.g. a transfer to a dispute-escrow account or a lock). The node/indexer must enforce: market resolved, within grace window, committee assigned, caller holds a position, no open dispute. Emit a `dispute_opened` event carrying `dispute_id`, `market_id`, `claim_url`.
+
+### Screen: Market detail — existing dispute card (all viewers)
+
+Once `data.dispute` is present, every viewer sees an **outcome card** (`app.js` 1876–1883):
+
+- Title **"Dispute #"** (`dispute.title`, ID interpolated) followed by a status label derived from `d.status`:
+ - `0` → **"Open"** (`dispute.status_0`)
+ - `1` → **"Resolved for complaint"** (`dispute.status_1`, i.e. oracle was wrong)
+ - `2` → **"Resolved for oracle"** (`dispute.status_2`, i.e. oracle was right)
+- If `d.claim_url`: **"Claim:"** (`dispute.claim_label`) plus a hyperlink labelled **"link"** (`dispute.claim_link`) opening the URL in a new tab.
+- If `d.oracle_response`: **"Oracle response:"** (`dispute.oracle_response_label`) followed by the escaped text.
+- If `d.committee_decision`: **"Committee decision:"** (`dispute.committee_decision_label`) followed by the escaped justification text.
+
+This card is the **dispute verification view**: reviewers read the claim link, the oracle's rebuttal, and the committee's justification side by side. Broader outcome verification (the numeric result, payouts, and the full market log including `dispute`, `dispute_resolve`, `dispute_oracle_response` entries) is rendered by the surrounding market-detail screen and market-log table (`app.js` ~1930), so committee members can check the resolved outcome and payout recalculation before deciding.
+
+### Screen: Oracle response form (oracle only)
+
+Rendered only when `auth && d.status==0 && data.oracle && user.id==data.oracle.id` (`app.js` 1885). The market's oracle sees:
+
+- A text input `oracle_resp` with placeholder **"Oracle response"** (`dispute.oracle_response_placeholder`).
+- A blue button **"Reply"** (`dispute.oracle_response_button`, class `oracle-respond-action`).
+
+On success the client shows a success toast **"Oracle response sent"** (`dispute.oracle_responded_success`) and re-loads the current market from the URL hash.
+
+#### API: `dispute-oracle-response`
+
+- **Method:** POST `/api/dispute-oracle-response/`
+- **Fires when:** oracle clicks **"Reply"** (`oracle_respond_action`, `app.js` 2725).
+- **Role/auth:** authenticated user who is the market's oracle (`market.oracle == user.id`).
+
+**Request fields**
+
+| Field | Type | Meaning |
+|---|---|---|
+| `dispute_id` | int | Dispute being answered (`form.data('dispute')`). |
+| `oracle_response` | string | The oracle's rebuttal text (server `htmlspecialchars`-escaped). |
+
+**Server preconditions / errors**
+
+| Condition | Error |
+|---|---|
+| Dispute not found | "Dispute not found" |
+| `dispute.status != 0` | "Dispute is not open" |
+| caller is not the oracle | "You are not the oracle" |
+
+**Response fields**
+
+| Field | Type | UI use |
+|---|---|---|
+| `status` | bool `true` | Success; triggers toast + reload. |
+
+Server stores `oracle_response`, `oracle_response_time`, `update`. (No fee, no state change on the market; dispute stays `status==0` awaiting the committee.)
+
+**VIZ thin-client note (suggestion):** A signed operation from the oracle account referencing `dispute_id`, carrying the rebuttal text, valid only while the dispute is open. Emit `dispute_oracle_responded`.
+
+### Screen: Committee / DAO resolution form (committee member / arbitrator)
+
+Rendered only when `auth && d.status==0 && user.committee==1` (`app.js` 1892). This is the arbitrator role the About page calls the **"Arbitrator (Judge)"** (`about.role_judge`) who "resolves disputes between players and oracle". In the current prototype there is a **single committee/arbitrator role** — there is no multi-voter tally UI; one committee member's submission resolves the dispute outright. (A "private resolver"/DAO-fund payout account exists only server-side as a settings-configured recipient of resolver rewards, not as a distinct client role.)
+
+The form (`.committee-form`, carries `data-dispute` and `data-market`) contains:
+
+- Heading **"Committee decision"** (`dispute.committee_title`).
+- Select `committee_decision_type` with two options:
+ - value `0` → **"Oracle is right"** (`dispute.committee_oracle_right`)
+ - value `1` → **"Oracle is wrong"** (`dispute.committee_oracle_wrong`)
+- Select `correct_outcome` — the outcome the committee deems correct. For binary markets: value `0` = side A label (`m.a`), value `1` = side B label (`m.b`). For multi-outcome markets it enumerates `m.outcomes[i].label` at index `i`.
+- Text input `committee_text` — placeholder **"Justification"** (`dispute.committee_justification`).
+- Amber button **"Issue decision"** (`dispute.committee_submit`, class `resolve-dispute-action`).
+
+On success: toast **"Dispute resolved"** (`dispute.resolved_success`) and market re-load after 1s.
+
+#### API: `resolve-dispute`
+
+- **Method:** POST `/api/resolve-dispute/`
+- **Fires when:** committee member clicks **"Issue decision"** (`resolve_dispute_action`, `app.js` 2739).
+- **Role/auth:** authenticated user with `user_arr['committee'] == 1`, else "Committee permission required". This is the single governance gate for the entire resolution.
+
+**Request fields — what the CLIENT currently sends** (`app.js` 2746):
+
+| Field | Type | Meaning |
+|---|---|---|
+| `dispute_id` | int | Dispute being resolved. |
+| `decision` | int | Value of the `committee_decision_type` select (0 = oracle right, 1 = oracle wrong). **Note:** the server does *not* read `decision`; it derives "oracle wrong" purely by comparing `correct_outcome` to the market's `resolved_outcome`. This field is effectively informational/legacy from the client. |
+| `correct_outcome` | int | The authoritative decision: `-1` = no-contest (refund everyone), `0` = outcome A wins, `1` = outcome B wins. Server rejects anything else with "Invalid correct outcome (-1, 0, or 1)". |
+| `committee_decision` | string | The justification text (`committee_text`). Stored as `disputes.committee_decision` and shown back in the dispute card. |
+
+**Additional request fields the SERVER accepts but the current UI does NOT send** (`api.php` 2100–2106). Node devs must map these as first-class governance parameters even though the prototype form omits them — the endpoint reads them via `intval($request[...])`, so they default to `0` when absent:
+
+| Field | Type | Meaning / server behaviour |
+|---|---|---|
+| `penalty_amount` | int | Extra insurance slash beyond the automatic dispute reward pool. Clamped to `>=0` and capped at the oracle's remaining `oracle_insurance`. If > 0, moved from oracle `oracle_insurance` into the `dao_fund_account_id` balance (history type `18`). Available for **any** outcome (committee discretion). |
+| `ban_oracle` | int (0/1) | If `1`, sets the oracle's `oracle_banned=1` and `oracle_ban_until` = `ban_oracle_until`; increments `oracle_bans_received`. |
+| `ban_oracle_until` | int | Unixtime the oracle ban lifts; `0` = permanent. |
+| `ban_creator` | int (0/1) | If `1`, sets the market creator's `creator_banned=1` and `creator_ban_until` = `ban_creator_until`. |
+| `ban_creator_until` | int | Unixtime the creator ban lifts; `0` = permanent. |
+
+**Full governance effect of a single `resolve-dispute` call** (enumerated so every possible on-chain transaction is covered):
+
+1. **Determine fault.** `oracle_was_wrong = (correct_outcome != market.resolved_outcome)`.
+2. **Fee distribution — oracle wrong:** compute `reward_pool = min(dispute_fee * dispute_reward_multiplier, oracle_insurance)` (multiplier default 3.0). Disputer is refunded `dispute_fee + disputer_reward` (`disputer_reward = reward_pool / multiplier`; history type `13` = "Dispute refund", payout type `5`). The remainder `voter_reward` goes to the settings-configured `dispute_resolver_account_id` (history type `20`, payout type `8`). `reward_pool` is slashed from the oracle's `oracle_insurance`.
+3. **Fee distribution — oracle right:** dispute fee is split by `dispute_rejected_voter_permille` (default 500 = 50%): resolver account gets `voter_share` (history type `20`, payout type `8`); the oracle gets the remaining `oracle_share` as compensation (history type `21`, payout type `9`). The disputer is **not** refunded.
+4. **Extra sanctions (any outcome):** apply `penalty_amount` slash to `dao_fund_account_id`; apply `ban_oracle` / `ban_creator` flags with their until-timestamps.
+5. **Payout recalculation:**
+ - `correct_outcome == -1` (no-contest): delete pending payouts, refund every active bet at face value (payout type `0`), refund every LP its principal (payout type `1`, lazy-pool principal returned to pool), set market `resolved_outcome=-1`, `payout_status=1`.
+ - `correct_outcome == 0|1`: recompute parimutuel payouts — winners get `bet + profit_share - time_penalty` (payout type `0`), oracle fee (payout type `2`), creator fee (payout type `7`), LP fee pool + penalty pool distributed to liquidity (payout type `1`); set `resolved_outcome` and `payout_status=1`.
+6. **Dispute record close-out:** `disputes.status` set to `1` (oracle wrong) or `2` (oracle right); stores `committee_decision`, `committee_user` (the acting member), `resolved_time`, and the echoed `penalty_amount` / `ban_*` fields.
+7. **Reputation:** increments oracle `oracle_disputes_lost` (+ lazy-pool fault stamp) or `oracle_disputes_won`; accumulates `oracle_total_insurance_slashed` by `reward_pool + penalty_amount`.
+8. Writes a `market_log` `dispute_resolve` entry summarising `correct_outcome`, `penalty_amount`, `ban_oracle`, `ban_creator`.
+
+**Response fields**
+
+| Field | Type | UI use |
+|---|---|---|
+| `status` | bool `true` | Success; toast "Dispute resolved" + market re-load. The UI reads nothing else — all recomputed payouts/bans are re-fetched via the subsequent `load_market_detail`. |
+
+**Error states:** "Committee permission required", "Invalid correct outcome (-1, 0, or 1)", "Dispute not found", "Dispute is not open", "Market not found", or "Dispute resolution failed: …" (transaction rollback). All shown via `show_message('danger', …)`.
+
+**VIZ thin-client note (suggestion):** This is the heaviest governance transaction and must be signed by a committee-authorised account. A single operation should carry: `dispute_id`, `correct_outcome` (−1/0/1), `justification`, and the discretionary `penalty_amount`, `ban_oracle` + `ban_oracle_until`, `ban_creator` + `ban_creator_until`. Broadcasting it must atomically trigger, on-chain/indexer: insurance slash + reward split to disputer and resolver/DAO-fund accounts, ban flags, full payout recomputation for the market, and dispute close-out. If governance moves to true multi-signer voting, model each committee member's submission as a separate `dispute_vote` op and resolve on quorum — the current single-submission flow is the minimum to replicate. The `decision` field can be dropped (server ignores it).
+
+### Data feed: `load-committees` (market creation, arbitrator picker)
+
+- **Method:** GET `/api/load-committees/`
+- **Fires when:** the create-market screen initialises (`app.js` ~920), to populate the **"Dispute judge:"** selector (`market.committee_label`; default option **"Not selected"**, `market.committee_not_selected`).
+- **Role/auth:** none (public GET).
+
+**Response:** a JSON array of objects `{ id, name }`, one per user with `committee==1` (max 100). `name` is the account handle, or a name/username derived from the user's `data` blob, or `User #` fallback. The client renders each as `{name} [#{id}] `.
+
+**VIZ thin-client note (suggestion):** the equivalent is a chain/indexer query returning accounts holding a "committee"/arbitrator flag, so the market creator can bind a committee at creation time (the `committee` field that later gates disputes).
+
+### Screen / action: Unban user (committee / governance)
+
+There is **no dedicated unban screen in the shipped `app.js`** — the `unban-user` endpoint exists server-side for governance tooling but no button in the reviewed client calls it. It is the inverse of the ban flags a committee can set via `resolve-dispute`. Documented here for completeness because node devs must map it as a governance transaction.
+
+#### API: `unban-user`
+
+- **Method:** POST `/api/unban-user/`
+- **Fires when:** governance/admin action (no UI trigger in current `app.js`).
+- **Role/auth:** `user_arr['committee'] == 1`, else "Committee permission required".
+
+**Request fields**
+
+| Field | Type | Meaning |
+|---|---|---|
+| `user_id` | int | Target user to unban. Must be > 0 and exist ("Invalid user ID" / "User not found"). |
+| `unban_oracle` | int (0/1) | Clear the target's oracle ban (`oracle_banned=0`, `oracle_ban_until=0`). |
+| `unban_creator` | int (0/1) | Clear the target's market-creator ban (`creator_banned=0`, `creator_ban_until=0`). |
+
+If both flags are `0`, server returns "Nothing to unban". Writes a `history` row type `19` for the target.
+
+**Response fields**
+
+| Field | Type | UI use |
+|---|---|---|
+| `status` | bool `true` | Success. |
+| `unbanned_oracle` | int (0/1) | Echo of which ban was cleared. |
+| `unbanned_creator` | int (0/1) | Echo of which ban was cleared. |
+
+**Error states:** "Committee permission required", "Invalid user ID", "User not found", "Nothing to unban", "Unban failed" (rollback).
+
+**VIZ thin-client note (suggestion):** a committee-signed op targeting an account, with two booleans (clear oracle ban / clear creator ban). Should reverse whatever `resolve-dispute` set. Emit an `unban` governance event for auditability.
+
+### Cross-role summary of dispute-related history / payout labels
+
+Reviewers verifying money movement will encounter these labels (from `i18n/en.json`), all produced by the flows above:
+
+| Code | Label | Where produced |
+|---|---|---|
+| history `12` | "Dispute payment" | disputer pays fee (`create-dispute`) |
+| history `13` | "Dispute refund" | disputer refunded when oracle wrong (`resolve-dispute`) |
+| history `14` | "Oracle penalty" | oracle insurance penalty |
+| history `15` | "Committee reward" | committee/resolver reward |
+| history `18` | (extra penalty → DAO fund) | `penalty_amount` slash in `resolve-dispute` |
+| history `19` | (unban marker) | `unban-user` |
+| history `20`/`21` | resolver share / oracle compensation | `resolve-dispute` |
+| payout type `4` | "Committee" | committee payout |
+| payout type `5` | "Dispute refund" | disputer refund |
+| payout type `6` | "Oracle penalty bonus" | penalty bonus |
+| market status | "Disputed" (`status.payout_3`) | market `payout_status==3` after `create-dispute` |
+## 9. Wallet, balance, history, liquidity pools & leverage
+
+This section documents the **Balance / Wallet tab**, transaction history, the user's positions & payouts views, liquidity provision (add/withdraw), the lazy pool endpoints, and the Boost (leverage) flow. All request/response field names below are taken verbatim from `app.js` and `module/api.php`.
+
+**Conventions used throughout:**
+- Amounts are internally in **milli-VIZ** (1 VIZ = 1000 units; `price_precision_ratio` = 1000). Inputs typed by the user in VIZ are multiplied by 1000 client-side (`Math.floor(x*1000)`) OR by the server (`floor(floatval(...)*1000)`) — noted per endpoint. The UI renders amounts back with `render_number()` and appends the `Ƶ` glyph.
+- All money endpoints in this section require `$auth` (a logged-in session cookie). Endpoints throw `Auth required` (HTTP 400 JSON `{success:false, error:...}`) when not authenticated.
+- `api_post(endpoint, body)` (app.js:2044) is the shared POST helper: it `POST`s JSON to `/api//`, parses JSON, and **throws `new Error(data.error)` on any non-2xx** — every caller shows that `.message` in a red/`negative-info` element.
+
+---
+
+### Screen: Balance / Wallet tab (`#tab/history`)
+
+Rendered in `select_tab()` at app.js:943-1015. The tab is internally named `history`; its title uses `balance.title` = **"Balance change history"**.
+
+**What the user sees (top to bottom):**
+1. Title **"Balance change history"** (`balance.title`).
+2. If not logged in: **"You are not logged in"** (`market.not_logged_in`) and nothing else.
+3. Available balance line (only if `user.balance` is defined): **"Available balance: %AMOUNT% Ƶ"** (`balance.available`), rendered green (`positive-balance`) from the cached `user['balance']` (no dedicated fetch — balance comes from session/user-data).
+4. Two toggle buttons: **"Top up"** (`balance.topup_button`, `rel="deposit"`) and **"Withdraw"** (`balance.withdraw_button`, `rel="withdraw"`), separated by " / ". Clicking a `.toggle-addon` (handler app.js:3144) expands an inline `.addon` panel rendered by `render_addon(rel)` (app.js:1304). Clicking again collapses it; only one addon is open at a time.
+5. A horizontal rule, then the **history table** with three columns: **Date** (`balance.table_date`), **Operation type** (`balance.table_type`), **Amount** (`balance.table_amount`). While loading it shows a spanning row **"Loading…"** (`balance.loading`).
+6. Footer note (`balance.history_note`): *"History shows the last 100 records. The balance is displayed considering the precision (1 VIZ = 1000 units)."*
+
+On tab render, the table body is filled by a **GET** to `load-history` (below). Each row's amount cell is colored `positive-balance` when `history.negative==0`, else `negative-balance`.
+
+**Operation-type labels** are looked up by numeric `type` into an array built from `balance.history_type_0..16` (app.js:987). The full map:
+
+| type | i18n key | English label |
+|---|---|---|
+| 0 | `balance.history_type_0` | VIZ Deposit |
+| 1 | `balance.history_type_1` | TON/USDT Conversion |
+| 2 | `balance.history_type_2` | Withdrawal |
+| 3 | `balance.history_type_3` | Bet |
+| 4 | `balance.history_type_4` | Bet cancellation |
+| 5 | `balance.history_type_5` | Bet result |
+| 6 | `balance.history_type_6` | Liquidity provision |
+| 7 | `balance.history_type_7` | Liquidity result |
+| 8 | `balance.history_type_8` | Oracle registration |
+| 9 | `balance.history_type_9` | Oracle insurance deposit |
+| 10 | `balance.history_type_10` | Oracle insurance withdrawal |
+| 11 | `balance.history_type_11` | Creator registration |
+| 12 | `balance.history_type_12` | Dispute payment |
+| 13 | `balance.history_type_13` | Dispute refund |
+| 14 | `balance.history_type_14` | Oracle penalty |
+| 15 | `balance.history_type_15` | Committee reward |
+| 16 | `balance.history_type_16` | Payout |
+
+> Note: the server also writes history rows with `type` 19 (unban), 20/21/22 (lazy-pool deposit/withdraw/emergency), 30/34 (leverage open/convert). These numeric types have **no label** in the `0..16` array, so they render as `undefined` in the table. The VIZ thin client should extend this label map if lazy-pool/leverage rows must be human-readable.
+
+**Error states:** if `load-history` returns non-OK, the loading row text becomes **"Failed to load history"** (`balance.load_error`) and a warning toast is shown.
+
+---
+
+#### `load-history` (GET)
+- **When:** on opening the Balance tab (app.js:976).
+- **Role/auth:** logged-in. If not `$auth`, server returns an empty array.
+- **Request:** none (GET, no query params).
+- **Response:** a JSON **array**, newest first (`ORDER BY id DESC LIMIT 100`). Each element (api.php:433):
+
+| field | type | UI use |
+|---|---|---|
+| `time` | int (unix seconds) | `new Date(time*1000).toLocaleString()` → Date column |
+| `type` | int | index into the label array above → Type column |
+| `negative` | int (0/1) | 1 → red amount, 0 → green amount |
+| `amount` | int (milli-VIZ) | `render_number(amount)+' Ƶ'` → Amount column |
+
+- **UI states:** empty array → single row **"No records"** (`balance.no_records`).
+
+> **VIZ thin-client note (suggestion):** history is a per-user ledger. On chain, the equivalent is the account's operation history — the thin client would query the node's `get_account_history`/custom-operation feed and map each op (transfer in/out, bet, liquidity op, leverage op) to these same type labels rather than reading a server table.
+
+---
+
+### Deposit (Top up) addon — `render_addon('deposit')`
+
+Rendered inline when **"Top up"** is clicked (app.js:1304-1332). This is a **display-only** panel: no `deposit` API endpoint exists; funding is done by sending VIZ/TON to a well-known account and the backend credits the balance asynchronously (`balance.deposit_processing_note`). Two sub-sections:
+
+**A) Convert TON or USDT** (`balance.deposit_ton_title`)
+- Instruction `balance.deposit_ton_instruction`.
+- **Recipient** (`balance.deposit_recipient`): a copyable input pre-filled with the constant `ton_deposit` (app.js:9 = `UQCyJpqQKDodPO_GyGyRmrJccp7-54idxXmdvnK1EbdnrPoD`).
+- **Comment** (`balance.deposit_comment`): a copyable input pre-filled with `user['memo']` — this memo is how the backend attributes the incoming transfer to the user.
+- If the `tonkeeper` bridge object exists: a **"Open transfer"** button (`balance.deposit_open_transfer`, class `tonkeeper-transfer`). Handler (app.js:3138) navigates to `ton://transfer/?text=` to open Tonkeeper.
+
+**B) Top up VIZ** (`balance.deposit_viz_title`)
+- Instruction `balance.deposit_viz_instruction`.
+- **Recipient** copyable input = constant `viz_deposit` (app.js:8 = `forecaster`).
+- **Comment** copyable input = `user['memo']`.
+- If the `vizonator` bridge exists: three preset buttons **10 / 100 / 1000 VIZ** (class `vizonator-transfer`, `rel="10.000 VIZ"` etc.). Handler (app.js:3132) calls `vizonator_transfer(rel, el)` (app.js:1355) which invokes `vizonator.transfer({to:viz_deposit, amount, memo:user['memo'], force_memo_encoding:false}, cb)`; on success it adds `.btn-success` to the button. This is an **in-wallet-extension broadcast**, not a Forecaster API call.
+- Footer rate note `balance.deposit_rate_note` (shows `viz_rate`).
+
+Copyable inputs carry classes `select-all-action copy-action`; the click handler (app.js:3158) copies the value to clipboard and appends a ✔️.
+
+> **VIZ thin-client note (suggestion):** the "VIZ top up" flow is literally a chain `transfer` operation to account `forecaster` with the user's memo. A native VIZ thin client would **sign & broadcast a `transfer` op** (from → `forecaster`, amount, memo = the deposit tag) directly via viz-js instead of showing a copyable address. The TON path is external and out of scope for the VIZ node.
+
+---
+
+### Withdraw addon — `render_addon('withdraw')`
+
+Rendered when **"Withdraw"** is clicked (app.js:1334-1348). Two sub-sections:
+
+**A) Withdraw VIZ to TON** (`balance.withdraw_ton_title`) — informational only: shows `balance.deposit_ton_token_note` ("The VIZ token launch on the TON network is planned for 2025."). No form, no endpoint.
+
+**B) Withdraw VIZ** (`balance.withdraw_viz_title`) — the working form:
+- Instruction `balance.withdraw_viz_instruction`.
+- Input **Recipient** (`name=account`, placeholder `balance.withdraw_recipient_placeholder`).
+- Input **Amount** (`name=amount`, placeholder `balance.withdraw_amount_placeholder`).
+- Input **Comment** (`name=memo`, placeholder `balance.withdraw_memo_placeholder`).
+- Button **"Confirm"** (`balance.withdraw_confirm`, class `withdraw-action`, `rel="viz"`).
+- A `.loading` line showing `balance.withdraw_wait` while pending.
+
+**Submit flow** (handler app.js:3091 → `withdraw()` app.js:1366): reads the three fields, POSTs to `withdraw-` i.e. `withdraw-viz`. On success the loading line shows `common.success` (green) and the Amount field is cleared; on error it shows `common.error_prefix + result.error` (red) and re-enables the button.
+
+#### `withdraw-viz` (POST)
+- **When:** user clicks **Confirm** in the Withdraw VIZ form.
+- **Role/auth:** logged-in.
+- **Request** (app.js:1375):
+
+| field | type | meaning / server handling |
+|---|---|---|
+| `account` | string | recipient VIZ account; server lowercases and strips to `[a-z0-9\-\.]` (api.php:781) |
+| `amount` | string | VIZ amount typed by user; server sanitizes, converts `,`→`.`, then `floor(value*1000)` → milli-VIZ |
+| `memo` | string | free-text comment stored on the withdraw record |
+
+- **Response** (api.php:798-814):
+
+| field | type | UI use |
+|---|---|---|
+| `status` | bool | `true` → success path; `false` → error path. The client computes `error = !result.status` |
+| `error` | string | present only when `status=false` (e.g. `"Недостаточно баланса"` / `"Withdraw failed"`); shown after `common.error_prefix` |
+
+- **Server side effects:** inserts a `withdraw` row (`type=0`), decrements `users.balance` by `amount`, writes a `history` row `type=2` (`negative=1`). Withdrawals are queued and processed off-band.
+- **UI/edge states:** if `amount >= balance` → `status=false, error="Недостаточно баланса"` (insufficient balance). A network/HTTP failure throws `try_check_session` inside `withdraw()` and the callback fires with `error=true`.
+
+> **VIZ thin-client note (suggestion):** a withdrawal maps to a chain `transfer` from the platform custodial account to `account` with `memo`. The custodial model means the server signs; a fully non-custodial VIZ client would instead not need a "withdraw" concept at all (the user already holds the funds). If keeping custody, the thin client just needs to submit `{account, amount, memo}` and poll balance.
+
+---
+
+### Screen: My Positions & My Payouts (profile)
+
+These two tables are rendered into `.my-positions` and `.my-payouts` containers by `load_positions()` (app.js:2883) and `load_payouts()` (app.js:2903), called when the profile/portfolio view loads.
+
+#### `load-positions` (POST)
+- **When:** profile render (`load_positions()`).
+- **Role/auth:** logged-in (throws `Auth required` otherwise).
+- **Request:** `{}` (no fields; user derived from session).
+- **Response:** JSON **array** of the user's bets with `status IN (0,3)` (active or resolved), newest first, LIMIT 100. Each row joins market columns. Fields consumed by the UI (app.js:2888-2896):
+
+| field | type | UI use |
+|---|---|---|
+| `market` | int | market id → link target (`clickable-market rel`) |
+| `market_q` | string | market question → link text (falls back to `#`) |
+| `side` | int (0/1) | picks `market_a`/`market_b` as the outcome name (binary) |
+| `market_a` / `market_b` | string | binary outcome labels |
+| `market_status` | int | index into `market_status_labels` → Status column |
+| `amount` | int (milli-VIZ) | Amount column (`render_number`) |
+| `expected_payout` | int (milli-VIZ) | Expected column; only shown if `>0` |
+| `expected_payout_approx` | int (0/1) | if `1`, prefixes the payout with **"≈ "** (floating parimutuel estimate); resolved markets show exact value |
+
+Column headers: **Market / Outcome / Amount / Status / Expected** (`table.market`, `table.outcome`, `table.amount`, `table.status`, `table.expected`).
+
+- **Empty/edge:** empty array → **"No active positions"** (`profile.no_active_positions`); a thrown error → generic `common.error`.
+- (Server also returns `resolved_amount`, `outcome_index`, `weight`, `final_payout`, and many raw market fields; the positions table above only uses the subset listed. `final_payout` is `{amount,status}` or null.)
+
+#### `load-payouts` (POST)
+- **When:** profile render (`load_payouts()`).
+- **Role/auth:** logged-in (throws `Auth required`).
+- **Request:** `{}`.
+- **Response:** JSON **array** of the user's `payouts` rows joined to market question, newest first LIMIT 100. Fields used (app.js:2908-2910):
+
+| field | type | UI use |
+|---|---|---|
+| `market` | int | link target |
+| `market_q` | string | link text (fallback `#`) |
+| `type` | int | index into `payout_type_labels` (see below) → Type column |
+| `amount` | int (milli-VIZ) | Amount column |
+| `status` | int | `1` → **"Paid"** (`payout.status_paid`), else **"Pending"** (`payout.status_pending`) |
+
+Column headers: **Market / Type / Amount / Status** (`table.market`, `table.type`, `table.amount`, `table.status`).
+
+**Payout type labels** (`payout.type_0..7`): 0 Win, 1 Liquidity, 2 Oracle, 3 Creator, 4 Committee, 5 Dispute refund, 6 Oracle penalty bonus, 7 Creator reward.
+
+- **Empty:** **"No payouts"** (`profile.no_payouts`).
+
+#### `load-market-payouts` (POST) — public per-market payouts
+- **When:** *No direct app.js caller.* The public payouts table shown on a market detail page (**"Payouts (N)"** `payouts_public.title`, rendered at app.js:1849-1863) is populated from the `all_payouts` array **embedded in the `load-market` detail response**, not from this endpoint. `load-market-payouts` exists as a standalone endpoint the VIZ client may call directly.
+- **Role/auth:** none required (public).
+- **Request:** `{ market_id: int }`.
+- **Response:** JSON **array** of `payouts` rows for that market (`ORDER BY id DESC LIMIT 200`), all columns. The market-detail public table uses per-row: `user` (+ optional `user_data.{username,first_name}` for display name), `type` (→ `payout_type_labels`), `amount`, `status` (0 Pending / 1 Paid / else Error via `payout.status_error`). Columns: **User / Type / Amount / Status**.
+
+> **VIZ thin-client note (suggestion):** positions and payouts are derived state. On chain they correspond to the user's open bet operations and the settlement/payout operations emitted at market resolution. The thin client can reconstruct "My positions" from the user's bet ops filtered by market status, and "My payouts" from resolution payout ops — mirroring the `type` label taxonomy above.
+
+---
+
+### Liquidity provision (on the market detail page)
+
+Liquidity UX lives on the **market detail** screen (not the wallet tab). Three related blocks are rendered by `load_market_detail`:
+
+**1) Add-liquidity form** (app.js:1747-1755) — shown only when the market is active (`status==1`), the user is logged in, `user['add_liquidity']==1`, and betting has not expired. Contains:
+- Heading **"Add liquidity"** (`liquidity.add_title`).
+- Number input `name=liq_amount` (placeholder `liquidity.amount_placeholder` = "Amount VIZ", step 0.001).
+- Button **"Add"** (`liquidity.add_button`, class `add-liquidity-action`).
+- A `.liq-loading` line.
+
+Submit (handler app.js:3262 → `add_liquidity_action()` app.js:2290): reads `liq_amount`, calls `add-liquidity`, on success shows **"Liquidity added!"** (`liquidity.added_success`, green), refreshes user data and reloads the market after 1s.
+
+**2) My liquidity** (app.js:1794-1803) — shown when the market-detail response contains `data.my_liquidity[]`. For each position renders a `liquidity-card`: **"Amount: %AMOUNT% Ƶ, Status: %STATUS%"** (`liquidity.amount_display`), plus **", Earned: %AMOUNT% Ƶ"** (`liquidity.earned_display`) when `earned_fee>0`. Status labels array: 0 Active (`liquidity.status_active`), 1 "—" (`liquidity.status_1`), 2 Returned (`liquidity.status_returned`), 3 Resolved (`liquidity.status_resolved`). Fields consumed: `amount`, `status`, `earned_fee`.
+
+**3) All liquidity (public)** (app.js:1830-1846) — public providers table **"Liquidity providers (N)"** (`liquidity.providers_title`). Columns **User / Amount / Time to expir. / Earned / Status** (`liquidity.table_*`). Per row uses `user` (+ `user_data`), `amount`, `sec_to_expiration` (rendered as hours if >3600s else seconds, using `liquidity.hours`/`liquidity.seconds`), `earned_fee`, `status`.
+
+The LP creation `lp_reward` (`market.lp_reward`) fee is set at market-create time (hidden `liquidity_fee=5`, i.e. 0.5%) — see the create-market screen; LPs earn a share of that fee, surfaced as `earned_fee` above.
+
+#### `add-liquidity` (POST)
+- **When:** user clicks **Add** in the add-liquidity form.
+- **Role/auth:** logged-in **and** `user_arr.add_liquidity==1` (server throws `No liquidity permission` otherwise).
+- **Request** (app.js:2296):
+
+| field | type | meaning |
+|---|---|---|
+| `market_id` | int | target market |
+| `amount` | string/number | VIZ amount; server does `floor(value*1000)` → milli-VIZ |
+
+- **Response** (api.php:1537):
+
+| field | type | UI use |
+|---|---|---|
+| `status` | bool `true` | success (the client only inspects thrown errors; a 400 throws) |
+| `liquidity_id` | int | id of the new `liquidity` row (not displayed) |
+
+- **Server side effects:** splits `amount` across `reserve_a`/`reserve_b` proportionally, updates market reserves/`k`/`liquidity_sum`, decrements balance, writes `history` type=6, and a `market_log` `liquidity_add` row.
+- **Edge/errors thrown (shown via toast/loading line):** `Invalid amount`, `Market not found`, `Market is not active`, `Betting period expired`, `Insufficient balance`.
+
+#### `withdraw-liquidity` (POST)
+- **When:** *No app.js caller wires this button in the reviewed code* — the endpoint is fully implemented server-side and is available for the VIZ client to call (LPs currently see their positions read-only in "My liquidity"). Documented here for coverage.
+- **Role/auth:** logged-in; the position must belong to the caller and be `status==0`.
+- **Request:**
+
+| field | type | meaning |
+|---|---|---|
+| `liquidity_id` | int | the `liquidity` row to withdraw |
+| `withdraw_amount` | string/number (optional) | VIZ to withdraw; server `floor(value*1000)`. If `<=0` or `>=` the position amount → **full** withdrawal; otherwise partial with proportional weight reduction |
+
+- **Response** (api.php:1643-1650):
+
+| field | type | meaning |
+|---|---|---|
+| `status` | bool `true` | success |
+| `returned` | int (milli-VIZ) | principal withdrawn + `fee_share` returned to balance |
+| `fee_share` | int (milli-VIZ) | time-weighted fee earned on the withdrawn amount |
+| `time_ratio` | float | fraction of market duration the LP was in (fee discount factor) |
+| `is_full` | bool | whether the whole position was withdrawn |
+| `remaining_amount` | int | present only when partial: leftover LP principal |
+
+- **Edge/errors:** `Liquidity position not found`, `Not your liquidity position`, `Liquidity position is not active`, `Market is not active`, `Betting period expired, withdrawal not possible`, `Withdrawal amount too small`, `Cannot withdraw: would deplete reserves`, `Cannot withdraw: would reduce market liquidity below minimum (100 VIZ)`.
+
+> **VIZ thin-client note (suggestion):** add/withdraw liquidity are custom market operations that adjust AMM reserves. On chain they'd be two custom ops (`liquidity_add` / `liquidity_withdraw`) carrying `{market_id, amount}` / `{liquidity_id, withdraw_amount}`; the node computes reserve split and fee share deterministically. The thin client should surface a Withdraw button per "My liquidity" card wired to `withdraw-liquidity`.
+
+---
+
+### Lazy pool (endpoints only — no UI wired in reviewed app.js)
+
+The **lazy pool** is a shared yield pool: users deposit VIZ for locked shares that earn rewards and back leverage loans. The four endpoints below are fully implemented in `module/api.php` but **have no caller in the reviewed `app.js`** (the Boost feature reads leverage-fund availability indirectly via `leverage-preview`). They are documented so the VIZ client can build the lazy-pool screen. All require login (`Auth required`), except `lazy-pool-info` which is partly public.
+
+#### `lazy-pool-deposit` (POST)
+- **Request:** `{ amount: string/number }` — VIZ, server `floor(value*1000)` → milli-VIZ.
+- **Response** (api.php:2487-2491): `status` (bool), `deposit_id` (int), `shares` (int, minted shares — 1:1 on first pool deposit else pro-rata to `free_balance`), `unlock_time` (unix, = now + lock_days×86400), `lock_days` (int, from settings, default 30).
+- **Side effects:** debits `users.balance`, credits `lazy_pool_balance`/`lazy_pool_shares`, inserts locked `lazy_pool_deposits` row (`status=0`), history type=20.
+- **Errors:** `Invalid amount`, `Insufficient balance`, `Pool has no free balance`, `Deposit too small`.
+
+#### `lazy-pool-withdraw` (POST)
+- **Request:** `{ withdraw_percent: float (optional, default 100; clamped 0<..<=100) }`. Withdraws from the user's single **unlocked** deposit record (`status=1`).
+- **Response** (api.php:2570-2575): `status`, `payout` (int milli-VIZ, = share value + reward portion), `share_value` (int), `reward_portion` (int), `shares_burned` (int), `is_full` (bool).
+- **Side effects:** burns shares, credits balance, history type=21.
+- **Errors:** `No unlocked shares available for withdrawal`, `Nothing to withdraw`, `Pool is empty`, `Insufficient pool free balance`.
+
+#### `lazy-pool-emergency-withdraw` (POST)
+- **Request:** `{}` — withdraws **all** of the user's shares (locked + unlocked) immediately, paying a penalty on the profit attributable to still-locked shares.
+- **Response** (api.php:2651-2656): `status`, `total_payout` (int milli-VIZ), `penalty` (int), `penalty_percent` (int, from settings), `locked_shares` (int), `profit` (int).
+- **Side effects:** marks all deposits `status=3`, penalty stays in pool (raises `reward_per_share` for remaining participants), zeroes the user's lazy-pool columns, history type=22.
+- **Errors:** `No shares in lazy pool`.
+
+#### `lazy-pool-info` (POST)
+- **Role/auth:** the `pool` and `active_allocations` sections are **public**; `user`/`deposits` sections and `oracle_penalty` are only added when `$auth`.
+- **Request:** `{}`.
+- **Response** (api.php:2666-2723):
+ - `pool`: `{ total_shares, free_balance, allocated_balance, total_pool_value (=free+allocated), reward_per_share }` (all int).
+ - `active_allocations[]`: per market allocation `{ market, amount, original_amount, recalled_amount, check_step, last_check_time, market_bets_sum }`.
+ - `oracle_penalty` (only if caller `is_oracle`): `{ active_stamps, oracle_multiplier }`.
+ - `user` (auth only): `{ lazy_pool_shares, lazy_pool_balance, lazy_pool_reward_snapshot, lazy_pool_pending_rewards }`.
+ - `deposits[]` (auth only): each `{ id, amount, shares, unlock_time, status (0 locked / 1 unlocked), time }`.
+ - `status`: `true`.
+
+> **VIZ thin-client note (suggestion):** the lazy pool is a share-accounting vault (deposit → shares, withdraw after lock, emergency-exit with penalty). On chain these map to custom vault ops; the node holds `total_shares`/`free_balance`/`reward_per_share` as global state and the per-user share/lock records. The thin client would render a pool dashboard from `lazy-pool-info` and offer deposit/withdraw/emergency actions.
+
+---
+
+### Boost / Leverage (on the market detail page)
+
+Boost lets a user open a **leveraged bet** funded partly by a loan from the lazy pool. All UI lives on the market detail page. The Boost form renders (`render_boost_form`, app.js:2313) only when the market is active (`status==1`), the user is logged in, and betting hasn't expired (app.js:1742). The user's active boosted positions render separately in the profile via `load_boost_info()` (app.js:2651).
+
+**Boost form contents** (`render_boost_form`):
+- Heading 🚀 **"Boost Your Bet"** (`boost.title`).
+- Outcome selector: for multi markets a `select[name=boost_outcome]` of outcome labels; for binary a `select[name=boost_side]` (A/B).
+- **"Your collateral:"** (`boost.your_collateral`) number input `name=boost_collateral` (placeholder `boost.collateral_placeholder`, step 0.001).
+- **Boost slider** `input[type=range name=boost_leverage]` in **0.01× units** (100 = 1.00×), min 100, initially disabled/max 100. Labels **"no boost"** … **1.00×** … **"max available"** (`boost.no_boost`, `boost.max_available`). A CSS `--danger-start` marks the danger zone.
+- A `.boost-detail-box` populated after preview.
+- **"Slippage tolerance:"** (`boost.slippage_tolerance`) chips 0.5% / 1% / 2% / 3% (`slippage-chip`, default 2% active; handler app.js:3346 toggles `.active`).
+- **Auto-close acknowledgment** checkbox `name=boost_auto_close_ack` (label text `boost.auto_close_checkbox` with the auto-close DATE and buffer HOURS). Must be checked to enable Open.
+- **"Open Boosted Position"** button (`boost.open_button`, class `boost-open-action`, starts `disabled`).
+
+**Live preview:** whenever collateral changes, `boost_fetch_preview(form)` (app.js:2353) calls `leverage-preview`. The response either drives the slider/detail box or shows an **"Boost unavailable"** panel (`boost.unavailable`, reason from `failed_constraints`).
+
+As the slider moves, `boost_render_detail` (app.js:2406) interpolates between `slider_stops` and shows a table: **Pool loan / interest / Total bet / Current position value / Auto-close at** (`boost.pool_loan`, `boost.interest`, `boost.total_bet`, `boost.current_value`, `boost.auto_close_at`) plus a risk note (`boost.risk_note`), info note (`boost.info_note`), safety note (`boost.safety_note`), and a **"Danger zone"** warning (`boost.danger_zone`) when leverage ≥ `danger_zone_leverage`.
+
+#### `leverage-preview` (POST)
+- **When:** collateral input changes (`boost_fetch_preview`).
+- **Role/auth:** logged-in.
+- **Request** (app.js:2363):
+
+| field | type | meaning |
+|---|---|---|
+| `market_id` | int | target market |
+| `collateral` | int | `Math.floor(collateral_VIZ*1000)` (client already multiplies; server multiplies again by 1000 — see edge note) |
+| `outcome_index` | int | chosen outcome (0/1 for binary) |
+
+- **Response** (api.php:3437; body = `ok:true` plus all keys from `leverage_compute_max_leverage`). Fields the UI consumes (app.js:2382-2450):
+
+| field | type | UI use |
+|---|---|---|
+| `unavailable_reason` | string (optional) | if present → render "Boost unavailable" panel, disable slider/open |
+| `failed_constraints[]` | array of `{constraint, reason}` (optional) | reason text via `boost_format_failed_constraints` (priority: `min_market_liquidity` > `market_liquidity` > `fund_availability` > `position_size`); full list in a `` |
+| `max_leverage` | int (0.01 units) | slider max; `100` default |
+| `danger_zone_leverage` | int (0.01 units, optional) | computes `--danger-start` CSS %; danger warning threshold |
+| `auto_close_date` | string | shown in auto-close checkbox and notes |
+| `expiration_buffer_hours` | int | hours-before-resolution shown in notes (default 24) |
+| `safety_margin_percent` | number | shown in safety note (default 1) |
+| `liquidation_threshold` | int | fallback threshold for interpolated stops |
+| `slider_stops[]` | array of `{leverage_01, loan, total_bet, pool_profit, current_cancel_value, liquidation_threshold, expected_tokens}` | per-leverage detail table + slippage baseline |
+
+> **Edge note:** the client sends `collateral` **already ×1000** (app.js:2363) and the server does `floor(floatval(collateral)*1000)` **again** (api.php:3424). Treat this as an existing quirk to reconcile in the new client (likely the intended contract is raw VIZ in, ×1000 on server).
+
+#### `leverage-open` (POST)
+- **When:** user clicks **Open Boosted Position** (`boost_open_action`, app.js:2476).
+- **Role/auth:** logged-in.
+- **Request** (app.js:2493):
+
+| field | type | meaning |
+|---|---|---|
+| `market_id` | int | target market |
+| `outcome_index` | int | chosen outcome |
+| `collateral` | int | `Math.floor(collateral_VIZ*1000)` (server multiplies again by 1000) |
+| `leverage` | float | selected leverage as X.XX (from slider/100); server requires ≥1.01 |
+| `max_slippage_percent` | float | from the active slippage chip (default 2) |
+
+- **Response** (api.php:3545-3551): `ok`, `position_id` (int), `bet_id` (int), `tokens` (int), `loan` (int milli-VIZ), `total_bet` (int milli-VIZ), `liquidation_threshold` (int). On success the loading line shows **"Boosted position opened! Total bet: %AMOUNT% Ƶ"** (`boost.opened_success`) using `total_bet`, refreshes user data, reloads market after 1s.
+- **Side effects:** places the underlying bet (CPMM or LMSR), inserts a `leveraged_positions` row, debits collateral from balance, draws `loan` from the lazy pool (`leverage_fund_used += loan`), history type=30.
+- **Errors thrown (shown red):** `Invalid collateral`, `Leverage too low (min 1.01x)`, `Market is not active`, `Too close to expiration for leverage`, `Insufficient balance`, `Requested leverage Nx exceeds maximum Mx`, `Trade too small`, slippage failure (`Tokens received (...) below minimum (...). Slippage too high.`), or safety failure (`Position fails safety check: cancel_value < liquidation_threshold`).
+
+#### Active boosts table & actions — `load_boost_info()`
+
+`leverage-info` populates the **"Your Active Boosts"** table (`boost.info_title`) in `.boost-positions-container` (app.js:2667-2704). Columns: **Market / Outcome / Boost / Collateral / Value / Auto-closes: / Actions** (`boost.info_*`). Each row shows leverage `= total_bet/collateral` as `N.N×`, collateral, `cancel_value`, and the auto-close date + live countdown (`boost.info_countdown`, `format_countdown`). Two action buttons per row: **"Close"** (`boost.info_close_btn`, class `boost-close-btn`) and **"Convert"** (`boost.info_convert_btn`, class `boost-convert-btn`). It also fills `boost_active_market_ids` so market cards can show a boost banner. Empty → **"No active boosted positions"** (`boost.info_no_positions`).
+
+#### `leverage-info` (POST)
+- **When:** on profile/market render (`load_boost_info`, `select_tab` app.js:520).
+- **Role/auth:** logged-in.
+- **Request:** `{}`.
+- **Response** (api.php:3757-3759): `ok`, `active_count` (int), and `positions[]` where each (api.php:3741):
+
+| field | type | UI use |
+|---|---|---|
+| `position_id` | int | data attr for Close/Convert buttons |
+| `market_id` | int | market link `#` |
+| `outcome_index` | int | Outcome column |
+| `collateral` | int | Collateral column; also `leverage = total_bet/collateral` |
+| `total_bet` | int | leverage numerator |
+| `leverage` | float | (also provided server-side, 2dp) |
+| `cancel_value` | int | Value column |
+| `liquidation_threshold` | int | (context) |
+| `current_profit` | int | (context) |
+| `auto_close_date` | string/null | Auto-closes column |
+| `countdown_seconds` | int | live countdown; also drives per-market boost banners |
+| `open_time` | int | (context) |
+
+#### Close flow — `leverage-close-preview` → `leverage-close`
+
+Clicking **Close** calls `boost_close_action(position_id)` (app.js:2502) → `leverage-close-preview`, then renders a modal **"Close Boosted Position"** (`boost.close_title`) with a warning (`boost.close_warning`) and a breakdown table.
+
+##### `leverage-close-preview` (POST)
+- **Request:** `{ position_id: int }`.
+- **Response** (api.php:3577-3591). Fields the modal uses (app.js:2507-2551):
+
+| field | type | UI use |
+|---|---|---|
+| `cancel_value` | int | **"Position value (cancel):"** (`boost.close_position_value`) |
+| `pool_obligation` | int | **"Pool receives:"** (`boost.close_pool_receives`) |
+| `bettor_receives` | int | **"You receive:"** (`boost.close_you_receive`); also becomes `data-min-return` on confirm |
+| `collateral` | int | **"Your collateral:"** (`boost.close_your_collateral`) |
+| `loss` *(read as `data.loss`)* | int | drives loss section & `loss_pct`; NOTE server key is `loss_vs_collateral` (see edge) |
+| `leverage` | number | in warning text; NOTE server does not return `leverage` here (see edge) |
+| `loan_repayment` | int (optional) | **"Loan repayment:"** (`boost.close_loan_repayment`) — server does not return this key (see edge) |
+| `pool_profit_charge` | int (optional) | **"Pool profit:"** (`boost.close_pool_profit`) — server does not return top-level; it's inside `loss_breakdown` |
+| `loss_breakdown.market_loss` | int | **"Market moved against you:"** (`boost.close_market_moved`); server key is `market_movement_loss` (see edge) |
+| `loss_breakdown.pool_profit_charge` | int | **"Pool profit charge:"** (`boost.close_profit_charge`) + 1× comparison (`boost.close_comparison`) |
+
+> **Edge note (field-name drift to reconcile):** the modal reads `data.loss`, `data.leverage`, `data.loan_repayment`, `data.pool_profit_charge` and `loss_breakdown.market_loss`, but the server (api.php:3579-3591) returns `loss_vs_collateral`, no `leverage`, no `loan_repayment`, `loss_breakdown.market_movement_loss`, and `loss_breakdown.pool_profit_charge`/`total_loss`/`total_loss_pct`. The new VIZ client should align on the server names (or the server should be adjusted) — several close-modal figures currently read as `0`/`undefined`.
+
+On confirm, **"Confirm Close"** (`boost.close_confirm_button`) carries `data-position` and `data-min-return` (= `bettor_receives`) → `boost_close_confirmed` (app.js:2568).
+
+##### `leverage-close` (POST)
+- **Request** (app.js:2574): `{ position_id: int, min_return: int }` (`min_return` from the preview's `bettor_receives`; a slippage floor).
+- **Response** (api.php:3623-3627): `ok`, `pool_received` (int), `bettor_received` (int), `cancel_value` (int), optional `warning` ("Position was already closed"). On success shows **"Boosted position closed. You received: %AMOUNT% Ƶ"** (`boost.close_success`) using `bettor_received`, refreshes user data, reloads market.
+- **Errors:** `Position not found`, `Position is not active`, voluntary-close guard (`Position cannot be voluntarily closed: cancel_value < liquidation_threshold. Position will be liquidated by the protocol.`), or `Return amount (...) below minimum (...)`.
+
+#### Convert flow — `leverage-convert-preview` → `leverage-convert`
+
+Clicking **Convert** calls `boost_convert_action(position_id)` (app.js:2586) → `leverage-convert-preview`, then renders modal 🔄 **"Convert Boost to Normal Bet"** (`boost.convert_title`).
+
+##### `leverage-convert-preview` (POST)
+- **Request:** `{ position_id: int }`.
+- **Response** (api.php:3652-3660). Fields used by the modal (app.js:2590-2611):
+
+| field | type | UI use |
+|---|---|---|
+| `cancel_value` | int | **"Current position value:"** (`boost.convert_current_value`) |
+| `current_profit` | int | **"Your unrealized profit:"** (`boost.convert_unrealized_profit`) — read as `data.current_profit` |
+| `pool_obligation` | int | **"Loan + pool profit:"** (`boost.convert_loan_profit`) |
+| `conversion_fee` | int | **"Conversion fee (PCT%):"** (`boost.convert_fee`) |
+| `total_user_payment` | int | **"Total:"** (`boost.convert_total`) — read as `data.total_user_payment` |
+| `conversion_profit_cost` | number | fee % shown, and echoed back on confirm (`data-conv-pct`) |
+| `leverage` | number | intro text — NOTE server does not return `leverage` here |
+
+The modal lists post-conversion effects (`boost.convert_after_*`) and a risk warning (`boost.convert_risk`). Confirm button **"Convert to Normal Bet"** (`boost.convert_confirm_button`) carries `data-conv-pct` → `boost_convert_confirmed` (app.js:2633).
+
+##### `leverage-convert` (POST)
+- **Request** (app.js:2639): `{ position_id: int, conversion_profit_cost: number }` (must match server setting within 0.01 or it throws `conversion_profit_cost mismatch`).
+- **Response** (api.php:3716-3720): `ok`, `position_id`, `total_paid` (int), `conversion_fee` (int), `pool_profit` (int). On success shows **"Position converted to normal bet!"** (`boost.convert_success`), refreshes user data, reloads market.
+- **Side effects:** marks position `status=5`, debits `total_user_payment` from balance, repays loan to pool, keeps the underlying bet as a normal 100%-owned bet, history type=34.
+- **Errors:** `Position not found`, `Position is not active`, `Position has no unrealized profit. Conversion not available.`, `Insufficient balance for conversion. Need: N VIZ`.
+
+#### `leverage-pool-state` (POST)
+- **When:** *No app.js caller in the reviewed code* — available for a leverage/pool dashboard.
+- **Role/auth:** logged-in.
+- **Request:** `{}`.
+- **Response** (api.php:3767-3772): `ok`, `leverage_fund_total`, `leverage_fund_available`, `leverage_fund_used`, `free_balance`, `earned_balance`, `free_amount` (= `free_balance − leverage_fund_used`) — all int milli-VIZ.
+
+> **VIZ thin-client note (suggestion):** a boosted position = a normal bet op + a loan drawn from the lazy pool, plus a protocol-enforced liquidation threshold. On chain the client would (1) query `leverage-preview` equivalent (deterministic max-leverage/quote from reserves + pool free fund), (2) broadcast an `open_leveraged_position` op `{market_id, outcome_index, collateral, leverage, max_slippage}`, and (3) later a `close`/`convert` op referencing `position_id`. The node enforces the safety check (`cancel_value ≥ liquidation_threshold`) and auto-liquidation at `auto_close_date`. Reconcile the preview/close field names noted above before implementing.
+## 10. Master API / transaction coverage matrix
+
+Every `/api//` call the current client makes, grouped by area. Node developers should map each **write** endpoint to a signed VIZ operation (or off-chain-then-settled action) and each **read** endpoint to a chain/index query, and tick that all are covered. `M` = HTTP method. `Role`: A=any authenticated, P=player, C=creator, O=oracle, L=LP, D=disputer, J=arbitrator/committee/DAO, G=guest/unauth ok. Detailed request/response contracts are in the referenced section.
+
+### 10.1 Session, identity, config, preferences (§2)
+
+| Endpoint | M | Kind | Role | Fires when | § |
+|----------|---|------|------|-----------|---|
+| `check-session` | POST | write | G | Telegram initData present on load | 2 |
+| `auth-code` | GET | read | G | Website login (no Telegram) — get one-time code | 2 |
+| `load-session` | POST | read | A | Every app load, and after any balance-changing action (`update_user_data`) | 2 |
+| `load-settings` | GET | read | G | App boot | 2 |
+| `update-user-preferences` | POST | write | A | Save jurisdiction/country + language | 2 |
+| `register-oracle` | POST | write | A→O | Become / update oracle settings | 2, 7 |
+| `register-creator` | POST | write | A→C | Become market creator | 2, 6 |
+| `load-oracle-profile` | POST | read | G | Open oracle picker / oracle public profile | 2, 6, 7 |
+
+### 10.2 Browse & filter (§3)
+
+| Endpoint | M | Kind | Role | Fires when | § |
+|----------|---|------|------|-----------|---|
+| `load-markets` | POST | read | G | Market list load / filter / paginate | 3 |
+| `load-markets-catalog` | POST | read | G | Catalog/enriched list variant | 3 |
+| `load-categories` | POST | read | G | App boot (filter chips + create form) | 3 |
+| `load-oracles` | GET | read | G | Create form oracle dropdown | 3, 6 |
+| `load-committees` | GET | read | G | Create form committee dropdown | 3, 6, 8 |
+
+### 10.3 Market detail & binary betting (§4)
+
+| Endpoint | M | Kind | Role | Fires when | § |
+|----------|---|------|------|-----------|---|
+| `load-market-enriched` | POST | read | G | Open market detail | 4 |
+| `load-market` | POST | read | G | Detail refresh / lightweight reload | 4 |
+| `place-bet` | POST | write | P | Place binary bet (instant / batch) | 4 |
+| `commit-bet` | POST | write | P | Hidden bet — commit phase (client hash) | 4 |
+| `reveal-bet` | POST | write | P | Hidden bet — reveal phase | 4 |
+| `cancel-bet` | POST | write | P | Cancel a binary bet | 4 |
+| `transfer-position` | POST | write | P | Transfer a position to another user | 4 |
+
+### 10.4 Multi-outcome markets (§5)
+
+| Endpoint | M | Kind | Role | Fires when | § |
+|----------|---|------|------|-----------|---|
+| `create-market-multi` | POST | write | C | Create Onix Multi market | 5, 6 |
+| `place-bet-multi` | POST | write | P | Bet on an outcome index | 5 |
+| `cancel-bet-multi` | POST | write | P | Cancel a multi bet | 5 |
+| `add-liquidity-multi` | POST | write | L | Add LP to a multi market | 5, 9 |
+| `withdraw-liquidity-multi` | POST | write | L | Withdraw LP from a multi market | 5, 9 |
+| `resolve-market-multi` | POST | write | O | Oracle resolves winning outcome index | 5, 7 |
+
+### 10.5 Market creation (§6)
+
+| Endpoint | M | Kind | Role | Fires when | § |
+|----------|---|------|------|-----------|---|
+| `create-market` | POST | write | C | Create Onix Binary market | 6 |
+
+### 10.6 Oracle role & resolution (§7)
+
+| Endpoint | M | Kind | Role | Fires when | § |
+|----------|---|------|------|-----------|---|
+| `oracle-deposit-insurance` | POST | write | O | Fund oracle insurance | 7 |
+| `oracle-withdraw-insurance` | POST | write | O | Withdraw oracle insurance | 7 |
+| `oracle-accept-market` | POST | write | O | Accept an assigned market | 7 |
+| `oracle-reject-market` | POST | write | O | Reject an assigned market | 7 |
+| `load-pending-markets` | POST | read | O | Oracle's pending-acceptance queue | 7 |
+| `resolve-market` | POST | write | O | Resolve binary market outcome | 7 |
+| `oracle-no-contest` | POST | write | O | Declare no-contest (refund) | 7 |
+
+### 10.7 Disputes & governance (§8)
+
+| Endpoint | M | Kind | Role | Fires when | § |
+|----------|---|------|------|-----------|---|
+| `create-dispute` | POST | write | D | Bettor opens a dispute | 8 |
+| `dispute-oracle-response` | POST | write | O | Oracle rebuts a dispute | 8 |
+| `resolve-dispute` | POST | write | J | Committee/DAO/private resolver decides | 8 |
+| `unban-user` | POST | write | J | Governance lifts a ban | 8 |
+
+### 10.8 Wallet, history, positions, LP, leverage (§9)
+
+| Endpoint | M | Kind | Role | Fires when | § |
+|----------|---|------|------|-----------|---|
+| `load-history` | GET | read | A | Balance tab transaction history | 9 |
+| `load-positions` | POST | read | P | "My positions" | 9 |
+| `load-payouts` | POST | read | P | "My payouts" | 9 |
+| `load-market-payouts` | POST | read | G | Per-market payout breakdown | 9 |
+| `withdraw-viz` | POST | write | A | Withdraw VIZ to external address | 9 |
+| `add-liquidity` | POST | write | L | Add LP (binary) | 9 |
+| `withdraw-liquidity` | POST | write | L | Withdraw LP (binary) | 9 |
+| `lazy-pool-deposit` | POST | write | L | Lazy pool deposit | 9 |
+| `lazy-pool-withdraw` | POST | write | L | Lazy pool withdraw | 9 |
+| `lazy-pool-emergency-withdraw` | POST | write | L | Lazy pool emergency exit | 9 |
+| `lazy-pool-info` | POST | read | A | Lazy pool state for user | 9 |
+| `leverage-preview` | POST | read | L | Preview opening a boost | 9 |
+| `leverage-open` | POST | write | L | Open a boosted position | 9 |
+| `leverage-close-preview` | POST | read | L | Preview closing a boost | 9 |
+| `leverage-close` | POST | write | L | Close a boosted position | 9 |
+| `leverage-convert-preview` | POST | read | L | Preview convert boost→bet | 9 |
+| `leverage-convert` | POST | write | L | Convert boost to plain bet | 9 |
+| `leverage-info` | POST | read | A | User's boost positions (card banners) | 9 |
+| `leverage-pool-state` | POST | read | G | Leverage pool global state | 9 |
+
+**Total: 58 endpoints** (39 write / transaction-bearing, 19 read/query).
+
+### 10.9 Transaction-type ledger (history)
+
+The balance history renders 17 core types plus 5 boost types (`balance.history_type_*`). Each is a settled balance movement the thin client must be able to reconstruct from chain/indexer events:
+
+`0` VIZ Deposit · `1` TON/USDT Conversion · `2` Withdrawal · `3` Bet · `4` Bet cancellation · `5` Bet result · `6` Liquidity provision · `7` Liquidity result · `8` Oracle registration · `9` Oracle insurance deposit · `10` Oracle insurance withdrawal · `11` Creator registration · `12` Dispute payment · `13` Dispute refund · `14` Oracle penalty · `15` Committee reward · `16` Payout · `30` Boost opened · `31` Boost liquidated · `32` Boost resolved (won) · `33` Boost resolved (lost) · `34` Boost converted to bet.
diff --git a/.qoder/docs/pm-workflow/resolver-account/resolver-account.md b/.qoder/docs/pm-workflow/resolver-account/resolver-account.md
new file mode 100644
index 0000000000..36682b53b1
--- /dev/null
+++ b/.qoder/docs/pm-workflow/resolver-account/resolver-account.md
@@ -0,0 +1,53 @@
+# Resolver — single account (centralized, dispute_mode = 1)
+
+Part of [PM Workflow](../README.md). In **account mode** the market names one `dispute_resolver`
+account (e.g. a regulator multisig) that decides disputes alone — **no stake weight, no DAO vote**. It
+is set at creation and must differ from both `oracle` and `creator` (anti self-judging). The protocol
+stays neutral: same op set as committee mode, only *who decides* differs.
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ disp -->|pm_dispute_create| D[(dispute, mode=1)]
+ resolver -->|pm_dispute_resolve correct_outcome=B penalty_amount, ban flags| FIN[[verdict]]
+ FIN -->|slash penalty_amount| orac[oracle.insurance ↓]
+ FIN -->|fee + reward| disp
+ FIN --> AUTO[[pm_auto_payout settles B]]
+```
+
+## Operations it sends (signed)
+- `pm_dispute_resolve` — **auth `active` of the named `dispute_resolver`** only. Fields:
+ `correct_outcome`, `penalty_amount` (insurance to slash — a fixed amount, **not** stake-scaled),
+ `ban_oracle` + `ban_oracle_until`, `ban_creator` + `ban_creator_until`.
+
+## Virtual operations
+- `pm_auto_payout` — settles on the resolver's chosen outcome after grace.
+
+## Tokens
+| Actor | sends | receives |
+|-------|-------|----------|
+| resolver | 0 | **0** — a neutral arbiter; it sets the verdict, money flows to the parties |
+| oracle (overturned) | insurance −`penalty_amount` | keeps market fee |
+| disputer (right) | fee | fee back + reward carve-out from `penalty_amount` |
+
+The post-verdict canon is identical to committee mode; only the slash size differs — here it is the
+resolver-set `penalty_amount` (no `consensus_strength` scaling, since there is a single decider).
+
+## Notes
+- The resolver can also **ban** the oracle and/or creator (`pm_oracle_object.banned_until`,
+ `pm_creator_ban_object`). See [oracle-banned](../oracle-banned/oracle-banned.md).
+- KYC/whitelisting of the resolver account is a **client-layer** concern; on-chain it is just an account
+ with `active` auth over `pm_dispute_resolve`.
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| `pm_dispute_resolve` (only the named resolver, `dispute_mode==1`) | ✔ | `pm_dispute_resolve_evaluator` |
+| slash = `penalty_amount` (no consensus scaling) | ✔ | overturn branch |
+| `ban_oracle` / `ban_creator` applied | ✔ | sets `banned_until` / upserts `pm_creator_ban_object` |
+| settlement on resolver's outcome | ✔ | `settle_market` + `pm_auto_payout` |
+| resolver token reward | — | none — neutral arbiter |
+
+**Observe via plugin:** `get_dispute` (resolved status), `get_oracle` (slash / ban),
+**`get_creator_ban(account)`** (a creator ban's `banned_until` / `ban_count`).
diff --git a/.qoder/docs/pm-workflow/resolver-committee/resolver-committee.md b/.qoder/docs/pm-workflow/resolver-committee/resolver-committee.md
new file mode 100644
index 0000000000..498eaaeeb9
--- /dev/null
+++ b/.qoder/docs/pm-workflow/resolver-committee/resolver-committee.md
@@ -0,0 +1,93 @@
+# Resolver — committee (stake-weighted, dispute_mode = 0)
+
+Part of [PM Workflow](../README.md). In **committee mode** the *whole SHARES electorate* decides a
+dispute by **stake-weighted vote**. There is no single resolver account; the verdict is tallied
+deterministically by the `pm_dispute_finalize` cron at `voting_end_time`.
+
+## Voting weight (read this carefully)
+A voter's weight is its live **`effective_vesting_shares`** (`vesting − delegated + received`, like
+`committee_vote_request`) **plus its lazy-pool stake converted to vesting-shares**. Since many DAO
+members park VIZ in the [lazy pool](../lazy-pool/lazy-pool.md) for yield (where it is *liquid*, not
+vested), counting only vested SHARES would disenfranchise them. So the tally adds, per voter:
+
+```
+pool_claim_viz = pool_NAV × deposit.shares / pool.total_shares (their share of the pool)
+pool_weight = pool_claim_viz × get_vesting_share_price() (same token↔shares price as
+ create_vesting — drifts with dust)
+voter_weight = effective_vesting_shares + pool_weight
+```
+
+The **participation quorum** denominator is likewise `total_vesting_shares + (pool_NAV → vesting-shares)`,
+so the bar scales with the full electorate (vested + pooled). The 7-day deposit lock prevents
+deposit-vote-withdraw gaming — the same reason vesting can't be flash-staked to vote.
+
+## Interaction diagram
+
+```mermaid
+flowchart LR
+ V1[voter · eff_vshares] -->|pm_dispute_vote outcome,percent| D[(pm_dispute_votes)]
+ V2[voter · eff_vshares] -->|pm_dispute_vote| D
+ D ==>|voting_end_time| FIN[[pm_dispute_finalize]]
+ FIN -->|argmax rshares, threshold check| VERDICT{uphold / overturn}
+ VERDICT -->|consensus_strength scales slash & bans| OUT[settle]
+```
+
+## Operations sent (by voters)
+- `pm_dispute_vote` — **auth `regular`** (same as committee voting). `vote_outcome = -1` upholds the
+ oracle, else proposes the correct outcome; `vote_percent ∈ [-10000, 10000]` is conviction/penalty intensity.
+ **A voter may revise their ballot** any number of times while voting is open — a repeat
+ `pm_dispute_vote` **overwrites** the prior one (latest ballot wins, no "Already voted" rejection).
+
+## Why a public hearing — no commit-reveal (deliberate, will NOT change)
+A committee dispute is an **open public hearing**: the running tally is visible (`get_dispute_votes`)
+and votes are **not** hidden behind a commit-reveal phase. This is a permanent design decision, not a
+gap:
+- The DAO's entire value proposition for prediction markets is **resolving disputes as truthfully and
+ transparently as possible**. Hiding votes until a reveal phase would erode the very trust the platform
+ is built on — opacity in adjudication is exactly what makes a market venue lose credibility.
+- Because the hearing is open, **new evidence and arguments surface during voting**, and voters are
+ *expected* to update — so ballots are **revisable** until `voting_end_time`. A voter who is persuaded
+ by a late argument simply re-sends `pm_dispute_vote` with the new outcome.
+- The usual anti-herding rationale for commit-reveal (Keynesian beauty contest) is weak here: voters are
+ **not paid** for matching the majority (see Tokens below), so there is no bandwagon bounty; influence
+ is pure stake weight, already gated by the 7-day deposit lock and the participation quorum.
+
+## Virtual operations
+- `pm_dispute_finalize` — at `voting_end_time`: per-outcome **rshares** = Σ voter
+ (`effective_vesting_shares` + lazy-pool stake → vesting-shares), a participation threshold
+ (`pm_dispute_approve_min_percent` of `total_vesting_shares + pool_NAV→shares`), winner = argmax, and
+ `consensus_strength = winning_rshares / max_rshares` scales the oracle slash and bans.
+
+## Tokens
+| Actor | sends | receives |
+|-------|-------|----------|
+| each voter | 0 | **0** — voting is a governance duty, not a paid action |
+| disputer / oracle / winners | see [disputer-winner](../disputer-winner/disputer-winner.md) · [oracle-dispute-loser](../oracle-dispute-loser/oracle-dispute-loser.md) | — |
+
+Voters never receive tokens; their influence is their **stake weight**. The economic flows (reward,
+slash, payouts) land on the disputer, oracle, and bettors per the [dispute ledger](../README.md#master-ledger--disputed-resolve-oracle-said-a--overturned-to-b).
+
+## Notes
+- Niche markets may fail the participation threshold → the dispute falls through to
+ [dispute-auto-close](../dispute-auto-close/dispute-auto-close.md) (anti-freeze refund).
+- Contrast the single-account path: [resolver-account](../resolver-account/resolver-account.md).
+
+## Code verification & how to observe
+| Element | In code | Where |
+|---------|---------|-------|
+| `pm_dispute_vote` (regular auth, only `dispute_mode==0`) | ✔ | `pm_dispute_vote_evaluator` |
+| ballot **revisable** while voting open (re-vote overwrites, latest wins) | ✔ | `pm_dispute_vote_evaluator` (modify-or-create on `by_market_voter`) |
+| **no commit-reveal** — open tally by design | ✔ | votes stored & tallied in the clear; `get_dispute_votes` exposes the live tally |
+| tally weight = `effective_vesting_shares` **+ lazy-pool stake → vesting-shares** | ✔ | `pm_dispute_finalize` (`lazy_vote_weight` lambda, `get_vesting_share_price`) |
+| quorum denominator = `total_vesting_shares + pool_NAV→shares` | ✔ | `pm_dispute_finalize` |
+| participation threshold + argmax + `consensus_strength` scaling | ✔ | same |
+| vop `pm_dispute_finalize` | ✔ | at `voting_end_time` |
+| voter token reward | — | none — governance duty, unpaid |
+
+**Observe via plugin:** `get_dispute_votes` (each vote + **live tally** + a stake-weighted **projection
+of finalize**: `quorum_percent_bp`, `quorum_reached`, `expected_uphold`, `expected_outcome`,
+`expected_consensus_strength_bp` — the verdict that would apply at `voting_end_time` under current
+votes), `get_dispute` (status, timers),
+`get_lazy_deposit` (a voter's pooled stake) + `get_lazy_pool` (NAV + total shares for the conversion).
+On-chain tests: `committee_dispute_lazy_pool_voting_weight` (lazy-pool weight), `committee_dispute_flips_outcome`
+(ballot revised mid-hearing: uphold → overturn, latest ballot wins).
diff --git a/.qoder/docs/prediction-market-library-integration-spec.md b/.qoder/docs/prediction-market-library-integration-spec.md
new file mode 100644
index 0000000000..1281216dc0
--- /dev/null
+++ b/.qoder/docs/prediction-market-library-integration-spec.md
@@ -0,0 +1,1193 @@
+# Prediction Markets (Onix, HF14) — Library Integration Specification
+
+> **Audience:** implementers of the JS / PHP / Python client libraries.
+> **Goal:** everything a library needs to (a) build & sign prediction-market transactions and
+> (b) read prediction-market state over JSON-RPC — operation names, plugin names, API method
+> names, parameters, and the full field layout of every operation and returned object.
+>
+> This document is generated from the node source and is authoritative for field **names,
+> order, and types**. It intentionally does **not** re-explain the economic mechanics
+> (CPMM/LMSR/parimutuel, leverage math, dispute game theory) — for that see
+> `docs/prediction-markets/specification.md`. Here we describe *the wire contract only*.
+
+---
+
+## 0. Delta — 2026-07 (oracle-accept window + lazy-pool min-fee)
+
+> **Read this first if you already integrated an earlier version.** Two consensus additions.
+> Everything else in this document is unchanged. No signed operation was added or changed — the
+> client only needs to (a) read two new `get_pm_chain_properties` fields and (b) parse one new
+> virtual operation.
+
+**1. Oracle acceptance window (`pm_oracle_accept_window_sec`, default `3600` = 1h).**
+A pending market must be accepted or rejected by its named oracle within this window. On expiry the
+per-block cron **refunds the creator's seed liquidity** (but **NOT** the non-refundable creation fee)
+and voids the market.
+
+- New chain property `pm_oracle_accept_window_sec` (uint32, seconds) — read via `get_pm_chain_properties` (§10).
+- New read-only field `pm_market_object.accept_deadline` (time) — set to `created_time +
+ pm_oracle_accept_window_sec` on pending markets, `0` (epoch) on markets active at creation (§7.2).
+- New virtual operation **`pm_market_expired`** (op-id **101**): `oracle`, `creator`, `market_id`
+ (int64), `refunded_liquidity` (asset). Parse it in history like the other PM vops (§4.13).
+- **Client UX:** show the accept deadline as a countdown on pending markets; on `pm_market_expired`
+ in history, mark the market "expired — seed refunded, creation fee forfeited." A rejected market
+ (signed `pm_oracle_accept_market accept=false`) still emits **no** vop — detect it via `status = -1`.
+
+**2. Lazy-pool minimum LP fee (`pm_lazy_min_liquidity_fee_percent`, default `200` bp = 2%).**
+The lazy pool now refuses to co-provide liquidity to a market whose `liquidity_fee_percent` is below
+this floor.
+
+- New chain property `pm_lazy_min_liquidity_fee_percent` (uint16, bp) — read via `get_pm_chain_properties` (§10).
+- No new op, object, or vop. Pure allocation-gate behavior change.
+- **Client UX:** when the creator sets `liquidity_fee_percent` below this value in the create-market
+ form, warn that the lazy pool won't add depth to the market (the creator's own seed is the only
+ liquidity). Read the live threshold from `get_pm_chain_properties`; don't hard-code 2%.
+
+---
+
+## 1. Conventions
+
+### 1.1 Data types
+
+| Spec type | Wire (JSON) representation | Notes |
+|---|---|---|
+| `account_name_type` | string | 3–25 chars, VIZ account name |
+| `asset` | string `"10.000 VIZ"` | VIZ, **3 decimals**, `TOKEN_SYMBOL = VIZ`. Serialized as `".<3 decimals> VIZ"` |
+| `share_type` | integer (int64) | raw amount, **already scaled by 1000** (i.e. `10.000 VIZ` = `10000`). Used inside read-only objects |
+| `pm_object_id_type` | integer (int64) | plain numeric id of a pm object (market/bet/liquidity/…). NOT the `space.type.instance` string form |
+| `_id_type` (in returned objects) | string `".."` OR integer, depending on serializer | ChainBase object id. In practice compare/pass the numeric instance. See §1.4 |
+| `time_point_sec` | string ISO-8601 `"2026-07-02T12:00:00"` (UTC, no `Z`) | seconds precision |
+| `fc::sha256` | hex string (64 chars) | |
+| `fc::uint128_t` | integer or string | may exceed JS `Number.MAX_SAFE_INTEGER` — use big-int/string |
+| `uint16_t` percent (bp) | integer | **basis points**, `10000 = 100.00%` unless noted otherwise |
+| `extensions_type` | array `[]` | always present, always empty for HF14; include as `[]` when signing |
+
+**Percent conventions differ by field — read the per-field notes.** Three scales appear:
+- **bp (basis points):** `10000 = 100%`. Most fee/percent fields.
+- **`time_penalty` on a bet:** `1e6 = 100%` (`pm_max_time_penalty = 1000000`).
+- **`pm_leverage_*_percent` governance knobs:** plain percent (`10 = 10%`), see §9.
+
+> ⚠️ **There is NO permille (‰) anywhere in the live protocol.** The original PHP prototype used
+> permille for market/oracle fees; the on-chain protocol converted every one of them to **bp**. Do
+> **not** carry a `fromPermille` helper over from prototype code. In particular the market/oracle
+> **fee** fields — `oracle_fee_percent`, `creator_fee_percent`, `liquidity_fee_percent`, the cap
+> `pm_max_oracle_fee_percent`, and the pool floor `pm_lazy_min_liquidity_fee_percent` — are **all bp**
+> (`10000 = 100%`), parsed with the *same* `fromBP`. Consensus proves it: settlement computes each fee
+> as `floor(losers_sum × fee_percent / 10000)` (`libraries/chain/pm/parimutuel.cpp`), and `validate()`
+> rejects markets whose fee sum exceeds `10000`. Using `fromPermille` on these fields renders/sends
+> values **10× off** (a real `200` = 2% LP fee would show as 20% and, on write, store as 0.2%).
+
+### 1.2 Operation serialization
+
+Operations are members of a single `static_variant` (tagged union). Two encodings:
+
+- **JSON (what libraries build):** a 2-element array `["", { ...fields }]`, where
+ `` is the struct name **with the `_operation` suffix removed** — e.g.
+ `pm_place_bet_operation` → `"pm_place_bet"`.
+- **Binary (for signing):** the variant **tag is a varint = the op-id** in §2/§3, followed by
+ the fields in the exact declared order. Field order in every table below **is** the binary
+ order. `extensions` is the last field of user ops (empty vector → single `0x00` byte).
+
+A transaction's `operations` array holds these 2-element arrays. Signing (ref-block, expiration,
+chain-id, canonical secp256k1) is identical to every other VIZ operation — unchanged by HF14.
+
+### 1.3 JSON-RPC calling convention
+
+All reads go through the **`prediction_market_api`** plugin. Call shape (same as every VIZ API
+plugin):
+
+```json
+{ "jsonrpc":"2.0", "id":1, "method":"call",
+ "params":["prediction_market_api", "", [ ]] }
+```
+
+Positional args are order-sensitive; trailing optional args may be omitted. Pagination is
+uniformly `(…key…, from, limit)` with `limit ≤ 1000`.
+
+### 1.4 Object ids
+
+Returned objects carry an `id`. The numeric **instance** part is what you pass back into ops as
+`market_id`, `bet_id`, `liquidity_id`, `position_id`, `commit_id`. When reading you may receive
+either the string `"space.type.instance"` or a bare integer depending on the serializer build;
+always be able to parse the trailing integer. Ops take the bare integer (`pm_object_id_type`).
+
+---
+
+## 2. Plugins & node configuration
+
+| Plugin (config `plugin =` name) | Role |
+|---|---|
+| `chain` | consensus DB (always on) |
+| `json_rpc` | RPC transport (always on) |
+| `prediction_market_api` | **all PM read methods** (§5). Requires `chain` + `json_rpc` |
+
+`prediction_market_api` config option (`config.ini`):
+
+| Option | Default | Meaning |
+|---|---|---|
+| `pmm-ttl-days` | `7` | Days to keep a market's non-consensus metadata + kline history after its dispute window closes, then pruned |
+
+There is **no separate operations plugin** — PM operations are part of the core protocol
+(`operation` variant); any node accepting transactions accepts them once HF14 is active.
+
+---
+
+## 3. User (signed) operations
+
+35 total PM operations exist; **23 are user operations** (submitted in transactions), 12 are
+virtual (§4). Op-id = index in the global `operation` variant (fixed, append-only).
+
+Legend for **Auth**: which authority must sign — `active` (spending auth) or `regular` (posting-level auth).
+
+| # | op-id | JSON name | Auth | Purpose |
+|---|---|---|---|---|
+| 1 | 66 | `pm_oracle_register` | active(`owner`) | Register an oracle with a bonded insurance deposit |
+| 2 | 67 | `pm_oracle_update` | active(`owner`) | Change insurance / fees / rules / auto-accept policy |
+| 3 | 68 | `pm_create_market` | active(`creator`) | Create a market; creator seeds first liquidity |
+| 4 | 69 | `pm_oracle_accept_market` | active(`oracle`) | Oracle accepts/rejects a pending market & quotes terms |
+| 5 | 70 | `pm_place_bet` | active(`account`) | Place a bet (instant or batch) |
+| 6 | 71 | `pm_commit_bet` | active(`account`) | Commit-reveal phase 1: hidden commitment |
+| 7 | 72 | `pm_reveal_bet` | active(`account`) | Commit-reveal phase 2: reveal & enqueue |
+| 8 | 73 | `pm_cancel_bet` | active(`account`) | Cancel an open/queued bet (needs `allow_cancellation`) |
+| 9 | 74 | `pm_add_liquidity` | active(`provider`) | Add liquidity to a market |
+| 10 | 75 | `pm_withdraw_liquidity` | active(`provider`) | Withdraw liquidity |
+| 11 | 76 | `pm_resolve_market` | active(`oracle`) | Oracle resolves to a winning outcome |
+| 12 | 77 | `pm_no_contest` | active(`oracle`) | Oracle voids the market (refund all) |
+| 13 | 78 | `pm_dispute_create` | active(`disputer`) | File a dispute (escrows `pm_dispute_fee`) |
+| 14 | 79 | `pm_dispute_vote` | **regular**(`voter`) | Committee-mode dispute vote |
+| 15 | 80 | `pm_dispute_resolve` | active(`resolver`) | Account-mode dispute verdict by configured resolver |
+| 16 | 81 | `pm_transfer_position` | active(`from`) | Transfer bet weight to another account |
+| 17 | 82 | `pm_lazy_deposit` | active(`account`) | Deposit into the lazy liquidity pool |
+| 18 | 83 | `pm_lazy_withdraw` | active(`account`) | Withdraw from the lazy pool |
+| 19 | 91 | `pm_leverage_open` | active(`account`) | Open a leveraged CPMM position |
+| 20 | 92 | `pm_leverage_close` | active(`account`) | Voluntarily close a leveraged position |
+| 21 | 93 | `pm_leverage_convert` | active(`account`) | Convert a leveraged position to a normal bet |
+| 22 | 98 | `pm_dispute_oracle_respond` | active(`oracle`) | Oracle posts a public rebuttal on an open dispute |
+| 23 | 99 | `pm_unban` | active(`resolver`) | Lift an oracle/creator ban set by that resolver |
+
+> Op-ids 84–90 and 94–97 are the **virtual** ops interleaved in the variant (§4). The user-op
+> op-ids above are therefore not fully contiguous. Always verify the numeric tag against a live
+> node if you hand-roll binary serialization; JSON name-based serialization is the safe path.
+
+Below, each op lists its fields **in declared (binary) order**. All ops end with
+`extensions: []` (omitted from the tables). Percent fields are bp unless noted.
+
+### 3.1 `pm_oracle_register`
+| Field | Type | Description |
+|---|---|---|
+| `owner` | account | Oracle account; also the required signer |
+| `insurance` | asset | VIZ bond locked from `owner`; must be `≥ pm_min_oracle_insurance` |
+| `fee_percent` | uint16 (bp) | Oracle % of losers' pool; `≤ pm_max_oracle_fee_percent` |
+| `fixed_fee` | asset | Per-market fixed fee (VIZ, `≥ 0`) |
+| `rules_url` | string | Oracle rules/profile URL; `≤ 256` chars |
+| `auto_accept_creator` | account | Auto-accept only markets from this creator; empty = any |
+| `auto_accept_resolver` | account | Empty = auto-accept committee-mode markets only; set = only account-mode markets whose `dispute_resolver` equals this |
+| `auto_accept` | bool | Enable the anti-collusion auto-accept policy above |
+
+### 3.2 `pm_oracle_update`
+All change fields are **optional** — omit to leave unchanged.
+| Field | Type | Description |
+|---|---|---|
+| `owner` | account | Oracle account (signer) |
+| `insurance_delta` | optional asset | Signed: `>0` top-up, `<0` withdraw. Withdraw blocked while active markets exist or if it would drop below `pm_min_oracle_insurance` |
+| `fee_percent` | optional uint16 (bp) | New oracle fee % |
+| `fixed_fee` | optional asset | New per-market fixed fee |
+| `rules_url` | optional string | New rules URL |
+| `auto_accept_creator` | optional account | Update auto-accept policy |
+| `auto_accept_resolver` | optional account | Update auto-accept policy |
+| `auto_accept` | optional bool | Enable/disable auto-accept |
+
+### 3.3 `pm_create_market`
+Oracle terms here are the creator's **offer ceiling** (max the maker will pay). The oracle locks
+its actual quote (`≤` these) at accept; a self-oracle freezes them as-is at creation.
+| Field | Type | Description |
+|---|---|---|
+| `creator` | account | Market creator (signer); becomes first LP |
+| `oracle` | account | Registered oracle, or `creator` for a self-oracle |
+| `market_type` | uint8 | `0` binary (CPMM), `1` multi (LMSR) |
+| `outcomes` | string[] | Outcome labels. Size **2** for binary; `3..pm_max_outcomes` for multi. Each label `≤ 64` chars |
+| `url` | string | Resolution criteria/title; `≤ 256` chars |
+| `oracle_fee_percent` | uint16 (bp) | Offered max oracle % of losers' pool |
+| `oracle_fixed_fee` | asset | Offered max oracle fixed fee (VIZ, `≥ 0`) |
+| `creator_fee_percent` | uint16 (bp) | Creator's cut of losers' pool |
+| `liquidity_fee_percent` | uint16 (bp) | LP cut of losers' pool |
+| `liquidity` | asset | VIZ seed liquidity; `≥ pm_min_liquidity` |
+| `lmsr_b` | share_type | Multi only: client-computed LMSR `b`. Node checks `floor(liquidity / ln_q(N)) == b`. `0` for binary |
+| `betting_expiration` | time_point_sec | Betting closes at this time |
+| `result_expiration` | time_point_sec | `> betting_expiration`; `≤ now + pm_max_market_duration` |
+| `time_penalty_type` | uint8 | Time-decay penalty model selector |
+| `time_penalty_value` | uint32 | Penalty parameter (see market mechanics) |
+| `penalty_curve_type` | uint8 | Penalty curve selector |
+| `allow_early_resolution` | bool | Oracle may resolve before `betting_expiration` |
+| `allow_cancellation` | bool | Bettors may cancel open bets |
+| `allow_batch` | bool | Allow batch (commit-reveal / queued) betting |
+| `allow_instant_bet` | bool | Allow instant bets. **Multi forces `true`** (no LMSR batch yet) |
+| `endogeneity_tier` | uint8 | `1` econ-data / `2` sports / `3` political |
+| `dispute_mode` | uint8 | `0` committee / `1` account |
+| `dispute_resolver` | account | Required & must exist iff `dispute_mode==1`; must NOT equal `oracle` or `creator` |
+| `dispute_penalty_percent` | int16 (bp) | `−10000..+10000` oracle penalty policy on a successful dispute: `>0` slash % of insurance ×consensus; `<0` good-faith bonus from fee; `0` none |
+| `metadata` | string | Free-form client JSON (no length cap). Consensus-opaque; parsed off-chain by the meta plugin (see §6) |
+
+### 3.4 `pm_oracle_accept_market`
+Quote fields are ignored on reject and for self-oracle markets.
+| Field | Type | Description |
+|---|---|---|
+| `oracle` | account | Oracle (signer) |
+| `market_id` | int64 | Target market |
+| `accept` | bool | `true` accept, `false` reject |
+| `oracle_fee_percent` | uint16 (bp) | Oracle's quoted %; `≤` market offer & the median cap |
+| `oracle_fixed_fee` | asset | Oracle's quoted fixed fee; `≤` market offer |
+
+Emits virtual `pm_market_accepted` on accept.
+
+### 3.5 `pm_place_bet`
+| Field | Type | Description |
+|---|---|---|
+| `account` | account | Bettor (signer) |
+| `market_id` | int64 | Target market |
+| `side` | int8 | Binary: `0`/`1`. Multi: `-1` |
+| `outcome_index` | int16 | Multi: `0..N-1`. Binary: `-1` |
+| `amount` | asset | Stake (VIZ, `> 0`) |
+| `min_tokens` | share_type | Slippage floor on received weight (`0` = none) |
+| `mode` | uint8 | `0` instant, `1` batch (queued to next epoch) |
+
+### 3.6 `pm_commit_bet`
+| Field | Type | Description |
+|---|---|---|
+| `account` | account | Bettor (signer) |
+| `market_id` | int64 | Target market |
+| `commitment` | sha256 | SHA-256 of the exact binary preimage in §3.6.1 (consensus-critical) |
+| `escrow_amount` | asset | VIZ locked; `≥ pm_min_batch_bet` |
+| `no_reveal_fee_percent` | uint16 (bp) | **MUST equal** median(`pm_commit_no_reveal_penalty_percent`) — consensus-checked |
+
+#### 3.6.1 Commitment preimage (byte-exact — must match consensus)
+
+The node recomputes this hash in the `pm_reveal_bet` evaluator (`verify_commit` in
+`libraries/chain/pm_evaluator.cpp`) and rejects the reveal on any mismatch — a wrong preimage
+**forfeits the escrow**. The preimage is a **raw binary concatenation with no separators and no
+length prefixes** (it is *not* the colon-delimited string some prototypes used). Fields, in order:
+
+| # | Field | Bytes | Encoding |
+|---|---|---|---|
+| 1 | `market_id` | 8 | int64, **little-endian** (the numeric market instance) |
+| 2 | `account` | 32 | ASCII account name left-aligned in a **32-byte** buffer, **zero-padded** (VIZ `fixed_string_32` storage) |
+| 3 | `side` | 1 | int8 (binary: `0`/`1`; multi: `-1` = `0xFF`) |
+| 4 | `outcome_index` | 2 | int16, **little-endian** (multi: `0..N-1`; binary: `-1` = `0xFFFF`) |
+| 5 | `amount` | 8 | int64, little-endian — the **revealed** amount in **milli-VIZ** (`asset.amount`, i.e. VIZ×1000) |
+| 6 | `min_tokens` | 8 | int64, little-endian (milli-VIZ / weight units) |
+| 7 | `salt` | variable | raw UTF-8/byte content of the salt string (its own length, no terminator) |
+
+Fixed portion = **59 bytes**; total = `59 + len(salt)`. `commitment = SHA-256(preimage)` (32-byte
+digest). The same `side`, `outcome_index`, `amount`, `min_tokens`, `salt` must be re-supplied in
+`pm_reveal_bet` (§3.7). Integers use **little-endian** because the node hashes raw host bytes on its
+(x86-64, little-endian) consensus platform — libraries MUST emit little-endian regardless of host.
+
+**Golden test vector** (verify your encoder against this before shipping):
+
+```
+market_id = 5
+account = "alice"
+side = 0
+outcome_index = -1
+amount = 10000 # 10.000 VIZ
+min_tokens = 0
+salt = "cafe1234"
+
+preimage (hex, 67 bytes):
+0500000000000000 616c696365000000000000000000000000000000000000000000000000000000 00 ffff 1027000000000000 0000000000000000 6361666531323334
+
+commitment = SHA-256(preimage)
+ = acc2fbc9e024509a529584baba41dc2eabdb82c3c107dc041d37c94b24f4b3c0
+```
+
+(Preimage shown space-grouped by field for readability; concatenate without spaces before hashing.)
+
+### 3.7 `pm_reveal_bet`
+| Field | Type | Description |
+|---|---|---|
+| `account` | account | Bettor (signer) |
+| `commit_id` | int64 | The `pm_commit` object being revealed |
+| `side` | int8 | Same value that was committed |
+| `outcome_index` | int16 | Same value that was committed |
+| `amount` | asset | `≤ escrow_amount`; surplus refunded |
+| `salt` | string | Entropy bound into the commitment hash |
+| `min_tokens` | share_type | Slippage floor |
+
+### 3.8 `pm_cancel_bet`
+| Field | Type | Description |
+|---|---|---|
+| `account` | account | Bettor (signer) |
+| `bet_id` | int64 | Bet to cancel |
+| `min_return` | share_type | Slippage floor on the refund |
+
+### 3.9 `pm_add_liquidity`
+| Field | Type | Description |
+|---|---|---|
+| `provider` | account | LP (signer) |
+| `market_id` | int64 | Target market |
+| `amount` | asset | VIZ to add (`> 0`) |
+
+### 3.10 `pm_withdraw_liquidity`
+| Field | Type | Description |
+|---|---|---|
+| `provider` | account | LP (signer) |
+| `liquidity_id` | int64 | LP position to withdraw from |
+| `amount` | asset | VIZ to withdraw; `0` = full position. Principal-safe; locked from `betting_expiration` until resolution |
+
+### 3.11 `pm_resolve_market`
+| Field | Type | Description |
+|---|---|---|
+| `oracle` | account | Oracle (signer) |
+| `market_id` | int64 | Target market |
+| `winning_outcome` | int16 | Winning outcome index |
+| `decision_url` | string | Evidence/decision URL; `≤ 256` chars. Stored on the market (`pm_market_object.decision_url`) |
+| `decision_reason` | string | Free-text justification; `≤ 1024` chars. Stored on the market (`pm_market_object.decision_reason`), readable via `get_market` |
+
+### 3.12 `pm_no_contest`
+| Field | Type | Description |
+|---|---|---|
+| `oracle` | account | Oracle (signer) |
+| `market_id` | int64 | Target market |
+| `reason` | string | `≤ 1024` chars. Stored on the market as `decision_reason` |
+
+### 3.13 `pm_dispute_create`
+| Field | Type | Description |
+|---|---|---|
+| `disputer` | account | Disputer (signer); escrows `pm_dispute_fee` |
+| `market_id` | int64 | Target market |
+| `proposed_outcome` | int16 | Outcome the disputer claims is correct (`-1` = void/no-contest challenge) |
+| `reason` | string | `≤ 1024` chars |
+
+### 3.14 `pm_dispute_vote` (committee mode)
+Signed with **regular** authority (like `committee_vote_request`). Ballots are revisable until
+voting closes (modify-or-create).
+| Field | Type | Description |
+|---|---|---|
+| `voter` | account | Voter (regular-auth signer) |
+| `market_id` | int64 | Disputed market |
+| `vote_outcome` | int16 | Correct outcome, or `-1` to uphold the oracle |
+| `vote_percent` | int16 | Conviction / penalty intensity `[-10000, 10000]` (bp). Sign encodes direction; see §5 `get_dispute_votes` for tally semantics |
+
+### 3.15 `pm_dispute_resolve` (account mode)
+Verdict by the market's configured `dispute_resolver`.
+| Field | Type | Description |
+|---|---|---|
+| `resolver` | account | Configured resolver (signer) |
+| `market_id` | int64 | Disputed market |
+| `correct_outcome` | int16 | Final correct outcome |
+| `penalty_amount` | asset | Oracle insurance to slash |
+| `ban_oracle` | bool | Ban the oracle |
+| `ban_oracle_until` | time_point_sec | Oracle ban expiry (`time_point_sec::maximum()` = permanent) |
+| `ban_creator` | bool | Ban the creator from creating markets |
+| `ban_creator_until` | time_point_sec | Creator ban expiry (max = permanent) |
+
+> **Bans are a compliance/regulator feature, exclusive to account mode.** When a market routes
+> disputes to an account-mode `dispute_resolver` (e.g. a regulator or a licensed arbitrator), that
+> resolver can sanction **both the oracle and the market creator** — temporarily or permanently
+> (`*_until = maximum()`) — in the same verdict, on top of the insurance slash. This lets a regulator
+> that serves as resolver enforce off-chain rules (bar a bad-faith oracle or a repeat-offender
+> creator from the platform). **Committee/DAO mode (`dispute_mode = 0`) has no ban power by design**
+> — it is a transparent public hearing that only slashes insurance and dings reputation
+> (`pm_dispute_finalize`); it never bans. A ban set here records the issuing `resolver` in the
+> target's `banned_by`, so only that resolver may lift it early via `pm_unban` (§3.23); otherwise it
+> lapses at `banned_until` (cron emits `pm_ban_expired`, §4.12).
+
+### 3.16 `pm_transfer_position`
+| Field | Type | Description |
+|---|---|---|
+| `from` | account | Current holder (signer) |
+| `bet_id` | int64 | Bet whose weight is transferred |
+| `to` | account | Recipient |
+| `amount` | share_type | Weight to reassign; `0` = full. No market impact |
+| `memo` | string | Plaintext, or `#`-prefixed ECIES (same as VIZ memos) |
+
+### 3.17 `pm_lazy_deposit`
+| Field | Type | Description |
+|---|---|---|
+| `account` | account | Depositor (signer) |
+| `amount` | asset | VIZ deposited into the lazy pool (`> 0`) |
+
+### 3.18 `pm_lazy_withdraw`
+| Field | Type | Description |
+|---|---|---|
+| `account` | account | Depositor (signer) |
+| `shares` | share_type | Pool shares to burn; `0` = all |
+| `emergency` | bool | `true` = withdraw before lock ends, with penalty on locked-share profit |
+
+### 3.19 `pm_leverage_open`
+Gated by `pm_leverage_enabled`.
+| Field | Type | Description |
+|---|---|---|
+| `account` | account | Bettor (signer) |
+| `market_id` | int64 | Target market (binary CPMM only) |
+| `outcome_index` | int16 | Binary side `0`/`1` |
+| `collateral` | asset | Bettor's own stake (VIZ) |
+| `loan` | asset | Pool loan (VIZ) |
+| `min_tokens` | share_type | Slippage floor (consensus-checked) |
+| `max_slippage_percent` | uint16 (bp) | User-facing front-run guard |
+
+### 3.20 `pm_leverage_close`
+| Field | Type | Description |
+|---|---|---|
+| `account` | account | Position owner (signer) |
+| `position_id` | int64 | Leverage position to close (only if `cancel_value ≥ liquidation_threshold`) |
+| `min_return` | share_type | Slippage floor on the bettor's return |
+
+### 3.21 `pm_leverage_convert`
+| Field | Type | Description |
+|---|---|---|
+| `account` | account | Position owner (signer) |
+| `position_id` | int64 | Leverage position to convert to a normal bet |
+| `conversion_profit_cost` | uint16 (bp) | **MUST equal** median(`pm_conversion_profit_cost_percent`) |
+
+### 3.22 `pm_dispute_oracle_respond`
+The market's oracle posts a public rebuttal onto the open dispute. Stored on the dispute object
+(read via `get_dispute`); allowed only while the dispute is open and `now ≤ oracle_response_deadline`.
+Re-posting overwrites the previous response.
+| Field | Type | Description |
+|---|---|---|
+| `oracle` | account | Market oracle (signer) |
+| `market_id` | int64 | Disputed market |
+| `response` | string | Rebuttal text; non-empty, `≤ 1024` chars |
+
+### 3.23 `pm_unban`
+Lifts a ban imposed by an account-mode `pm_dispute_resolve`. Only the resolver recorded in the
+target's `banned_by` may lift it. At least one of `unban_oracle` / `unban_creator` must be true.
+| Field | Type | Description |
+|---|---|---|
+| `resolver` | account | The resolver that imposed the ban (signer); must equal the target's `banned_by` |
+| `target` | account | The banned oracle / creator account |
+| `unban_oracle` | bool | Clear the oracle ban (`pm_oracle_object.banned_until`) |
+| `unban_creator` | bool | Clear the creator ban (`pm_creator_ban_object.banned_until`) |
+
+---
+
+## 4. Virtual operations
+
+Virtual ops are **never submitted** by clients — they are emitted deterministically by the
+node's bounded per-block cron and appear in block/account history (via the account-history /
+operation-history plugins, `get_ops_in_block`, etc.). Libraries need to **parse** them.
+They have no `extensions` field.
+
+| op-id | JSON name | Emitted when |
+|---|---|---|
+| 84 | `pm_batch_settle` | Epoch boundary: queued/revealed bets settle at a uniform price |
+| 85 | `pm_commit_forfeit` | An unrevealed commitment forfeits its penalty into `forfeit_pool` |
+| 86 | `pm_auto_payout` | Deferred market-level payout marker after the dispute grace elapses |
+| 87 | `pm_dispute_finalize` | Committee-mode tally finalized at `voting_end_time` |
+| 88 | `pm_dispute_auto_close` | Anti-freeze: dispute unresolved at `auto_close_time` → full refund |
+| 89 | `pm_oracle_missed_penalty` | Oracle missed `result_expiration` → insurance slashed, refund all |
+| 90 | `pm_lazy_recall` | Lazy-pool graduated recall step (idle allocation returned) |
+| 94 | `pm_leverage_liquidate` | A leveraged position force-closed (opposing bet / cancel / expiration) |
+| 95 | `pm_leverage_resolve` | A leveraged position settled at market resolution |
+| 96 | `pm_market_accepted` | Oracle accepted (or self-oracle auto-accepted); terms frozen |
+| 97 | `pm_payout` | Per-bettor parimutuel settlement (one per active bet inside settle) |
+| 100 | `pm_ban_expired` | A temporary oracle/creator ban lapsed at `banned_until`; the cron cleared it |
+| 101 | `pm_market_expired` | Oracle didn't accept/reject within `pm_oracle_accept_window_sec`; market voided, seed refunded |
+
+### 4.1 `pm_batch_settle`
+`market_id` (int64), `epoch` (uint32), `settled_bets` (uint32).
+
+### 4.2 `pm_commit_forfeit`
+`account`, `commit_id` (int64), `market_id` (int64), `penalty` (asset → `forfeit_pool`), `refund` (asset → account).
+
+### 4.3 `pm_auto_payout`
+`account`, `market_id` (int64), `bet_id` (int64), `payout` (asset).
+
+### 4.4 `pm_dispute_finalize`
+`market_id` (int64), `winning_outcome` (int16), `oracle_penalty` (asset).
+
+### 4.5 `pm_dispute_auto_close`
+`market_id` (int64), `oracle_penalty` (asset).
+
+### 4.6 `pm_oracle_missed_penalty`
+`oracle`, `market_id` (int64), `slashed` (asset).
+
+### 4.7 `pm_lazy_recall`
+`market_id` (int64), `recalled` (asset).
+
+### 4.8 `pm_leverage_liquidate`
+`account`, `position_id` (int64), `market_id` (int64), `cancel_value` (asset), `pool_received` (asset),
+`bettor_received` (asset), `reason` (uint8: `0` opposing_bet, `1` cancel_bet, `2` expiration).
+
+### 4.9 `pm_leverage_resolve`
+`account`, `position_id` (int64), `market_id` (int64), `won` (bool), `pool_received` (asset),
+`bettor_received` (asset), `outcome_index` (int16), `leverage` (uint16 — integer multiple `total_bet/collateral`).
+`won` = position was solvent; `bettor_received == 0` ⇒ collateral lost.
+
+### 4.10 `pm_market_accepted`
+`oracle`, `creator`, `market_id` (int64), `oracle_fee_percent` (uint16 bp, frozen),
+`oracle_fixed_fee` (asset, frozen), `self_oracle` (bool — `true` = auto-accepted at creation).
+
+### 4.11 `pm_payout`
+`account`, `market_id` (int64), `bet_id` (int64), `side` (int8: binary `0`/`1`, multi `-1`),
+`outcome_index` (int16: multi `0..N-1`, binary `-1`), `amount` (asset — the stake),
+`payout` (asset — credited at settle; `0` on a loss).
+
+### 4.12 `pm_ban_expired`
+`account`, `oracle` (bool), `creator` (bool). Emitted by the per-block cron when a **temporary**
+oracle and/or creator ban reaches `banned_until` — the cron clears the ban and fires this so
+history/indexers observe the lift. An *early manual* lift is the signed `pm_unban` op instead (§3.23),
+so this vop fires only for automatic time-expiry. Permanent bans (`banned_until = maximum()`) never
+expire and never emit it.
+
+### 4.13 `pm_market_expired`
+`oracle`, `creator`, `market_id` (int64), `refunded_liquidity` (asset). Emitted by the per-block cron
+when a **pending** market (`status 0`) reaches `accept_deadline` (= `created_time +
+pm_oracle_accept_window_sec`) without the named oracle having accepted or rejected it. The cron voids
+the market (`status → -1`) and refunds the creator's **seed liquidity** (`refunded_liquidity`); the
+**creation fee is NOT refunded** (it went to the DAO fund at creation). Distinct from an explicit
+oracle reject (the signed `pm_oracle_accept_market` with `accept=false`, which emits no vop).
+
+---
+
+## 5. API methods — `prediction_market_api` plugin
+
+Call as `call("prediction_market_api", "", [args])`. `from`/`limit` are pagination
+offsets; `limit ≤ 1000`. Returned object schemas are in §7.
+
+### Markets
+
+**`get_market(market_id)`**
+- `market_id` int64.
+- Returns `pm_market_object` (§7.2). Throws if not found.
+
+**`list_markets(status, from, limit, [show_risky=false])`**
+- `status` int8 — filter by market status (`-1` deleted, `0` waiting, `1` active, `2` closed, `3` resolved).
+- `from` uint32, `limit` uint32.
+- `show_risky` bool (optional) — when `false` (default), markets whose oracle insurance covers
+ `< 2.5×` their betting volume are **hidden**. `true` reveals them.
+- Returns `pm_market_object[]`.
+
+**`list_markets_by_oracle(oracle, from, limit)`** → `pm_market_object[]` for `oracle` (account name).
+
+**`list_markets_by_creator(creator, from, limit)`** → `pm_market_object[]` for `creator` (account name).
+
+**`get_market_outcomes(market_id)`** → `pm_outcome_object[]` (§7.3), ordered by `outcome_index`.
+
+**`get_market_weight_sums(market_id)`** → `pm_market_weight_sums_api_object` (§7.16). Per-outcome
+aggregated staked amount and curve weight (computed live from active/resolved bets). For binary
+markets the outcome labels are synthesized as `"A"`/`"B"`.
+
+**`get_market_bets(market_id, from, limit)`** → `pm_bet_object[]` (§7.4), all bets on the market.
+
+**`get_account_positions(account, from, limit)`** → `pm_position_api_object[]` (§7.15) — each
+bet plus its `expected_payout` (parimutuel projection mirroring settlement) and the market status.
+
+**`get_market_liquidity(market_id, from, limit)`** → `pm_liquidity_object[]` (§7.5).
+
+**`get_market_full(market_id, [account])`** → `pm_market_full_api_object` (§7.24). One-call enriched
+view: market + outcomes + weight sums + oracle (with reliability) + parsed metadata, plus — when
+`account` is given — that account's bets / leverage positions / LP **on this market**. Saves the thin
+client several round-trips when opening a market detail screen. Throws only if the market is missing.
+
+### Leverage
+
+**`get_account_leverage_positions(account, from, limit)`** → `pm_leverage_position_object[]` (§7.12).
+
+**`get_market_leverage_positions(market_id, from, limit)`** → `pm_leverage_position_object[]` (§7.12).
+
+**`get_creator_ban(account)`** → `pm_creator_ban_object` (§7.13). Throws if the account is not banned.
+
+**`get_leverage_quote(market_id, outcome_index, collateral)`** → `pm_leverage_quote_api_object` (§7.20).
+Read-only projection of `pm_leverage_open` using the **same in-node margin math**: the max solvent
+loan, the resulting max leverage, the pool/position caps, and up to 12 slider stops (each with
+tokens, threshold, current & worst-case cancel value). `collateral` is a `share_type` integer
+(milli-VIZ). When leverage is not possible, `available=false` and `failed_constraints[]` explains
+why. Throws only if the market does not exist.
+
+**`get_leverage_close_preview(position_id)`** → `pm_leverage_close_preview_api_object` (§7.21).
+Mirrors `pm_leverage_close` at head-block reserves: cancel value, pool obligation, what the bettor
+receives, and `closeable` (false ⇒ the protocol would liquidate instead).
+
+**`get_leverage_convert_preview(position_id)`** → `pm_leverage_convert_preview_api_object` (§7.22).
+Mirrors `pm_leverage_convert`: cancel value, current profit, the conversion fee at the current
+median `pm_conversion_profit_cost_percent`, and the `total_user_payment` the convert op would debit.
+
+> These three are **non-consensus quotes** — reserves move between the read and the broadcast, so
+> treat the numbers as an estimate at the head block and always send the on-chain slippage guards
+> (`min_tokens` / `min_return` / `conversion_profit_cost`).
+
+### Oracles
+
+**`get_oracle(owner)`** → `pm_oracle_api_object` (§7.14) — the raw `pm_oracle_object` plus a
+non-consensus `reliability_score` (bp `[0..10000]`). `owner` = account name. Throws if not found.
+
+**`list_oracles(from, limit)`** → `pm_oracle_object[]` (§7.1), ordered by owner name.
+
+### Disputes
+
+**`get_dispute(market_id)`** → `pm_dispute_object` (§7.7). Throws if no dispute exists.
+
+**`get_dispute_votes(market_id)`** → `pm_dispute_votes_api_object` (§7.17). Returns the live
+ballot list **plus** a stake-weighted projection of what the finalize cron would apply right now
+(quorum status, expected outcome, consensus strength). Safe to call when no dispute exists
+(returns defaults). Key semantics of the vote tally:
+- A vote with `vote_outcome == -1` (or `vote_percent ≤ 0`) counts as **defending the oracle**.
+- A vote with `vote_percent > 0` and a valid `vote_outcome` backs an **outcome change**, weighted
+ by `voter_shares × vote_percent / 10000`.
+- Voter weight = `effective_vesting_shares + lazy-pool stake→shares`.
+
+### Lazy pool & chain properties
+
+**`get_lazy_pool()`** (no args) → `pm_lazy_pool_object` (§7.9). Throws if the pool is uninitialized.
+
+**`get_lazy_deposit(account)`** → `pm_lazy_deposit_object` (§7.10). Throws if the account has no deposit.
+
+**`get_lazy_allocations(from, limit)`** → `pm_lazy_allocation_object[]` (§7.11). All lazy-pool
+per-market allocation records (for a pool dashboard).
+
+**`get_market_lazy_allocation(market_id)`** → `pm_lazy_allocation_object` (§7.11). The lazy-pool
+allocation for one market. Throws if none.
+
+> Oracle penalty stamps are already on `pm_oracle_object` (`penalty_stamps`, `last_penalty_stamp_time`,
+> returned by `get_oracle`) — no separate method needed.
+
+**`get_pm_chain_properties()`** (no args) → `chain_properties_pm` (§9) — the **median** (active,
+consensus) values of every PM governance parameter.
+
+### Metadata & charts (non-consensus, plugin-indexed)
+
+**`get_market_meta(market_id)`** → `pm_market_meta_object` (§7.18) — parsed category/tags/etc.
+Throws if none indexed yet.
+
+**`list_markets_by_category(category, from, limit, [jurisdiction=""], [subcategory=""], [tag=""], [sort="newest"])`**
+→ `pm_market_meta_object[]`. Optional filters: `jurisdiction` (ISO code — exclude markets banning
+it), `subcategory` (exact match), `tag` (CSV membership). `sort` ∈ `newest` (market id desc,
+default) · `oldest` (id asc) · `volume` (`bets_sum` desc) · `expiration` (`betting_expiration` asc).
+`volume`/`expiration` load each matching market, so they scan the whole (non-pruned) category
+before paging.
+
+**`get_market_categories()`** (no args) → `pm_market_categories_api_object` (§7.23). Category taxonomy
+with live per-category / per-subcategory counts, plus the top 20 hot tags (jurisdiction-* tags
+excluded), aggregated over the currently indexed (non-pruned) markets. Use it to build the browse
+filter chips without hard-coding a taxonomy.
+
+**`get_market_kline(market_id, [from=0], [limit=1000])`** → `pm_kline_api_object[]` (§7.19).
+Time-series of per-outcome weight snapshots, ascending by `seq` (oldest→newest). Pagination is
+**offset-from-newest**: `from` skips the newest N points, then up to `limit` are returned. So
+`(from=0, limit=1000)` is the latest ≤1000 changes; `(from=1000, limit=1000)` steps another 1000
+back. Empty array when the market has no recorded points.
+
+---
+
+## 6. `metadata` JSON (client convention)
+
+The `metadata` field of `pm_create_market` is consensus-opaque free-form JSON. The
+`prediction_market_api` plugin parses these keys (unknown keys ignored) and exposes them via
+`get_market_meta` / `list_markets_by_category`:
+
+| Key | Type | Indexed as |
+|---|---|---|
+| `category` | string | `category` (queryable) |
+| `subcategory` | string | `subcategory` |
+| `tags` | string[] or CSV | `tags` (comma-joined) |
+| `banned_jurisdictions` | string[] or CSV of ISO codes | `banned_jurisdictions` (filtered in `list_markets_by_category`) |
+
+Any other keys (title translations, images, source links, etc.) are preserved on-chain in
+`metadata` verbatim but not indexed. Localization is a pure client concern.
+
+---
+
+## 7. Returned object schemas
+
+Field order below matches the node's reflection (JSON key order is not guaranteed, but these are
+the exact keys). All monetary fields are `share_type` **integers** (raw, ×1000) unless the type
+says `asset`.
+
+### 7.1 `pm_oracle_object`
+| Field | Type | Description |
+|---|---|---|
+| `id` | object-id | Oracle id |
+| `owner` | account | Oracle account |
+| `insurance` | share_type | Locked insurance bond |
+| `fee_percent` | uint16 (bp) | Oracle % of losers' pool |
+| `fixed_fee` | share_type | Per-market fixed fee |
+| `rules_url` | string | Rules/profile URL |
+| `active_since` | time | First activation time |
+| `last_active_time` | time | Last activity time |
+| `banned_until` | time | `0` not banned; `time_point_sec::maximum()` = permanent |
+| `markets_accepted` | uint32 | Reputation counter |
+| `markets_resolved` | uint32 | Reputation counter |
+| `no_contest_count` | uint32 | Markets voided |
+| `missed_count` | uint32 | Missed resolution deadlines |
+| `disputes_received` | uint32 | Disputes filed against this oracle |
+| `disputes_lost` | uint32 | Disputes where oracle was overturned |
+| `disputes_won` | uint32 | Disputes where oracle was upheld |
+| `disputes_auto_closed` | uint32 | Disputes that hit auto-close |
+| `dispute_responses_missed` | uint32 | Missed dispute-response deadlines |
+| `total_volume_resolved` | share_type | Cumulative resolved volume |
+| `total_insurance_slashed` | share_type | Cumulative insurance slashed |
+| `avg_resolution_time` | uint32 | Avg seconds to resolve |
+| `penalty_stamps` | uint32 | Active penalty stamps (10-day decay) |
+| `bans_received` | uint32 | Ban count |
+| `last_penalty_stamp_time` | time | Time of most recent penalty stamp |
+| `auto_accept_creator` | account | Auto-accept policy (empty = any) |
+| `auto_accept_resolver` | account | Auto-accept policy |
+| `auto_accept` | bool | Auto-accept enabled |
+| `banned_by` | account | Resolver that set `banned_until` (empty if unset); the account allowed to `pm_unban` |
+
+### 7.2 `pm_market_object`
+| Field | Type | Description |
+|---|---|---|
+| `id` | object-id | Market id |
+| `creator` | account | Creator |
+| `oracle` | account | Oracle |
+| `market_type` | uint8 | `0` binary (CPMM), `1` multi (LMSR) |
+| `outcome_count` | uint8 | Number of outcomes (binary = 2) |
+| `url` | string | Resolution criteria/title |
+| `status` | int8 | `-1` deleted, `0` waiting, `1` active, `2` closed, `3` resolved |
+| `payout_status` | uint8 | `0` none, `1` pending, `2` paid, `3` disputed |
+| `created_time` | time | Creation time |
+| `accept_deadline` | time | Pending markets only: `created_time + pm_oracle_accept_window_sec`. If the oracle hasn't accepted/rejected by then the cron voids the market (refund seed) and emits `pm_market_expired`. `0` (epoch) for markets active at creation |
+| `betting_expiration` | time | Betting closes |
+| `result_expiration` | time | Oracle deadline |
+| `resolved_outcome` | int16 | Winning outcome; `-1` if unresolved |
+| `reserve_a` | share_type | Binary CPMM reserve A |
+| `reserve_b` | share_type | Binary CPMM reserve B |
+| `k` | uint128 | CPMM invariant `reserve_a × reserve_b` |
+| `a_bets_sum` | share_type | Binary: total staked on side A |
+| `b_bets_sum` | share_type | Binary: total staked on side B |
+| `lmsr_b` | share_type | Multi: LMSR liquidity parameter |
+| `lmsr_subsidy` | share_type | Multi: LMSR subsidy |
+| `bets_sum` | share_type | Total staked across all outcomes |
+| `liquidity_sum` | share_type | Total LP liquidity |
+| `oracle_fee_percent` | uint16 (bp) | Frozen oracle fee % |
+| `creator_fee_percent` | uint16 (bp) | Creator fee % |
+| `liquidity_fee_percent` | uint16 (bp) | LP fee % |
+| `oracle_fixed_fee` | share_type | Frozen oracle fixed fee |
+| `liquidity_fee_earned` | share_type | Accrued LP fees |
+| `forfeit_pool` | share_type | Forfeited commit penalties added to winners' pool |
+| `time_penalty_type` | uint8 | Time-decay penalty model |
+| `time_penalty_value` | uint32 | Penalty parameter |
+| `penalty_curve_type` | uint8 | Penalty curve |
+| `allow_early_resolution` | bool | |
+| `allow_cancellation` | bool | |
+| `allow_batch` | bool | |
+| `allow_instant_bet` | bool | |
+| `endogeneity_tier` | uint8 | `1`/`2`/`3` |
+| `current_epoch` | uint32 | Current batch epoch |
+| `dispute_mode` | uint8 | `0` committee / `1` account |
+| `dispute_resolver` | account | Configured resolver (account mode) |
+| `dispute_penalty_percent` | int16 (bp) | Oracle penalty policy on successful dispute |
+| `metadata` | string | Raw client JSON |
+| `decision_url` | string | Oracle's cited evidence link, set at resolution (empty until resolved) |
+| `decision_reason` | string | Oracle's free-text justification, set at `pm_resolve_market` (or the NO-CONTEST reason at `pm_no_contest`). Stored on-chain, readable via `get_market` |
+
+### 7.3 `pm_outcome_object`
+| Field | Type | Description |
+|---|---|---|
+| `id` | object-id | |
+| `market` | object-id | Owning market |
+| `outcome_index` | uint8 | Index within the market |
+| `label` | string | Outcome label |
+| `q` | share_type | LMSR quantity |
+| `bets_sum` | share_type | Total staked on this outcome |
+| `weight_sum` | share_type | Total curve weight on this outcome |
+| `bets_count` | uint32 | Number of bets |
+
+### 7.4 `pm_bet_object`
+| Field | Type | Description |
+|---|---|---|
+| `id` | object-id | Bet id |
+| `market` | object-id | Owning market |
+| `account` | account | Bettor |
+| `side` | int8 | Binary: `0`/`1`; multi: `-1` |
+| `outcome_index` | int16 | Multi: `0..N-1`; binary: `-1` |
+| `amount` | share_type | Stake |
+| `weight` | share_type | Curve weight received |
+| `price` | uint64 | Execution price (fixed-point) |
+| `time_penalty` | uint32 | Time penalty (`1e6 = 100%`) |
+| `mode` | uint8 | `0` instant, `1` batch |
+| `epoch` | uint32 | Batch epoch (for queued bets) |
+| `status` | uint8 | `0` active, `1` cancelled, `2` refunded, `3` resolved, `5` queued, `6` revealed-pending |
+| `min_tokens` | share_type | Slippage floor for queued batch bets |
+| `resolved_amount` | share_type | Realized payout after settlement |
+| `created_time` | time | |
+
+### 7.5 `pm_liquidity_object`
+| Field | Type | Description |
+|---|---|---|
+| `id` | object-id | LP position id |
+| `market` | object-id | Owning market |
+| `provider` | account | LP account; **empty = Lazy Pool** |
+| `amount` | share_type | Provided liquidity |
+| `weight_a` | share_type | Binary reserve share A |
+| `weight_b` | share_type | Binary reserve share B |
+| `b_share` | share_type | LMSR share |
+| `sec_to_expiration` | uint32 | Position term |
+| `deposit_time` | time | |
+| `earned_fee` | share_type | Accrued fees |
+| `status` | uint8 | `0` active, `3` resolved/closed |
+
+### 7.6 `pm_commit_object`
+| Field | Type | Description |
+|---|---|---|
+| `id` | object-id | |
+| `market` | object-id | Owning market |
+| `account` | account | Committer |
+| `commitment` | sha256 | Commitment hash |
+| `escrow_amount` | share_type | Locked escrow |
+| `no_reveal_fee_percent` | uint16 (bp) | Snapshotted penalty % at commit |
+| `commit_time` | time | |
+| `reveal_deadline` | time | |
+| `status` | uint8 | `0` committed, `1` revealed, `2` forfeited |
+
+> Note: there is no direct "get_commit" API method in HF14; commits surface via history/virtual ops.
+
+### 7.7 `pm_dispute_object`
+| Field | Type | Description |
+|---|---|---|
+| `id` | object-id | |
+| `market` | object-id | Disputed market |
+| `disputer` | account | Who filed |
+| `dispute_fee` | share_type | Escrowed fee |
+| `reason` | string | Dispute reason |
+| `filed_time` | time | |
+| `oracle_response_deadline` | time | Oracle must respond by |
+| `dispute_mode` | uint8 | `0` committee / `1` account |
+| `voting_end_time` | time | Committee voting closes |
+| `auto_close_time` | time | Anti-freeze fallback |
+| `proposed_outcome` | int16 | Outcome the disputer proposes |
+| `status` | uint8 | `0` open, `1` oracle-wrong, `2` oracle-right, `3` auto-closed |
+| `oracle_response` | string | Oracle's public rebuttal (empty until it responds via `pm_dispute_oracle_respond`) |
+| `oracle_response_time` | time | When the rebuttal was posted (`0` = none) |
+
+### 7.8 `pm_dispute_vote_object`
+| Field | Type | Description |
+|---|---|---|
+| `id` | object-id | |
+| `market` | object-id | Disputed market |
+| `voter` | account | Voter |
+| `vote_outcome` | int16 | Correct outcome, or `-1` = uphold oracle |
+| `vote_percent` | int16 | Conviction/penalty `[-10000, 10000]` |
+| `time` | time | Vote time |
+
+### 7.9 `pm_lazy_pool_object` (singleton, id 0)
+| Field | Type | Description |
+|---|---|---|
+| `id` | object-id | Always instance 0 |
+| `total_shares` | share_type | Total pool shares outstanding |
+| `free_balance` | share_type | Unallocated VIZ |
+| `allocated_balance` | share_type | VIZ allocated into markets |
+| `earned_balance` | share_type | Cumulative earnings (monotonic) |
+| `reward_per_share` | uint128 | Reward accumulator (`LAZY_POOL_PRECISION = 1e9`) |
+| `leverage_fund_used` | share_type | Total active leverage loans |
+
+### 7.10 `pm_lazy_deposit_object`
+| Field | Type | Description |
+|---|---|---|
+| `id` | object-id | |
+| `account` | account | Depositor |
+| `shares` | share_type | Pool shares held |
+| `principal` | share_type | Original deposited principal |
+| `reward_snapshot` | uint128 | Reward accumulator snapshot |
+| `pending_rewards` | share_type | Unclaimed rewards |
+| `unlock_time` | time | Lock expiry (`pm_lazy_lock_sec` after deposit) |
+
+### 7.11 `pm_lazy_allocation_object`
+Per-market lazy-pool allocation record. Readable via `get_lazy_allocations` (list) and
+`get_market_lazy_allocation(market_id)` (§5).
+`id`, `market`, `amount`, `original_amount`, `recalled_amount`, `returned_amount`,
+`bets_sum_at_check`, `check_step` (uint32 `0..10`), `last_check_time` (time), `status` (uint8: `0` active, `1` returned).
+
+### 7.12 `pm_leverage_position_object`
+| Field | Type | Description |
+|---|---|---|
+| `id` | object-id | Position id |
+| `market` | object-id | Owning market |
+| `account` | account | Position owner |
+| `outcome_index` | int16 | Binary side `0`/`1` |
+| `collateral` | share_type | Bettor's own stake |
+| `loan` | share_type | Pool loan |
+| `total_bet` | share_type | `collateral + loan` deployed |
+| `tokens` | share_type | Weight received from the AMM |
+| `bet` | object-id | Underlying `pm_bet` id |
+| `pool_profit` | share_type | `loan × R%` |
+| `liquidation_threshold` | share_type | `loan × (1 + R%)` |
+| `status` | uint8 | `0` active, `1` liquidated, `2` resolved_won, `3` resolved_lost, `4` closed_voluntary, `5` converted |
+| `liquidated_at` | time | |
+| `liquidated_by_bet` | object-id | The opposing bet that triggered liquidation |
+| `cancel_value_at_liquidation` | share_type | |
+| `pool_received` | share_type | VIZ returned to pool at close |
+| `bettor_received` | share_type | VIZ returned to bettor at close |
+| `created_time` | time | |
+| `last_update` | time | |
+
+### 7.13 `pm_creator_ban_object`
+| Field | Type | Description |
+|---|---|---|
+| `id` | object-id | |
+| `creator` | account | Banned creator |
+| `banned_until` | time | Ban expiry (`maximum()` = permanent; a past time = not banned) |
+| `ban_count` | uint32 | Number of bans received |
+| `banned_by` | account | Resolver that set the current ban (the account allowed to `pm_unban`) |
+
+### 7.14 `pm_oracle_api_object` (from `get_oracle`)
+| Field | Type | Description |
+|---|---|---|
+| `oracle` | `pm_oracle_object` | The raw oracle object (§7.1) |
+| `reliability_score` | uint32 (bp) | Non-consensus heuristic `[0..10000]`: blends resolution success and dispute-win ratios, minus `1000` per ban |
+
+### 7.15 `pm_position_api_object` (from `get_account_positions`)
+| Field | Type | Description |
+|---|---|---|
+| `bet` | `pm_bet_object` | The bet (§7.4) |
+| `expected_payout` | share_type | Parimutuel payout if this side wins (or realized payout once settled). Mirrors settlement exactly |
+| `market_status` | int8 | The market's `status` |
+| `resolved_outcome` | int16 | The market's `resolved_outcome` (`-1` if unresolved) |
+
+### 7.16 `pm_market_weight_sums_api_object` (from `get_market_weight_sums`)
+| Field | Type | Description |
+|---|---|---|
+| `market_type` | uint8 | `0` binary / `1` multi |
+| `bets_sum` | share_type | Total staked |
+| `outcomes` | `pm_weight_entry[]` | Per-outcome breakdown |
+
+`pm_weight_entry`: `outcome_index` (int16), `label` (string), `bets_sum` (share_type), `weight_sum` (share_type).
+
+### 7.17 `pm_dispute_votes_api_object` (from `get_dispute_votes`)
+| Field | Type | Description |
+|---|---|---|
+| `votes` | `pm_dispute_vote_object[]` | All ballots (§7.8) |
+| `uphold_weight` | int64 | **Legacy** rough tally (Σ`|vote_percent|` upholding oracle) |
+| `challenge_weight` | int64 | Legacy: Σ`|vote_percent|` backing a change |
+| `total_weight` | int64 | `uphold + challenge` (legacy) |
+| `challenger_leads` | bool | Legacy: challenge share ≥ approve-min |
+| `proposed_outcome` | int16 | Disputer's proposed outcome |
+| `participation_shares` | int64 | Σ voter weight (vesting-shares) that has voted |
+| `electorate_shares` | int64 | `total_vesting_shares + pool_NAV→shares` (quorum base) |
+| `quorum_required_shares` | int64 | `electorate × pm_dispute_approve_min_percent` |
+| `quorum_percent_bp` | int32 | `participation / electorate` (bp) |
+| `quorum_reached` | bool | `participation ≥ quorum_required` |
+| `oracle_defense_shares` | int64 | Σ rshares defending the oracle |
+| `change_shares` | int64 | Σ rshares backing an outcome change |
+| `outcome_change_shares` | int64[] | Per-outcome backing rshares (size = `outcome_count`) |
+| `expected_uphold` | bool | `true` ⇒ oracle resolution stands if finalized now |
+| `expected_outcome` | int16 | Outcome that would be set at finalize now |
+| `expected_consensus_strength_bp` | int32 | `winning / participation` (bp); `0` when uphold |
+
+### 7.18 `pm_market_meta_object` (from `get_market_meta` / `list_markets_by_category`)
+| Field | Type | Description |
+|---|---|---|
+| `id` | object-id | |
+| `market` | object-id | Owning market |
+| `category` | string | Parsed from metadata JSON |
+| `subcategory` | string | |
+| `tags` | string | Comma-joined |
+| `banned_jurisdictions` | string | Comma-joined ISO codes; empty = allowed everywhere |
+| `expiry` | time | Prune time (dispute window close + TTL) |
+
+### 7.19 `pm_kline_api_object` (from `get_market_kline`)
+| Field | Type | Description |
+|---|---|---|
+| `seq` | uint32 | 0-based contiguous index of the change within the market |
+| `timestamp` | uint32 | **Unix seconds** (x coordinate) |
+| `reason` | uint8 | `0` bet, `1` cancel, `2` liquidation, `3` batch settle, `4` leverage open, `5` leverage resolve |
+| `bets_sum` | share_type | Total staked across all outcomes at this point |
+| `weights` | share_type[] | Per-outcome staked weight (y values), index = `outcome_index`. For binary: `[a_bets_sum, b_bets_sum]` |
+
+For charting: `x = timestamp`, `y[i] = weights[i]`; implied probability of outcome `i` =
+`weights[i] / Σ weights`.
+
+### 7.20 `pm_leverage_quote_api_object` (from `get_leverage_quote`)
+| Field | Type | Description |
+|---|---|---|
+| `available` | bool | `true` ⇒ `max_loan > 0` (some leverage possible) |
+| `outcome_index` | int16 | Echoed side (0/1) |
+| `collateral` | share_type | Echoed collateral |
+| `max_loan` | share_type | Largest solvent loan (`0` if none qualifies) |
+| `max_leverage_x100` | uint32 | `(collateral+max_loan)/collateral × 100` (`100` = 1.00×) |
+| `pool_free_amount` | share_type | `free_balance − leverage_fund_used` |
+| `fund_available` | share_type | `free_balance × pm_leverage_fund_percent − leverage_fund_used` |
+| `per_position_cap` | share_type | `fund_available × pm_leverage_max_per_position_bp` |
+| `market_position_cap` | share_type | `liquidity_sum × pm_leverage_max_position_ratio_percent` |
+| `pool_profit_percent` | uint16 | `R` (plain %) |
+| `safety_margin_percent` | uint16 | `S` (plain %) |
+| `max_slippage_percent` | uint16 | `SL` (plain %) |
+| `m_factor_percent` | uint16 | worst-opposing m-factor (plain %) |
+| `expiration_buffer_sec` | uint32 | Leverage disabled this long before `betting_expiration` |
+| `auto_close_time` | time | `betting_expiration − buffer` (protocol force-close point) |
+| `stops` | `pm_leverage_stop[]` | Up to 12 solvent slider stops (`0 < loan ≤ max_loan`, ≥1.01×) |
+| `failed_constraints` | `pm_leverage_constraint[]` | Populated when `!available` |
+
+`pm_leverage_stop`: `leverage_x100` (uint32), `loan`, `total_bet`, `expected_tokens`, `pool_profit`,
+`liquidation_threshold`, `current_cancel_value`, `worst_case_cancel_value` (all share_type).
+`pm_leverage_constraint`: `constraint` (string key: `leverage_disabled` / `cpmm_binary_only` /
+`market_inactive` / `expiration_buffer` / `min_market_liquidity` / `fund_availability` /
+`position_size` / `solvency`), `reason` (string).
+
+### 7.21 `pm_leverage_close_preview_api_object` (from `get_leverage_close_preview`)
+| Field | Type | Description |
+|---|---|---|
+| `position_id` | int64 | Echoed position |
+| `outcome_index` | int16 | Position side |
+| `cancel_value` | share_type | VIZ the tokens fetch from the curve now |
+| `pool_obligation` | share_type | `liquidation_threshold` → returned to the pool |
+| `bettor_receives` | share_type | `cancel_value − pool_obligation` (floored 0) |
+| `collateral` | share_type | Original bettor stake |
+| `loan` | share_type | Pool loan |
+| `pool_profit_charge` | share_type | Pool's fixed profit on the loan |
+| `closeable` | bool | `cancel_value ≥ pool_obligation` (else the protocol liquidates) |
+| `loss_vs_collateral` | int64 | `collateral − bettor_receives` (negative = profit) |
+| `loss_percent_bp` | int32 | `loss_vs_collateral / collateral` (bp) |
+
+### 7.22 `pm_leverage_convert_preview_api_object` (from `get_leverage_convert_preview`)
+| Field | Type | Description |
+|---|---|---|
+| `position_id` | int64 | Echoed position |
+| `outcome_index` | int16 | Position side |
+| `cancel_value` | share_type | VIZ the tokens fetch now |
+| `pool_obligation` | share_type | Loan + pool profit (repaid on convert) |
+| `current_profit` | share_type | `cancel_value − pool_obligation` |
+| `conversion_profit_cost_percent` | uint16 | Median value the convert op **must** echo |
+| `conversion_fee` | share_type | `current_profit × cost% / 100` |
+| `total_user_payment` | share_type | `pool_obligation + conversion_fee` (debited on convert) |
+| `convertible` | bool | `current_profit > 0` |
+
+### 7.23 `pm_market_categories_api_object` (from `get_market_categories`)
+| Field | Type | Description |
+|---|---|---|
+| `categories` | `pm_category_count[]` | Sorted by count desc |
+| `hot_tags` | `pm_tag_count[]` | Top 20 tags by count (jurisdiction-* excluded) |
+
+`pm_category_count`: `category` (string), `count` (uint32), `subcategories` (`pm_subcategory_count[]`).
+`pm_subcategory_count`: `subcategory` (string), `count` (uint32).
+`pm_tag_count`: `tag` (string), `count` (uint32).
+
+### 7.24 `pm_market_full_api_object` (from `get_market_full`)
+| Field | Type | Description |
+|---|---|---|
+| `market` | `pm_market_object` | The market (§7.2) |
+| `outcomes` | `pm_outcome_object[]` | Outcomes (§7.3); empty for binary markets |
+| `weight_sums` | `pm_market_weight_sums_api_object` | Per-outcome amount + curve weight (§7.16) |
+| `oracle` | `pm_oracle_api_object` \| null | The market's oracle + reliability (§7.14); null if not found |
+| `meta` | `pm_market_meta_object` \| null | Parsed metadata (§7.18); null if not indexed |
+| `my_positions` | `pm_position_api_object[]` | The `account` arg's bets on this market (empty if no account) |
+| `my_leverage_positions` | `pm_leverage_position_object[]` | The account's leverage positions on this market |
+| `my_liquidity` | `pm_liquidity_object[]` | The account's LP positions on this market |
+
+`oracle` and `meta` are optional (JSON `null` when absent). The `my_*` arrays are empty unless the
+optional `account` argument was supplied.
+
+---
+
+## 8. Enum / status quick reference
+
+| Enum | Values |
+|---|---|
+| `market.market_type` | `0` binary (CPMM), `1` multi (LMSR) |
+| `market.status` | `-1` deleted, `0` waiting, `1` active, `2` closed, `3` resolved |
+| `market.payout_status` | `0` none, `1` pending, `2` paid, `3` disputed |
+| `market.dispute_mode` | `0` committee, `1` account |
+| `market.endogeneity_tier` | `1` econ-data, `2` sports, `3` political |
+| `bet.status` | `0` active, `1` cancelled, `2` refunded, `3` resolved, `5` queued, `6` revealed-pending |
+| `bet.mode` / `place_bet.mode` | `0` instant, `1` batch |
+| `liquidity.status` | `0` active, `3` resolved/closed |
+| `commit.status` | `0` committed, `1` revealed, `2` forfeited |
+| `dispute.status` | `0` open, `1` oracle-wrong, `2` oracle-right, `3` auto-closed |
+| `leverage_position.status` | `0` active, `1` liquidated, `2` resolved_won, `3` resolved_lost, `4` closed_voluntary, `5` converted |
+| `leverage_liquidate.reason` | `0` opposing_bet, `1` cancel_bet, `2` expiration |
+| `lazy_allocation.status` | `0` active, `1` returned |
+| `kline.reason` | `0` bet, `1` cancel, `2` liquidation, `3` batch settle, `4` leverage open, `5` leverage resolve |
+| `dispute_vote.vote_outcome` | `-1` uphold oracle; `≥0` proposed correct outcome |
+
+---
+
+## 9. Chain (governance) properties — `get_pm_chain_properties`
+
+These are median-voted validator params (part of `versioned_chain_properties`). Values shown are
+**mainnet defaults**; a client must read live values via `get_pm_chain_properties`. Assets are
+VIZ; `*_percent` are **bp (10000 = 100%)** unless the row says otherwise.
+
+| Field | Default | Unit | Meaning |
+|---|---|---|---|
+| `pm_oracle_registration_fee` | 10.000 VIZ | asset | Oracle registration fee → committee fund |
+| `pm_min_oracle_insurance` | 5000.000 VIZ | asset | Minimum insurance bond |
+| `pm_market_creation_fee` | 5.000 VIZ | asset | Market creation fee → committee fund |
+| `pm_min_liquidity` | 100.000 VIZ | asset | Minimum seed liquidity |
+| `pm_max_outcomes` | 10 | count | Max outcomes per multi market (`≤ 16`) |
+| `pm_max_market_duration` | 31536000 | sec | Max market lifetime (≤ 1 year) |
+| `pm_max_oracle_fee_percent` | 500 | bp | Cap on oracle fee % (5%) |
+| `pm_oracle_accept_window_sec` | 3600 | sec | Time for the named oracle to accept/reject a pending market (1h). On expiry the cron refunds the seed liquidity (NOT the creation fee) and voids the market → `pm_market_expired` |
+| `pm_listing_min_coverage_percent` | 250 | **coverage %** | Hide markets whose oracle insurance covers < this % of bets (250 = 2.5×); enforced by `list_markets`/`list_markets_by_category` (revealed via `show_risky`) |
+| `pm_betting_min_coverage_percent` | 150 | **coverage %** | Advisory: below this coverage a client should require an explicit risk confirmation. Not enforced on-chain; must be `≤ pm_listing_min_coverage_percent` |
+| `pm_default_time_penalty_percent` | 50 | bp | Default time penalty |
+| `pm_max_time_penalty` | 1000000 | 1e6=100% | Max time penalty on profit |
+| `pm_dispute_fee` | 1000.000 VIZ | asset | Dispute filing fee |
+| `pm_dispute_grace_sec` | 43200 | sec | Payout grace after resolution (12h) |
+| `pm_oracle_dispute_response_sec` | 43200 | sec | Oracle response window (12h) |
+| `pm_dispute_auto_close_sec` | 1209600 | sec | Anti-freeze auto-close (14d) |
+| `pm_dispute_vote_period_sec` | 259200 | sec | Committee voting period (3d) |
+| `pm_dispute_approve_min_percent` | 1000 | bp | Quorum / participation threshold |
+| `pm_oracle_penalty_percent` | 500 | bp | Insurance slashed on missed deadline |
+| `pm_no_contest_penalty_percent` | 5000 | bp | % of dispute fee on no-contest (50%) |
+| `pm_dispute_reward_multiplier` | 30000 | bp | Dispute reward multiplier (10000=1×, default 3×) |
+| `pm_batch_epoch_blocks` | 20 | blocks | Batch epoch length (~60s) |
+| `pm_reveal_window_blocks` | 200 | blocks | Reveal window (~10min) |
+| `pm_commit_no_reveal_penalty_percent` | 2000 | bp | No-reveal penalty → winners' pool (20%) |
+| `pm_min_batch_bet` | 1.000 VIZ | asset | Anti-dust minimum batch bet |
+| `pm_commit_reveal_enabled` | true | bool | Commit-reveal kill-switch |
+| `pm_processing_cap_per_block` | 200 | count | Bounded per-block virtual-op work |
+| `pm_lazy_pool_enabled` | true | bool | Lazy-pool kill-switch |
+| `pm_lazy_alloc_percent` | 2000 | bp | Free balance allocated per market |
+| `pm_lazy_max_total_alloc_percent` | 7000 | bp | Cap on total allocation |
+| `pm_lazy_lock_sec` | 604800 | sec | Deposit lock (7d) |
+| `pm_lazy_recall_step_percent` | 1000 | bp | Recalled per idle step |
+| `pm_lazy_emergency_penalty_percent` | 5000 | bp | Emergency-withdraw penalty on profit |
+| `pm_lazy_min_liquidity_fee_percent` | 200 | bp | Min market LP fee for the lazy pool to subsidize it (2%); markets below this get no pool liquidity |
+| `pm_leverage_enabled` | false | bool | **Leverage kill-switch (off by default)** |
+| `pm_leverage_fund_percent` | 10 | **percent** | % of free balance usable for loans (F) |
+| `pm_leverage_max_per_position_bp` | 20 | bp | Fund-available per position (P=0.2%) |
+| `pm_leverage_pool_profit_percent` | 10 | **percent** | Pool profit per loan (R) |
+| `pm_leverage_safety_margin_percent` | 1 | **percent** | Open-time safety buffer (S) |
+| `pm_leverage_max_slippage_percent` | 10 | **percent** | Max price impact per bet (SL) |
+| `pm_leverage_min_market_liquidity` | 5000.000 VIZ | asset | Min liquidity for leverage |
+| `pm_leverage_max_position_ratio_percent` | 5 | **percent** | Max position as % of `liquidity_sum` |
+| `pm_leverage_expiration_buffer_sec` | 86400 | sec | Leverage disabled N sec before expiration |
+| `pm_leverage_m_factor_percent` | 50 | **percent** | `M_effective = M_max × this%` |
+| `pm_conversion_profit_cost_percent` | 50 | **percent** | Fee % of unrealized profit on convert |
+
+> **Percent-scale trap for library authors:** the `pm_leverage_*` and `pm_conversion_*` knobs use
+> plain percent (`10 = 10%`, validated `≤ 100`), NOT basis points, unlike every other `*_percent`
+> in this table. `pm_leverage_max_per_position_bp` is the one leverage knob that is genuinely bp.
+
+---
+
+## 10. Fixed limits (compile-time, from `config.hpp`)
+
+| Constant | Value | Applies to |
+|---|---|---|
+| `MAX_PM_DECISION_URL_LEN` | 256 | `pm_resolve_market.decision_url` |
+| `MAX_PM_PROFILE_URL_LEN` | 256 | `pm_oracle_register.rules_url` |
+| `MAX_PM_DISPUTE_REASON_LEN` | 1024 | `pm_no_contest.reason`, `pm_dispute_create.reason` |
+| `MAX_PM_MARKET_TITLE_LEN` | 256 | `pm_create_market.url` |
+| `MAX_PM_OUTCOME_LABEL_LEN` | 64 | each `pm_create_market.outcomes[i]` |
+| `MAX_PM_OUTCOMES_PER_MARKET` | 16 | hard ceiling for `pm_max_outcomes` |
+| `LAZY_POOL_PRECISION` | 1e9 | `reward_per_share` accumulator scale |
+
+`metadata` on `pm_create_market` has **no length cap** at the protocol level (like `custom_operation`).
+
+---
+
+## 11. Implementation checklist per library
+
+1. **Operation builders** — 23 user ops (§3), each serialized as `["", {fields}]` in JSON
+ order, with `extensions: []`. Percent-scale per field (§1.1 / §9 trap).
+2. **Asset handling** — VIZ, 3 decimals; format/parse `"x.yyy VIZ"`. Internal `share_type` = ×1000 integer.
+3. **API client** — 29 read methods (§5) via `call("prediction_market_api", …, [args])`, `limit ≤ 1000`.
+4. **Object models** — decode §7 objects; keep `uint128`/large `int64` as big-int/string in JS.
+5. **Virtual-op parsing** — recognize the 12 vops (§4) in account/block history.
+6. **Enums** — map §8 status codes to human labels.
+7. **Metadata** — write the `category`/`subcategory`/`tags`/`banned_jurisdictions` convention (§6).
+8. **Governance** — surface `get_pm_chain_properties` (§9) so UIs read live limits, not hard-coded defaults.
+
+---
+
+*Source of truth (regenerate this doc if these change):*
+`libraries/protocol/include/graphene/protocol/pm_operations.hpp`,
+`pm_virtual_operations.hpp`, `operations.hpp`,
+`libraries/chain/include/graphene/chain/pm_objects.hpp`,
+`libraries/protocol/include/graphene/protocol/chain_operations.hpp` (`chain_properties_pm`),
+`libraries/protocol/include/graphene/protocol/config.hpp` (`MAX_PM_*`),
+`plugins/prediction_market_api/` (`prediction_market_api.{hpp,cpp}`, `meta_object.hpp`, `kline_object.hpp`).
diff --git a/.qoder/docs/prediction-market-thin-client-plan.md b/.qoder/docs/prediction-market-thin-client-plan.md
new file mode 100644
index 0000000000..11eb4d051d
--- /dev/null
+++ b/.qoder/docs/prediction-market-thin-client-plan.md
@@ -0,0 +1,301 @@
+# Prediction Markets — Thin-Client Implementation Plan & Prototype Reconciliation
+
+> **Part A** is an implementation plan for a thin client (js / php / python) built **only** from the
+> node wire contract in [`prediction-market-library-integration-spec.md`](./prediction-market-library-integration-spec.md).
+> **Part B** reconciles that plan against the current PHP+`app.js` prototype documented in
+> [`pm-workflow/prototype-frontend-design-document.md`](./pm-workflow/prototype-frontend-design-document.md):
+> a per-endpoint correspondence matrix (what we have vs. what the prototype has) plus a prioritized
+> list of node-side gaps to close.
+>
+> Terminology: **op** = signed operation (§3 of the spec); **API** = `prediction_market_api` read
+> method (§5); **vop** = virtual operation (§4); bp = basis points.
+
+---
+
+# Part A — Thin-client implementation plan
+
+## A.1 Target architecture
+
+The prototype is a server-mediated app: the browser talks to PHP `/api/*` endpoints, PHP holds a
+session, signs/settles on behalf of the user, and returns pre-computed JSON. The thin client
+**removes the server**: it holds the user's VIZ keypair, reads chain state directly over JSON-RPC,
+and **signs + broadcasts** every state change itself.
+
+```
+┌────────────────────────────────────────────────────────────┐
+│ UI layer (screens; framework-agnostic) │
+├────────────────────────────────────────────────────────────┤
+│ View-model / aggregation (compose reads, derive badges, │
+│ compute leverage previews, risk & jurisdiction gating) │
+├──────────────────────────┬───────────────────────────────────┤
+│ Read client │ Write client │
+│ call(prediction_market_ │ tx builder → op array → │
+│ api, method, [args]) │ sign(active/regular key) → │
+│ + generic account/ │ broadcast_transaction[_synchronous]│
+│ history reads │ │
+├──────────────────────────┴───────────────────────────────────┤
+│ Core: JSON-RPC transport · asset codec · key mgmt · ref-block │
+└────────────────────────────────────────────────────────────┘
+```
+
+Three hard rules carried over from the spec:
+- **Asset codec:** VIZ = 3 decimals. Ops take `asset` strings (`"10.000 VIZ"`); read objects return
+ `share_type` integers (×1000). One shared codec both ways.
+- **Percent scales are not uniform:** most `*_percent` are bp; `time_penalty` is `1e6=100%`; the
+ `pm_leverage_*` / `pm_conversion_*` governance knobs are plain percent. See spec §1.1 / §9.
+- **No server identity:** replace session/cookie with local key management; the `user` blob becomes
+ derived reads (account balances + PM objects).
+
+## A.2 Build layers
+
+1. **Transport & core** — JSON-RPC client (`call`), tx assembly (ref-block, expiration, chain-id),
+ secp256k1 canonical signing, `broadcast_transaction_synchronous`. Most of this already exists in
+ `viz-js` / the php / python VIZ libs; PM adds no new transport.
+2. **Asset & percent codec** — parse/format `"x.yyy VIZ"` ↔ integer milli-VIZ; bp/permille/percent
+ helpers. Centralize to avoid the scale traps.
+3. **Operation builders (21)** — one builder per user op (spec §3), each emitting
+ `["", {fields}]` with `extensions: []` and correct field order. Group: oracle (register,
+ update, accept), market (create, resolve, no_contest), betting (place, commit, reveal, cancel,
+ transfer), liquidity (add, withdraw), disputes (create, vote, resolve), lazy pool (deposit,
+ withdraw), leverage (open, close, convert).
+4. **Read client (23)** — thin wrappers over the API methods (spec §5) returning typed models.
+5. **View-models** — aggregate multiple reads into the screen shapes the prototype expects
+ (enriched market view, oracle profile, positions, boosts). This is where the prototype's
+ single fat endpoints get reassembled client-side.
+6. **Virtual-op & history decoder** — map account/block history (incl. the 11 vops, spec §4) into
+ the 22 balance-history rows the prototype's wallet renders.
+
+## A.3 Milestones
+
+| M | Scope | Ops used | API used | Notes |
+|---|---|---|---|---|
+| **M0** | Transport, keys, asset codec, tx sign/broadcast | — | `get_pm_chain_properties` | Foundation; cache chain props at boot |
+| **M1** | Browse & market detail (read-only) | — | `list_markets`, `list_markets_by_*`, `get_market`, `get_market_outcomes`, `get_market_weight_sums`, `get_market_bets`, `get_market_liquidity`, `get_oracle`, `list_oracles`, `get_market_meta`, `list_markets_by_category` | Compose the enriched detail view client-side |
+| **M2** | Betting lifecycle | `pm_place_bet`, `pm_commit_bet`, `pm_reveal_bet`, `pm_cancel_bet`, `pm_transfer_position` | `get_account_positions` | Persist reveals in local storage; pin commit preimage (see gap I) |
+| **M3** | Market creation + oracle role | `pm_create_market`, `pm_oracle_register`, `pm_oracle_update`, `pm_oracle_accept_market`, `pm_resolve_market`, `pm_no_contest` | `list_markets_by_oracle` (pending queue), `get_creator_ban` | Fold insurance deposit/withdraw into `pm_oracle_update` |
+| **M4** | Disputes & governance | `pm_dispute_create`, `pm_dispute_vote` (committee), `pm_dispute_resolve` (account) | `get_dispute`, `get_dispute_votes` | Two dispute modes — richer than prototype (see B) |
+| **M5** | Liquidity + lazy pool | `pm_add_liquidity`, `pm_withdraw_liquidity`, `pm_lazy_deposit`, `pm_lazy_withdraw` | `get_lazy_pool`, `get_lazy_deposit` | Emergency withdraw = `pm_lazy_withdraw(emergency=true)` |
+| **M6** | Leverage / Boost | `pm_leverage_open`, `pm_leverage_close`, `pm_leverage_convert` | `get_account_leverage_positions`, `get_market_leverage_positions`, `get_lazy_pool` | **Previews computed client-side** unless node adds them (gap F) |
+| **M7** | Charts & history | — | `get_market_kline` | Wallet history from account-history + vops |
+
+## A.4 Cross-cutting concerns (client-owned policy)
+
+- **Jurisdiction** — user country is a client preference (localStorage). Hide markets whose
+ `pm_market_meta_object.banned_jurisdictions` contains it, or use the `jurisdiction` arg of
+ `list_markets_by_category`. No on-chain per-user jurisdiction.
+- **Risk gating** — `list_markets(show_risky)` hides under-insured markets (hardcoded 2.5×
+ coverage floor). Any betting-time risk confirmation is pure client UX (no on-chain `risk_confirm`).
+- **Commit-reveal resilience** — persist `{commit_id, side/outcome, amount, min_tokens, salt,
+ reveal_deadline}` locally and retry `pm_reveal_bet` on load; drop past deadline.
+- **Leverage previews** — reimplement the frozen max-leverage / slider-stops / close / convert math
+ (see `libraries/chain/include/graphene/chain/pm/leverage.hpp`) unless preview API methods are
+ added (gap F). Gate the whole feature on `pm_leverage_enabled`.
+- **Charts** — use `get_market_kline` (offset-from-newest paging) instead of the prototype's
+ reserve-snapshot probing.
+
+---
+
+# Part B — Prototype ↔ node correspondence
+
+The prototype exposes **58 HTTP endpoints**. Each maps to one of: a node **op**, an **API** read, a
+generic (non-PM) VIZ facility, or a client-side computation — or it has **no node equivalent yet**.
+
+Status legend: **✅ direct** (op/method exists, 1:1) · **⚠ partial** (exists but shape/semantics
+differ — client adapts) · **🔵 client-side** (no chain op needed; local logic or generic VIZ) ·
+**❌ gap** (missing on the node; needs a decision — see B.9).
+
+## B.1 Session, identity, config, preferences
+
+| Prototype endpoint | Node mapping | Status | Comment |
+|---|---|---|---|
+| `check-session`, `auth-code`, `load-session` | local keypair; reads = `get_accounts` (generic) | 🔵 | Server session replaced by key mgmt; `user` blob = account balances + PM objects |
+| `load-settings` | `get_pm_chain_properties` | ✅ | `min_risk_score_listing` → `pm_listing_min_coverage_percent`, `min_risk_score_betting` → `pm_betting_min_coverage_percent` (both now governance, see C). `dispute_grace_hours` = `pm_dispute_grace_sec`/3600 |
+| `update-user-preferences` (lang, jurisdiction) | — | 🔵 | Client-side prefs (localStorage / optional account JSON metadata). No PM op |
+| `register-oracle` | `pm_oracle_register` / `pm_oracle_update` | ✅ | Fee unit differs: prototype ‰ vs node **bp** — convert. Update path folds settings edits |
+| `register-creator` | — | 🔵 | No on-chain creator role and none needed: **any account** may `pm_create_market`. Anti-spam is the per-market `pm_market_creation_fee` (see B.11), not a role. Keep any "creator" gate purely client-side (gap K) |
+| `load-oracle-profile` | `get_oracle` (`pm_oracle_api_object`) | ⚠ | Node returns raw counters + `reliability_score` (**0–10000 bp**, not 0–100). `trust_score`, `risk_score` (coverage), `derived{}` rates must be computed client-side |
+
+## B.2 Browse & filter
+
+| Prototype endpoint | Node mapping | Status | Comment |
+|---|---|---|---|
+| `load-markets` | `list_markets(status, from, limit, show_risky)` | ⚠ | Same risk-floor behavior. No creator/oracle enrichment in one call — join with `get_oracle` client-side |
+| `load-markets-catalog` | `list_markets` / `list_markets_by_category` | ✅ **(extended)** | `list_markets_by_category` now takes `subcategory`, `tag`, and `sort` (newest/oldest/volume/expiration) — gap B closed. `list_markets` still status-scoped |
+| `load-categories` (taxonomy + counts + hot_tags) | `get_market_categories()` | ✅ **(added)** | Category/subcategory counts + top-20 hot tags over indexed markets — gap A closed |
+| `load-oracles` | `list_oracles(from, limit)` | ✅ | Each row already carries fee/insurance/`reliability_score` |
+| `load-committees` | — | 🔵/⚠ | "Committee" list = DAO voters (any staked account) for committee-mode, or a chosen resolver account for account-mode. No PM "list committees" method; pick resolver by account name |
+
+## B.3 Market detail & binary betting
+
+| Prototype endpoint | Node mapping | Status | Comment |
+|---|---|---|---|
+| `load-market-enriched` | `get_market_full(market_id, [account])` | ✅ **(added)** | One call: market + outcomes + weight sums + oracle + meta + the account's per-market bets/leverage/LP (gap G closed). Public `all_bets`/`all_liquidity` still via `get_market_bets`/`get_market_liquidity` |
+| `load-market` | `get_market` | ✅ | Lightweight refresh |
+| `place-bet` | `pm_place_bet` | ✅ | Handles binary (`side`) + batch (`mode=1`) + `min_tokens`. `risk_confirm` is client-side |
+| `commit-bet` | `pm_commit_bet` | ✅ | Commitment preimage now byte-pinned in spec §3.6.1 (binary LE layout + golden vector). Prototype's colon-string preimage is **wrong** for this node |
+| `reveal-bet` | `pm_reveal_bet` | ✅ | Same field set; persist for retry |
+| `cancel-bet` | `pm_cancel_bet` | ✅ | `min_return` = slippage floor |
+| `transfer-position` | `pm_transfer_position` | ✅ | Has `amount` (weight) + `memo` |
+
+## B.4 Multi-outcome markets
+
+| Prototype endpoint | Node mapping | Status | Comment |
+|---|---|---|---|
+| `create-market-multi` | `pm_create_market` (`market_type=1`, `outcomes[]`, `lmsr_b`) | ✅ | Node **unifies** binary/multi into one op. `lmsr_b` client-computed (spec §3.3) |
+| `place-bet-multi` | `pm_place_bet` (`outcome_index`, `side=-1`) | ✅ | Same op as binary |
+| `cancel-bet-multi` | `pm_cancel_bet` | ✅ | Same op (prototype's separate multi endpoint + its field-drift bugs disappear) |
+| `add-liquidity-multi` | `pm_add_liquidity` | ✅ | Unified; fixes prototype's "multi never calls multi endpoint" bug |
+| `withdraw-liquidity-multi` | `pm_withdraw_liquidity` | ✅ | Unified |
+| `resolve-market-multi` | `pm_resolve_market` (`winning_outcome`) | ✅ | Unified; single canonical `winning_outcome` key resolves prototype's `outcome`/`winning_outcome` mismatch |
+
+## B.5 Market creation & oracle role
+
+| Prototype endpoint | Node mapping | Status | Comment |
+|---|---|---|---|
+| `create-market` (binary) | `pm_create_market` (`market_type=0`) | ⚠ | `q`/`resolution`/localized titles/`category`/`tags`/`banned_jurisdictions` go into the free-form `metadata` JSON; only resolution-criteria/title → `url`; outcome labels → `outcomes[]`. Fees ‰→bp. `committee_id` → `dispute_mode`+`dispute_resolver` (see B.6) |
+| `oracle-deposit-insurance` / `oracle-withdraw-insurance` | `pm_oracle_update(insurance_delta)` | ✅ | Signed delta; withdraw blocked while active markets / below min |
+| `oracle-accept-market` | `pm_oracle_accept_market(accept=true, …)` | ✅ (richer) | Node also lets the oracle **quote** `oracle_fee_percent`/`oracle_fixed_fee` ≤ creator's ceiling; prototype had no negotiation |
+| `oracle-reject-market` | `pm_oracle_accept_market(accept=false)` | ✅ | Same op, `accept=false` |
+| `load-pending-markets` | `list_markets_by_oracle` + filter `status==0` | ⚠ | No dedicated "pending for oracle" query; filter client-side |
+| `resolve-market` (binary) | `pm_resolve_market` | ✅ | Carries `decision_url` + `decision_reason`, both stored on `pm_market_object` and readable via `get_market` (gap J closed) |
+| `oracle-no-contest` | `pm_no_contest(reason)` | ✅ | Node has it (prototype never wired a button — add one) |
+
+## B.6 Disputes & governance
+
+| Prototype endpoint | Node mapping | Status | Comment |
+|---|---|---|---|
+| `create-dispute` | `pm_dispute_create(proposed_outcome, reason)` | ⚠ | Prototype sends only `claim_url`; node requires **`proposed_outcome` + `reason`** (embed the claim link in `reason`). Fee = `pm_dispute_fee` |
+| `dispute-oracle-response` (oracle rebuttal text) | `pm_dispute_oracle_respond` | ✅ **(added)** | Oracle writes `response` onto the open dispute (within `oracle_response_deadline`); surfaces in `get_dispute` as `oracle_response` / `oracle_response_time` (gap D closed) |
+| `resolve-dispute` (single arbitrator) | **account mode:** `pm_dispute_resolve` · **committee mode:** `pm_dispute_vote` → cron `pm_dispute_finalize` | ⚠ | Prototype = single-arbitrator. Node splits: account-mode `pm_dispute_resolve` matches 1:1 (correct_outcome, penalty_amount, ban_oracle/creator +until); committee-mode is **stake-weighted DAO voting** with auto-finalize — a richer flow the prototype lacks. Client must support both (branch on `market.dispute_mode`). See `get_dispute_votes` projection. **Bans are a regulator/compliance feature, exclusive to account mode**: an account-mode resolver (e.g. a regulator) can bar both the oracle and the creator; committee/DAO mode only slashes + dings reputation, never bans (by design) — see spec §3.15 |
+| `unban-user` | `pm_unban(resolver, target, unban_oracle, unban_creator)` | ✅ **(added)** | Only the `banned_by` resolver may lift a ban it set; otherwise bans expire at `banned_until` (gap E closed) |
+| `load-committees` | — (see B.2) | ⚠ | Committee-mode voters = staked accounts (DAO); account-mode resolver = a named account |
+
+## B.7 Wallet, history, positions, LP, lazy pool
+
+| Prototype endpoint | Node mapping | Status | Comment |
+|---|---|---|---|
+| `load-history` (22 types) | account-history plugin + PM vops (spec §4) | ⚠ | Reconstruct rows from generic transfers + PM vops (`pm_payout`, `pm_batch_settle`, `pm_commit_forfeit`, `pm_oracle_missed_penalty`, `pm_leverage_*`, …). No PM-specific history endpoint |
+| `load-positions` | `get_account_positions` | ✅ | Returns bet + `expected_payout` + market status |
+| `load-payouts` | account-history vops (`pm_payout`/`pm_auto_payout`) | ⚠ | No dedicated payout-list API; derive from vop history |
+| `load-market-payouts` | `get_market_bets` (`resolved_amount`) or per-market `pm_payout` vops | ⚠ | No per-market payout method |
+| `withdraw-viz` | generic `transfer_operation` | 🔵 | Not PM |
+| Deposit / top-up (TON/USDT) | off-chain exchange | 🔵 | Not PM |
+| `add-liquidity` / `withdraw-liquidity` | `pm_add_liquidity` / `pm_withdraw_liquidity` | ✅ | Binary + multi unified |
+| `lazy-pool-deposit` | `pm_lazy_deposit` | ✅ | |
+| `lazy-pool-withdraw` | `pm_lazy_withdraw(shares, emergency=false)` | ✅ | |
+| `lazy-pool-emergency-withdraw` | `pm_lazy_withdraw(emergency=true)` | ✅ | Folded into one op |
+| `lazy-pool-info` | `get_lazy_pool` + `get_lazy_deposit` + `get_lazy_allocations` / `get_market_lazy_allocation` | ✅ | Pool + user deposit + `active_allocations[]` all covered (gap H closed); oracle penalty stamps ship on `pm_oracle_object` via `get_oracle` |
+
+## B.8 Leverage / Boost
+
+| Prototype endpoint | Node mapping | Status | Comment |
+|---|---|---|---|
+| `leverage-open` | `pm_leverage_open` | ✅ | `collateral`, `loan`/leverage, `min_tokens`, `max_slippage_percent`. Gated by `pm_leverage_enabled` |
+| `leverage-close` | `pm_leverage_close(min_return)` | ✅ | |
+| `leverage-convert` | `pm_leverage_convert(conversion_profit_cost)` | ✅ | Must equal median(`pm_conversion_profit_cost_percent`) |
+| `leverage-info` | `get_account_leverage_positions` | ✅ | Countdown computed from market expiration − buffer |
+| `leverage-pool-state` | `get_lazy_pool` | ⚠ | `free_balance`, `leverage_fund_used` present; `leverage_fund_available` computed from `pm_leverage_fund_percent` client-side |
+| `leverage-preview` | `get_leverage_quote(market_id, outcome_index, collateral)` | ✅ **(added)** | Returns max_loan/max_leverage, caps, `failed_constraints[]`, and up to 12 slider stops — same in-node math |
+| `leverage-close-preview` | `get_leverage_close_preview(position_id)` | ✅ **(added)** | cancel_value, pool_obligation, bettor_receives, closeable |
+| `leverage-convert-preview` | `get_leverage_convert_preview(position_id)` | ✅ **(added)** | cancel_value, current_profit, conversion_fee, total_user_payment (gap F closed) |
+
+## B.9 Node-side gaps & action items (prioritized)
+
+These are the concrete things **missing on our side** relative to the prototype workflow, plus the
+decision each needs. "Client-only" means the thin client can proceed without node changes.
+
+| # | Gap | Status | Impact / Recommendation |
+|---|---|---|---|
+| **F** | Leverage preview/close/convert quote API | ✅ **DONE** | Added `get_leverage_quote` / `get_leverage_close_preview` / `get_leverage_convert_preview` to the `prediction_market_api` plugin, calling the same frozen `pm::leverage::*` math the evaluators use |
+| **A** | Category taxonomy / counts / hot-tags API | ✅ **DONE** | Added `get_market_categories` (per-category/subcategory counts + top-20 hot tags) |
+| **B** | Sort + subcategory/tag filter in list API | ✅ **DONE** | Extended `list_markets_by_category` with `subcategory`, `tag`, `sort` (newest/oldest/volume/expiration) |
+| **I** | Commit-reveal preimage not byte-pinned in spec | ✅ **DONE** | Spec §3.6.1 now documents the exact binary preimage (little-endian ints, 32-byte zero-padded account, raw salt — **not** a colon string) with a golden SHA-256 test vector, from `verify_commit` in `pm_evaluator.cpp` |
+| **D** | No oracle dispute-rebuttal op | ✅ **DONE** | Added `pm_dispute_oracle_respond` (op-id 98); response stored on the dispute object (`oracle_response` / `oracle_response_time`), read via `get_dispute`. Open-dispute + within `oracle_response_deadline`; re-post overwrites |
+| **E** | No standalone unban op | ✅ **DONE** | Added `pm_unban` (op-id 99). Bans now record `banned_by` (the account-mode resolver); only that resolver may lift, else expiry at `banned_until` |
+| **K** | No on-chain creator role | 🔵 by design | Any account may create a market; anti-spam = per-market fee (B.11). Keep creator gating client-side |
+| **J** | `pm_resolve_market` has no free-text justification | ✅ **DONE** | Added `decision_reason` to `pm_resolve_market` (≤1024 chars). Both `decision_url` and `decision_reason` are now **stored on `pm_market_object`** (like an oracle's rules_url) and readable via `get_market`; `pm_no_contest.reason` also lands in `decision_reason` |
+| **G** | No enriched single-call market view | ✅ **DONE** | Added `get_market_full(market_id, [account])` — market + outcomes + weight sums + oracle + meta + the account's per-market bets/leverage/LP in one call |
+| **H** | No API for lazy allocations / oracle penalty stamps | ✅ **DONE** | Added `get_lazy_allocations(from,limit)` + `get_market_lazy_allocation(market_id)`. Oracle penalty stamps already ship on `pm_oracle_object` via `get_oracle` |
+| **C** | Risk thresholds hardcoded (2.5×), not governance | ✅ **DONE** | Promoted to governance: `pm_listing_min_coverage_percent` (250) drives `below_risk_floor`; `pm_betting_min_coverage_percent` (150) published for client risk-confirm |
+
+> **Every lettered gap (A–K) is now closed.** F/A/B/G/H were non-consensus plugin reads; C promoted
+> the risk floor to governance; I pinned the commit preimage; D/E/J were added as consensus operations
+> (pre-hardfork design window). Additionally, ban expiry now emits the `pm_ban_expired` virtual op
+> (per-block cron), so ban lifts are observable both ways (manual `pm_unban` op + automatic vop).
+
+**Unit/semantic reconciliations (client adapts, no node change):**
+- Fees: prototype **‰ (per-mille)** ↔ node **bp**. Convert (×10).
+- `dispute_grace_hours` (prototype hours) ↔ `pm_dispute_grace_sec` (node seconds).
+- Oracle `reliability_score`: node **0–10000 bp** ↔ prototype **0–100**.
+- `trust_score` / `risk_score` / `derived{}` rates: compute client-side from `pm_oracle_object`
+ counters + market coverage.
+- Committee semantics: prototype single-arbitrator ↔ node account-mode resolver **or**
+ committee-mode stake-weighted DAO vote.
+- `leverage-preview` collateral ×1000 double-scaling quirk (prototype bug) — do **not** replicate;
+ send `asset`/`share_type` per the spec once.
+
+## B.10 Coverage summary
+
+| Bucket | Count | Endpoints |
+|---|---|---|
+| ✅ direct op/method | 31 | betting, cancel, transfer, liquidity (all), lazy deposit/withdraw/emergency/**allocations**, leverage open/close/convert/info + **preview ×3**, create (uni), resolve (uni, +justification), no-contest, accept/reject, insurance, oracle register, list-oracles, positions, **categories/sort**, **enriched market**, **oracle-rebuttal**, **unban** |
+| ⚠ partial (client adapts) | 10 | oracle-profile (derived rates), create-market metadata mapping, dispute-create (proposed_outcome), resolve-dispute (2 modes), pending queue, history/payouts, leverage-pool-state, market-payouts |
+| 🔵 client-side / generic VIZ | 7 | session/auth, preferences, withdraw-viz, deposit, committees list, register-creator |
+| ❌ open | 0 | — all lettered gaps closed |
+
+The thin client reaches **full feature parity** for the core lifecycle (browse → bet → create →
+resolve → dispute → LP → lazy pool → leverage). Every gap A–K is closed; ban lifts also emit the
+`pm_ban_expired` virtual op on automatic expiry. Remaining ⚠ rows are shape/semantic adaptations
+the client handles locally, not missing node capability.
+
+## B.11 Market-creation fee flow (answer: where the fee goes)
+
+Any account may `pm_create_market` — there is no on-chain "creator" registration. The economics,
+read straight from `pm_create_market_evaluator` / `pm_oracle_accept_market_evaluator`:
+
+1. **At creation**, the creator pays two separate amounts up front:
+ - `pm_market_creation_fee` (default **5.000 VIZ**) — deducted immediately and added to
+ `committee_fund` (the DAO fund). **Non-refundable anti-spam fee.**
+ - `liquidity` (the seed, ≥ `pm_min_liquidity`) — moved into the market as the creator's first LP
+ position.
+2. **The market starts pending** (`status = 0`) waiting for the named oracle, unless it is a
+ self-oracle or matches the oracle's auto-accept policy (then it activates immediately). A pending
+ market carries an `accept_deadline` = `created_time + pm_oracle_accept_window_sec` (default 1h).
+3. **If the oracle rejects** (`pm_oracle_accept_market` with `accept=false`): the creator's
+ **liquidity seed is refunded in full** (`return_liquidity`) and the market is marked deleted
+ (`status = -1`). The **`pm_market_creation_fee` is NOT refunded** — it already went to the DAO
+ fund at creation and stays there.
+4. **If the oracle does nothing before `accept_deadline`**: the per-block cron voids the market the
+ same way — seed refunded in full, market `status = -1`, creation fee kept — and emits the
+ `pm_market_expired` virtual op (op-id 101: `oracle`, `creator`, `market_id`, `refunded_liquidity`).
+ A reject emits no vop, so distinguish the two by watching for `pm_market_expired` vs. a bare
+ `status = -1`.
+
+So: *"any user can try to create a market by paying a fee and requesting an oracle; if the oracle
+declines, the seed liquidity comes back but the creation fee is forfeited to the DAO fund."* The
+`pm_oracle_registration_fee` behaves the same way (→ `committee_fund`, non-refundable). Thin-client
+UX should make the non-refundable fee explicit before the user picks an oracle, and ideally steer
+them to oracles with an `auto_accept` policy (or a self-oracle) to avoid a wasted fee.
+
+---
+
+# Part C — Thin-client-only UX (not in the node, but required)
+
+These have **no chain operation and no API method** — they are pure client responsibilities the
+prototype's server used to cover implicitly. A VIZ thin client must implement them itself; list
+them as first-class UI/onboarding work, not afterthoughts.
+
+| Concern | What the client must do | Persist where | Notes |
+|---|---|---|---|
+| **Key management / entry** | Import or generate the account's VIZ keys (active for spend ops, regular for `pm_dispute_vote`); sign every op locally. Never send keys anywhere | Encrypted local store (never plaintext, never to a server) | Replaces the prototype's server session entirely. Offer key import (WIF), optional password-encrypted keystore, and a clear "log out = wipe keys" |
+| **Node endpoint selection** | Let the user choose / edit the JSON-RPC **HTTPS node address** (and a fallback list); validate reachability via a cheap read (e.g. `get_pm_chain_properties`) | Local settings | Default to a known public node but always editable; show which node is active. All reads/broadcasts go here |
+| **Language / locale** | UI language switch (en/ru/…); localize market titles/labels from each market's `metadata` JSON | Local settings (localStorage) | No on-chain language pref — `update-user-preferences` disappears. Localization of market content is a client concern (metadata may carry `title_en`/`title_ru`, etc.) |
+| **Jurisdiction selection** | User picks their country (ISO code); use it to hide markets whose `pm_market_meta_object.banned_jurisdictions` includes it, or pass to `list_markets_by_category(jurisdiction=…)`; show the "restricted here" interstitial on a banned market | Local settings | No on-chain per-user jurisdiction. Purely a client filter + warning |
+| **Risk acknowledgment (betting)** | When a market is under-insured (below the coverage floor, or hidden unless "show risky"), require an explicit confirm before betting | Ephemeral (per action) | No on-chain `risk_confirm`; the node's only risk gate is hiding under-insured markets from the default listing. Betting-time confirmation is pure UX |
+| **Legal / risk disclaimer & agreement** | On first run (and on version change) show a **terms/disclaimer** the user must accept: *software used at own risk; user is solely responsible for complying with the laws of their own jurisdiction; user assumes all financial and legal risk; no warranty; the client is non-custodial and cannot recover lost keys or funds* | Local flag (accepted version + timestamp) | Not in the node. Gate app usage on acceptance; re-prompt when the disclaimer text version changes. Pair with the jurisdiction selection above |
+| **Non-refundable fee disclosure** | Before create-market / oracle-register, state that the fee is non-refundable and where it goes (DAO fund), and warn that an oracle rejection refunds only the seed liquidity (see B.11) | Ephemeral (per action) | Prevents surprise loss of the creation fee |
+| **Commit-reveal persistence** | Persist pending reveals `{commit_id, side/outcome, amount, min_tokens, salt, reveal_deadline}` and retry `pm_reveal_bet` on load | Local store | Consensus-critical salt must survive reloads or the escrow is forfeited (see gap I) |
+
+> These items are the onboarding/compliance surface of a non-custodial client. Treat the **legal
+> disclaimer + jurisdiction acceptance** and **secure key handling** as launch blockers — everything
+> else in the plan assumes an authenticated, consenting user pointed at a chosen node.
diff --git a/.qoder/plans/pm-plan/README.md b/.qoder/plans/pm-plan/README.md
new file mode 100644
index 0000000000..df6f62aeb8
--- /dev/null
+++ b/.qoder/plans/pm-plan/README.md
@@ -0,0 +1,104 @@
+# Onix Prediction-Market — VIZ DLT Implementation Bundle
+
+Self-contained handoff package for the **C++ VIZ DLT plugin** that adds prediction markets (binary CPMM + multi-outcome LMSR) to the VIZ chain. Everything you need to start building consensus code is in this directory; nothing references the parent forecaster repo.
+
+---
+
+## Read order
+
+1. **[viz-dlt-prediction-market-protocol-spec.md](viz-dlt-prediction-market-protocol-spec.md)** — the protocol spec.
+ - §1 ChainBase objects (`pm_oracle`, `pm_market`, `pm_outcome`, `pm_bet`, `pm_liquidity`, `pm_commit`, `pm_dispute`, `pm_dispute_vote`, lazy pool)
+ - §2 Object type registry additions
+ - §3 Regular operations (op IDs 64..)
+ - §4 Virtual operations
+ - §5 Chain properties added to the median-vote set
+ - §6 Settlement (binary + multi parimutuel)
+ - §7 Open issues — **read carefully**, several still need a decision before code can land:
+ - §7.2 (LMSR float determinism) — **RESOLVED** by `lmsr-fixed-point-spec.md`
+ - §7.1, 7.3, 7.4, 7.5, 7.6, 7.7, 7.10, 7.11, 7.12 — still open, see "Status of open items" below.
+
+2. **[lmsr-fixed-point-spec.md](lmsr-fixed-point-spec.md)** — frozen Q96 fixed-point LMSR algorithm (`exp_q`, `ln_q`, `lse_q`, `lmsr_*`). §6 has the C++ implementation guide (use `boost::multiprecision::int256_t`, no `-ffast-math`, frozen iteration counts). The C++ port MUST reproduce every value in `reference/lmsr_fixed_vectors.json` with strict equality.
+
+3. **[parimutuel-settlement.md](parimutuel-settlement.md)** — unified parimutuel settlement applied to both binary CPMM (weights from reserves) and multi LMSR (weights = bought tokens). One `settle()` math, two market types.
+
+4. **[batch-commit-reveal.md](batch-commit-reveal.md)** — Phase-2 anti-MEV path (`pm_commit_object`, batch settlement). Not required for binary-only Phase 1.
+
+5. **[lazy-pool-properties.md](lazy-pool-properties.md)** — Phase-2 lazy liquidity pool: passive LPs auto-allocate to high-volume markets.
+
+6. **[chain-properties-governance.md](chain-properties-governance.md)** — full list of ~25 PM chain properties added to the validator median set (caps, floors, fees, periods).
+
+7. **[security-threat-model.md](security-threat-model.md)** — attacker scenarios, oracle abuse, dispute griefing, MEV, etc. Use it to derive negative-path tests.
+
+---
+
+## Reference implementations (in `reference/`)
+
+The PHP and JS implementations are **normative**: the C++ port must produce bit-identical outputs.
+
+| File | Role |
+|---|---|
+| `lmsr_fixed.php` | PHP+GMP reference — source of truth for the test vectors |
+| `market_math.js` | JavaScript BigInt port — independent re-implementation that confirms PHP determinism |
+| `lmsr_fixed_vectors.json` | Frozen test vectors (Q96 primitives + public LMSR API). **C++ must pass every one with strict equality.** |
+| `lmsr_fixed_vectors_verify.php` | Asserts `lmsr_fixed.php` matches the JSON. `php reference/lmsr_fixed_vectors_verify.php` |
+| `lmsr_fixed_vectors_verify.js` | Same for JS. `node reference/lmsr_fixed_vectors_verify.js` |
+| `lmsr_vectors_generate.php` | Regenerates the JSON. Re-run only when the spec changes (bump `version`). |
+| `lmsr_fixed_smoke.php` | 24 invariants (`exp(LN2) = 2·ONE` etc.) — handy sanity gate. |
+
+### Quick verify
+
+```bash
+# PHP (needs gmp extension)
+php reference/lmsr_fixed_vectors_verify.php
+# expected: PHP verifier: 77 / 77 passed
+
+# Node
+node reference/lmsr_fixed_vectors_verify.js
+# expected: JS verifier: 74 / 74 passed
+```
+
+### Acceptance criterion for the C++ port
+
+```cpp
+// Pseudocode for the C++ unit test:
+auto V = parse_json("reference/lmsr_fixed_vectors.json");
+for (auto& t : V["lmsr_cost"]) REQUIRE(lmsr_cost(t.q, t.b) == t.expected);
+for (auto& t : V["lmsr_prices"]) REQUIRE(lmsr_prices(t.q, t.b) == t.expected);
+// ...same for buy_cost, sell_return, tokens_for_amount, b_from_liquidity,
+// exp_q, ln_q, mul_q, div_q.
+```
+**No tolerance.** Any deviation = fork risk.
+
+---
+
+## Status of open items in §7 (what still needs a product decision)
+
+| § | Topic | Required decision |
+|---|---|---|
+| 7.1 | Chain-property surface | Pick (a) median-vote all / (b) median-vote core + hardfork rest / (c) `pm_params_object` |
+| 7.3 | Batch / commit-reveal ordering | Define exact in-block op ordering and per-block caps |
+| 7.4 | Committee dispute voting | Quorum policy; vote-storage cap; integer tally formula; commit-reveal for votes? |
+| 7.5 | Locked-funds custody | Pick (a) explicit fields on objects (recommended) / (b) escrow subsystem |
+| 7.6 | Time-driven processing | Per-block cap N + fairness rule |
+| 7.7 | Bandwidth cost class | Reuse generic class, or add `pm_operations_cost_*` |
+| 7.10 | Dust routing | Where remainders go (committee fund? next round? oracle?) |
+| 7.11 | LMSR sell churn | Anti-churn argument or per-block sell cap |
+| 7.12 | Byte-length caps | Freeze `MAX_PM_*` table — **consensus-mechanical, not anti-spam** |
+
+§§7.2, 7.8, 7.9 are either resolved or client-side / standard hardfork hygiene.
+
+---
+
+## Phasing (recap of §8 of the protocol spec)
+
+- **Phase 1 (binary-only, integer-safe):** all objects, oracle lifecycle, create/accept, instant bet, liquidity, resolve, both dispute modes (oracle + committee), auto-payout, missed-resolution penalty. **No LMSR**, **no batch/commit-reveal**. Ships a usable consensus market with no §7.2-class risk.
+- **Phase 2:** wire in the deterministic LMSR (per `lmsr-fixed-point-spec.md`) → multi markets; add batch + commit-reveal (`batch-commit-reveal.md`); lazy pool (`lazy-pool-properties.md`).
+- **Phase 3:** shared/category liquidity, automated data oracles, advanced governance of PM params.
+
+Recommendation: build Phase 1 to mainnet first, freeze, then add Phase 2 behind a separate hardfork.
+
+---
+
+## Off-chain reference (informational)
+
+A working off-chain prototype (PHP backend + Telegram-WebApp frontend + the same `lmsr_fixed.php` and `market_math.js`) exists at the parent `forecaster/` repo. It is **not** required to read it, but it can serve as a behavioural oracle: any edge case you're unsure about, run the same scenario through the prototype and compare.
diff --git a/.qoder/plans/pm-plan/batch-commit-reveal.md b/.qoder/plans/pm-plan/batch-commit-reveal.md
new file mode 100644
index 0000000000..5702dfd484
--- /dev/null
+++ b/.qoder/plans/pm-plan/batch-commit-reveal.md
@@ -0,0 +1,338 @@
+# Plan: Opt-in Batch & Commit-Reveal Betting (Front-Running Protection)
+
+> Implementation plan to extend the Onix Protocol with **optional** anti-front-running execution, while keeping the current **instant per-bet** execution as the default. To be folded into [onix-protocol-specification.md](onix-protocol-specification.md).
+>
+> 🇷🇺 Russian: [plan_batch_commit_reveal_betting-ru.md](plan_batch_commit_reveal_betting-ru.md). Background: [.qoder/docs/theory_concepts/FORECASTER-FIT.md](../.qoder/docs/theory_concepts/FORECASTER-FIT.md) → "Mitigations for the reflexivity family".
+
+## 1. Goal
+
+The current CPMM is **path-dependent**: a bet's tokens depend on reserves at execution time, so a public mempool tx can be sandwiched/front-run. We add **two opt-in execution modes** that the bettor chooses *per bet* (checkboxes in UI), without changing the default flow or the LP guarantee:
+
+- **Instant (mode 0, default)** — unchanged. Immediate per-bet execution against the live `a·b=k` curve. Best UX, no protection.
+- **Batch (mode 1)** — bet is deferred to the next epoch boundary and settled together with all other batch bets at a **single uniform price**. Solves intra-batch ordering/front-running.
+- **Commit-Reveal (mode 2)** — bet is **hidden** (hash commit) then revealed; revealed bets settle in the batch. Strongest protection: a front-runner can't even see direction/size. If the commit is **not revealed**, a penalty is taken and **added to the winners' pool** at resolution.
+
+> // NOTE: mode 2 is mode 1 + confidentiality. Both share the same batch-settlement engine.
+
+## 2. UI
+
+At bet time, two checkboxes (default OFF → instant):
+
+- ☐ **Protect from front-running (batch)** → `mode = 1`
+- ☐ **Hide my bet until reveal (commit-reveal)** → `mode = 2` (implies batch). Surfaces the salt/escrow flow.
+
+> // NOTE: For high-`endogeneity_tier` markets (tier 3, political/social) the client MAY default commit-reveal ON. Protocol stays neutral.
+
+## 3. New / changed parameters
+
+### Global (governance, `settings` table)
+
+| Key | Default | Unit | Description |
+|-----|---------|------|-------------|
+| `batch_epoch_blocks` | 20 | blocks | Epoch length (~60s at 3s blocks). Batch settlement runs at each boundary. |
+| `reveal_window_blocks` | 200 | blocks | Grace margin to reveal after commit (~10 min). NOT a deliberation window — reveal is normally auto-immediate; this is liveness slack for a dropped tx / offline client. Generous on purpose (see §5.3 NOTE). |
+| `commit_no_reveal_penalty_permille` | 200 | ‰ | No-reveal fee (**20%**) on escrow → **winners' pool**. Delegate-voted; the value is **carried in `pm_commit_bet` and consensus-checked** (see §5.2). |
+| `min_batch_bet` | 1,000 | mVIZ | Minimum amount for batch/commit-reveal (anti-dust spam in queues). |
+| `commit_reveal_enabled` | 1 | 0/1 | Global kill-switch for hidden mode. |
+| `batch_price_band_permille` | 20 | ‰ | **Binary only**: max deviation of a batch fill's live execution price from the epoch-open snapshot price; beyond it the fill auto-refunds (anti-manipulation). See §6.1. |
+
+### Per-market (creator-set, oracle-confirmed)
+
+| Param | Type | Description |
+|-------|------|-------------|
+| `allow_batch` | 0/1 | Enable batch & commit-reveal for this market (default 1). Set 0 for instant-only markets. |
+| `allow_instant_bet` | 0/1 | Enable instant `mode=0` for this market (default 1). Set 0 to force every order through batch / commit-reveal (anti-MEV). At creation we require `allow_instant_bet OR allow_batch == 1`, otherwise the market is unbettable. Multi-outcome (LMSR) markets force this back to 1 server-side because batch settlement is binary-only today. |
+| `endogeneity_tier` | 1/2/3 | Reflexive-risk tag (1 econ-data, 2 sports/scheduled, 3 political/social). Display + client policy. |
+
+## 4. Epochs & lifecycle
+
+```
+Epoch N (batch_epoch_blocks long)
+ ├─ INSTANT bets: execute live against current reserves (unchanged)
+ ├─ BATCH bets: queued with epoch=N
+ ├─ COMMIT bets: escrow locked, commitment stored, reveal_deadline = now + reveal_window_blocks
+ └─ REVEAL ops: attach revealed bet to the batch of the FIRST epoch boundary at/after the reveal
+ (clients SHOULD reveal a few blocks BEFORE that boundary — see §5.3 / §11.1)
+Epoch boundary (block % batch_epoch_blocks == 0)
+ └─ VIRTUAL pm_batch_settle(market, N): settle all queued bets for the market in one curve update
+reveal_deadline reached without reveal
+ └─ VIRTUAL pm_commit_forfeit(commit_id): penalty → market.forfeit_pool, refund the rest
+```
+
+> // NOTE: batch settlement runs **per market that has a non-empty queue**, not globally — keeps virtual-op cost bounded.
+
+## 5. Operations
+
+### 5.1 `pm_place_bet` — add `mode` field
+
+```
+pm_place_bet { market_id, side, amount, min_tokens, mode }
+ mode = 0 (INSTANT): current behavior, unchanged.
+ Reject if market.allow_instant_bet == 0 (creator opted out of instant settlement).
+ mode = 1 (BATCH): require market.allow_batch == 1, amount >= min_batch_bet.
+ Lock `amount`, enqueue { bet, side, amount, min_tokens, submit_time, epoch }.
+ Bet status = 5 (queued). No reserve change yet.
+```
+
+### 5.2 `pm_commit_bet` — commit-reveal phase 1
+
+```
+pm_commit_bet { market_id, commitment, escrow_amount, no_reveal_fee_permille }
+ require commit_reveal_enabled == 1 && market.allow_batch == 1
+ require escrow_amount >= min_batch_bet
+ // CONSENSUS CHECK (agreement with the chain): the user must declare the exact penalty rate
+ // currently mandated by delegates. Reject the tx if it disagrees.
+ require no_reveal_fee_permille == settings.commit_no_reveal_penalty_permille
+ commitment = H(market_id || account || side || amount || min_tokens || salt) // binds everything
+ Lock escrow_amount.
+ Store { commit_id, market, account, commitment, escrow_amount, commit_time,
+ no_reveal_fee_permille, // RATE LOCKED at commit time
+ reveal_deadline = now + reveal_window_blocks, status = COMMITTED }
+```
+
+> // NOTE: `commitment` binds account + market so a commit can't be replayed on another market/user.
+> // `escrow_amount` must be ≥ the eventual `amount`; surplus is refunded at reveal.
+> // WHY carry the fee in the tx: it makes the penalty an explicit *agreement with consensus* — the
+> user signs the rate they accept, the node validates it equals the live delegate-voted value, and the
+> rate is then **snapshotted on the commitment** so a later governance change can't alter it retroactively.
+
+### 5.3 `pm_reveal_bet` — commit-reveal phase 2
+
+```
+pm_reveal_bet { commit_id, side, amount, salt, min_tokens }
+ require status == COMMITTED && now <= reveal_deadline
+ require H(market_id || account || side || amount || min_tokens || salt) == commitment // verify
+ require amount <= escrow_amount
+ refund (escrow_amount - amount) to account
+ enqueue { bet, side, amount, min_tokens, submit_time = commit_time, epoch = FIRST boundary at/after reveal }
+ status = REVEALED
+```
+
+> // INVARIANT: a mismatched reveal (wrong hash) is rejected; the commit then forfeits at deadline (§5.5).
+> // NOTE (reveal timing — matters for front-run resistance, see §11.1): the public reveal→settlement gap is
+> the ONLY residual front-run window, so the client SHOULD reveal a few blocks BEFORE the next boundary to
+> keep that gap ≈ 1–2 blocks (NOT immediately after commit — that maximises the exposed window).
+> // `reveal_window_blocks` (200) is a liveness FALLBACK only: if the client was offline and missed its target
+> boundary it can still reveal later (settling at a subsequent boundary, accepting more exposure) instead of
+> forfeiting. The **20% no-reveal fee** (not the window length) is what deters "commit-then-decide" optionality,
+> and `min_tokens` (baked into the hash) auto-refunds the bet if price drifted past the floor during the gap.
+
+### 5.4 `pm_batch_settle` — VIRTUAL, at epoch boundary
+
+See §6 for the math. Deterministic, generated at block time, validated by every node.
+
+### 5.5 `pm_commit_forfeit` — VIRTUAL, at reveal_deadline if unrevealed
+
+```
+// use the rate LOCKED on the commitment, not the current chain value
+penalty = floor(escrow_amount * commitment.no_reveal_fee_permille / 1000)
+market.forfeit_pool += penalty // boosts the winners' pool at resolution (per design)
+refund (escrow_amount - penalty) to account
+status = FORFEITED
+```
+
+## 6. Batch settlement math (uniform price, LP-safe)
+
+**Requirements:** (a) one uniform price per side within a batch (no ordering edge), (b) order-independent / deterministic, (c) the LP invariant `reserve_a + reserve_b ≥ L` MUST still hold.
+
+**Method — aggregate each side into ONE CPMM op, in a fixed canonical order.** Because each step is a standard CPMM transition, the AM-GM proof (spec §5) and `k`-preservation are **inherited for free**.
+
+```
+// pm_batch_settle(market, epoch)
+(Ra, Rb, k) = market.reserves // AFTER any instant bets in this epoch
+queued = bets where market_id == market && epoch == epoch && status in {5 queued, REVEALED}
+A_in = Σ amount for queued on side A
+B_in = Σ amount for queued on side B
+
+// Canonical order → deterministic & order-independent. Process larger aggregate first; tie → A.
+order = (A_in >= B_in) ? [A, B] : [B, A]
+
+for side in order where side_total > 0:
+ if side == A:
+ new_Rb = Rb + A_in; new_Ra = floor(k / new_Rb); T = Ra - new_Ra
+ else:
+ new_Ra = Ra + B_in; new_Rb = floor(k / new_Ra); T = Rb - new_Rb
+ (Ra, Rb) = (new_Ra, new_Rb) // k unchanged: betting, not a liquidity event
+ uniform_price = side_total / T // identical for every bettor on this side
+
+ // Slippage pre-pass (single recompute): drop bettors whose share < min_tokens, refund them,
+ // then recompute T/uniform_price once over the survivors.
+ // TODO: confirm single-pass is sufficient; alternative = iterate to fixpoint (costlier).
+ for bettor i on side:
+ tokens_i = floor(T * amount_i / side_total)
+ if tokens_i < bettor_i.min_tokens: reject & refund (bet not placed)
+ else: record bet { weight = tokens_i, time_penalty by submit_time } // §8
+ dust = T - Σ tokens_i → DAO fund // consistent with spec §7 rounding
+
+market.reserves = (Ra, Rb)
+// INVARIANT (assert): Ra + Rb >= L — holds by inheritance from standard CPMM steps
+```
+
+> // NOTE (fairness): the side processed second is priced after the first side moved the curve.
+> Within each side everyone is uniform, and **no external actor can insert between individual bets** →
+> front-running is eliminated. Cross-side price impact within a batch is accepted as a minor tradeoff.
+> // OPTIONAL (advanced, later): internally MATCH opposing flow at the clearing price and push only the
+> net residual through the curve, for tighter pricing. Leave as future work — must re-prove the invariant.
+
+**Onix Multi (LMSR):** identical idea — aggregate per-outcome `Δ` and settle via one combined `C(q+Δ)−C(q)` cost step per epoch, uniform price per outcome. `b` and the parimutuel settlement are untouched.
+
+### 6.1 Reconciling instant and batch flow (epoch-open snapshot)
+
+> **✅ [Unified Parimutuel Settlement](plan_unified_parimutuel_binary.md) is now implemented** (binary settles parimutuel in code), so this section simplifies: payout is capped at `losers_pool` regardless of weights for **both** types → **both use snapshot pricing** for full manipulation immunity, and `min_tokens` gates slippage. The binary-specific "live curve + price band" carve-out (and `batch_price_band_permille`) below is **no longer needed** — kept only as historical context for the pre-unification design.
+
+To stop instant bets from front-running the batch, the batch is priced against a **snapshot of the curve taken at the epoch's open** (= the previous epoch's closing reserves), frozen *before* any of this epoch's instant flow — so no intra-epoch instant bet can move the batch's clearing price. This is the user's intuition: **the batch fires on the previous epoch's parameters.**
+
+```
+boundary (E-1 → E): snapshot S_E = (A0,B0) / q0 // FROZEN — batch of E prices here
+during E: instant bets run live on C_live (k preserved); batch bets queue
+boundary (E → E+1): pm_batch_settle prices the queue at S_E, reconciles into C_live, opens E+1
+```
+
+Merging the two flows (instant on `C_live`, batch on `S_E`) into one E+1 curve **without breaking the LP invariant** is done differently per market type:
+
+**Onix Multi — snapshot pricing is fully safe.** Parimutuel solvency is *independent of token counts*:
+`disbursed = winning_bets + (losing_bets − fees) + subsidy = all_VIZ_held − fees ≤ held`, for ANY token totals. Snapshot mispricing only shifts the *ratio* that splits `winners_pool`; it never threatens solvency or the (unconditionally returned) LP subsidy. So:
+- price each batch outcome's `Δ` at snapshot `q0`, mint tokens, collect the real VIZ into the pool;
+- open E+1 with `q = q_live_after_instant + batch_Δ`.
+→ Full intra-epoch manipulation immunity, zero invariant risk.
+
+**Onix Binary — `weight` is a claim funded by losers, so settle on the live curve (do NOT force snapshot tokens).** There is no "token emission": `weight` is the bettor's claim — their own principal plus a profit (`weight − amount`) **paid by the losers**. `k` is constant under betting (spec §5; it only changes on liquidity add/withdraw) and maker liquidity `L` keeps `k>0`, so bets just slide the reserves along the curve — there is no "k→0". The real point: the snapshot (pre-move) price is **better than the live price**, so handing it to a batch bettor creates a **larger winner claim than curve-pricing would** — and on-curve pricing is exactly what guarantees the spec's surplus condition `loser_bets − fees ≥ winner_weights − winner_amounts` (betting-rules §Resolution). Off-curve claims can exceed what losers funded, forcing either pro-rating winners (no longer "payout = weight" — breaks the binary model) or tapping LP principal (breaks the guarantee). (Harmless for Multi, where `weight` only sets the split ratio of the losers' pool.) So for binary:
+- settle the batch as a **standard aggregated CPMM op on the live curve** (§6) → reserves stay valid, `k` unchanged, AM-GM invariant inherited automatically;
+- filter each order by its committed **`min_tokens`**: orders whose floor isn't met at the live settlement price drop out and refund; survivors are aggregated and applied (`reserve_b += Σamount`, `reserve_a −= Σtokens`).
+- This gives manipulation **resistance**, not immunity: an attacker who moved the price before the boundary either (a) only degrades the fill within the victim's own slippage tolerance, or (b) gets the victim's bet refunded and is left holding a pumped position with no victim flow to exit against (unprofitable). Optional extra: a **snapshot price band** `batch_price_band_permille` to refund fills that deviate too far from the epoch-open price (largely redundant with `min_tokens`).
+
+> // RESULT: Multi = full snapshot immunity. Binary = invariant-safety + band-bounded manipulation
+> (+ the ~1-block reveal window of §11.1 + min_tokens). Full binary immunity needs mode 3 (encrypted bids).
+> // TODO (binary): formal invariant proof IF we ever want a pure-snapshot binary variant (full immunity w/o crypto).
+
+**Liquidity ops:** `k`/`b`-changing ops apply to `C_live` and are picked up by the NEXT epoch's snapshot; they do not retroactively alter a frozen `S_E` (an in-flight batch prices at pre-add reserves — acceptable, minor). // see §8 block-ordering TODO
+
+## 7. Non-reveal → winners' pool (resolution change)
+
+Add `forfeit_pool` to the market; merge it into the winners' pool at resolution:
+
+```
+winners_pool = losers_sum − oracle_fee − creator_fee − liq_fee + market.forfeit_pool
+// distributed pro-rata by winning tokens, exactly like losers_sum
+```
+
+> // NOTE: no profitable attack — a forfeiter pays the penalty and receives **zero tokens**, so they can't
+> recapture it; the penalty only helps the (other) winners. This also deters the "commit optionality,
+> reveal only the favorable side" game.
+> // EDGE: if there are no winning tokens (edge cases in spec §6), `forfeit_pool` follows the same path as
+> undistributed `winners_pool` (LP bonus / DAO). Align with the existing edge-case table.
+
+## 8. Interaction with existing mechanics
+
+| Mechanic | Behavior |
+|----------|----------|
+| **Time penalty (spec §8)** | Computed from **submit/commit time**, NOT settlement time → users aren't penalized for the batch/reveal delay. // important |
+| **Slippage** | `min_tokens` enforced at settlement; failure → refund (bet not placed). For mode 2 it's inside the commitment hash → un-changeable. |
+| **Cancellation (spec §11)** | A queued batch bet (status 5) MAY be cancelled before settlement. A COMMITTED bet CANNOT be free-cancelled before `reveal_deadline` (else commit-reveal = free optionality); the only exits are reveal+settle or forfeit. // TODO confirm policy |
+| **Liquidity add/withdraw** | `k`-changing events still only on liquidity ops; must be sequenced **before** `pm_batch_settle` within a block to keep `k` consistent for the batch. // TODO define block-level op ordering |
+| **Dispute / resolution** | Unaffected, except `winners_pool` now includes `forfeit_pool`. |
+| **Self-oracle / lazy pool** | Unaffected. |
+
+## 9. Database schema changes
+
+```sql
+-- bets: execution mode + queueing
+ALTER TABLE `bets`
+ ADD COLUMN `mode` tinyint NOT NULL DEFAULT 0, -- 0 instant, 1 batch, 2 commit-reveal
+ ADD COLUMN `epoch` int NOT NULL DEFAULT 0, -- settlement epoch
+ ADD COLUMN `submit_time` int NOT NULL DEFAULT 0; -- for time-penalty basis
+ -- status extended: 5 = queued, 6 = revealed-pending (existing: 0 active,1 cancelled,...)
+
+-- commitments (commit-reveal phase 1)
+CREATE TABLE `bet_commitments` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `market` bigint NOT NULL,
+ `account` bigint NOT NULL,
+ `commitment` char(64) NOT NULL, -- hex hash
+ `escrow_amount` bigint NOT NULL,
+ `no_reveal_fee_permille` smallint NOT NULL, -- rate snapshotted at commit (consensus-checked in tx)
+ `commit_time` int NOT NULL,
+ `reveal_deadline` int NOT NULL,
+ `status` tinyint NOT NULL DEFAULT 0, -- 0 committed, 1 revealed, 2 forfeited
+ `revealed_bet_id` bigint DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `market_status` (`market`,`status`),
+ KEY `reveal_deadline` (`reveal_deadline`)
+);
+
+-- markets: forfeit accumulator + opt-in flags + tier
+ALTER TABLE `markets`
+ ADD COLUMN `forfeit_pool` bigint NOT NULL DEFAULT 0,
+ ADD COLUMN `allow_batch` tinyint NOT NULL DEFAULT 1,
+ ADD COLUMN `allow_instant_bet` tinyint NOT NULL DEFAULT 1,
+ ADD COLUMN `endogeneity_tier` tinyint NOT NULL DEFAULT 2,
+ ADD COLUMN `current_epoch` int NOT NULL DEFAULT 0;
+```
+
+## 10. Audit trail
+
+`market_log` gains actions: `commit`, `reveal`, `batch_settle` (with `A_in`, `B_in`, before/after reserves, dust), `commit_forfeit` (penalty, refund). Keeps the full before/after-reserves discipline of spec §3.
+
+## 11. Attack & corner cases
+
+| Case | Handling |
+|------|----------|
+| Commit on both sides, reveal only favorable | Reveals don't apply until epoch close (nothing to react to); non-reveal penalty makes optionality costly. |
+| Spam empty commits | `min_batch_bet` escrow + non-reveal penalty. |
+| Settle an empty queue | No-op; no virtual op emitted. |
+| Front-runner uses instant bets to move curve before a batch settles | The residual vector — see §11.1 for the full threat model and the layered defenses. |
+| Reveal after deadline | Rejected; commit forfeits. |
+| min_tokens fails for some batch bettors | Single recompute pre-pass over survivors (§6). |
+
+### 11.1 What commit-reveal actually protects (threat model)
+
+The hash hides a bet **only during commit → reveal**. After reveal, side+amount are public and the bet **does** enter the batch. So the protection is layered, not "the batch hides it":
+
+1. **No mempool sandwich of your specific order** — while it is a hash, no one can read it or execute immediately ahead of it. (Primary protection.)
+2. **Uniform-price batch** — no intra-batch ordering edge; a front-runner can't get a strictly better price than you inside the same batch (§6).
+3. **Batch-composition uncertainty** — other hidden commits (possibly on the opposite side) may be in the same batch, so an instant front-run is a *bet under uncertainty*, not a sure sandwich.
+
+**Residual vector (real):** between reveal and settlement the bet is public, and an **instant** bet executes against the live curve immediately → it can move the price the batch settles at. A profitable sandwich requires ALL of: (a) a long reveal→settlement gap, (b) `allow_cancellation=1` (attacker can exit), (c) a loose victim `min_tokens`.
+
+**Defenses, by increasing strength:**
+
+- **Shrink the gap.** Settle at the first boundary at/after reveal; clients reveal a few blocks before the boundary (§5.3) → gap ≈ 1–2 blocks. `reveal_window_blocks` is only a liveness fallback.
+- **`min_tokens` (in the hash).** A pump pushes the victim's fill below the floor → the bet is auto-rejected/refunded, so the attacker's pump backfires (left long with no victim flow).
+- **Non-cancellable markets.** `allow_cancellation=0` → attacker cannot exit → front-run becomes pure directional risk, not a riskless sandwich.
+- **STRUCTURAL — epoch-open snapshot (specified in §6.1):** price the batch against the reserves frozen at the epoch's open, so same-epoch instant bets cannot move its clearing price. Fully manipulation-immune & solvency-safe for **Multi**; for **Binary** use live-curve settlement + a snapshot price band (token=VIZ claim makes pure-snapshot pricing unsafe). Removes the instant-front-run vector without cryptography.
+- **FULL elimination (mode 3, roadmap):** **threshold/timelock-encrypted sealed bids** (Shutter/Penumbra-style) — the preimage is decrypted only at the boundary, so there is no public actionable window at all.
+
+> // BOTTOM LINE: commit-reveal+batch turns front-running from a *trivial mempool sandwich* into "guess the
+> hidden batch, race a ~1-block window, bear directional risk, and risk the victim's min_tokens canceling
+> the bet." A true zero requires an encrypted mempool (mode 3).
+
+## 12. Open questions (// TODO)
+
+- [ ] Block-level ordering of `liquidity_*`, instant `pm_place_bet`, and `pm_batch_settle` within one block.
+- [ ] Slippage rejection: single pre-pass vs. iterate-to-fixpoint.
+- [ ] Cancellation policy for COMMITTED (not yet revealed) bets.
+- [ ] Optional cross-side internal matching at clearing price (advanced pricing) + invariant re-proof.
+- [x] Epoch-open snapshot reconciliation — specified in §6.1 (Multi: snapshot pricing; Binary: live + price band). Remaining: formal invariant proof for a pure-snapshot *binary* variant, only if full crypto-free binary immunity is wanted.
+- [ ] Mode 3: threshold/timelock-encrypted sealed bids — whether/when to add (fully closes the residual).
+- [ ] Whether `endogeneity_tier` is purely advisory or can *force* commit-reveal at protocol level.
+
+## 13. Phased rollout
+
+1. **Phase 1 — Batch (mode 1).** Epochs + `pm_batch_settle` + uniform-price aggregation. Biggest front-running win, no crypto.
+2. **Phase 2 — Commit-Reveal (mode 2).** `pm_commit_bet`/`pm_reveal_bet` + `pm_commit_forfeit` + `forfeit_pool`. Adds confidentiality (endogeneity mitigation).
+3. **Phase 3 — Tiering & client policy.** `endogeneity_tier` defaults, optional advanced cross-side matching.
+
+## 14. Sections to add/modify in `onix-protocol-specification.md`
+
+| Spec section | Change |
+|--------------|--------|
+| §3 System Parameters | Add the 5 global + 2 per-market params (this doc §3). |
+| §4 Market State Machine | Add bet statuses 5 (queued) / 6 (revealed-pending); add `pm_batch_settle` & `pm_commit_forfeit` virtual ops to transitions. |
+| §5 Onix Binary | Add §5.x "Batch settlement" with the uniform-price aggregation math (§6). |
+| §6 Onix Multi | Add the LMSR batch analogue. |
+| §7 Fee Structure | Add `forfeit_pool` to the `winners_pool` formula. |
+| §8 Time Penalty | Clarify basis = submit/commit time for deferred modes. |
+| §10 Resolution & Payout | Include `forfeit_pool` in winners distribution + edge cases. |
+| §11 Bet Cancellation | Add cancellation rules for queued/committed bets. |
+| New §18 "Execution Modes" | Full description of instant/batch/commit-reveal + ops §5. |
+| §17 Database Schema | Add the tables/columns from this doc §9. |
diff --git a/.qoder/plans/pm-plan/chain-properties-governance.md b/.qoder/plans/pm-plan/chain-properties-governance.md
new file mode 100644
index 0000000000..85586400fd
--- /dev/null
+++ b/.qoder/plans/pm-plan/chain-properties-governance.md
@@ -0,0 +1,274 @@
+# Chain Properties: How Validators Govern Network Parameters
+
+## Overview
+
+In VIZ, there is no central authority that sets network fees, block sizes, inflation rates, or other critical parameters. Instead, **every active validator publishes their preferred values**, and the blockchain automatically calculates the **median** — the middle value that represents the consensus of all elected validators.
+
+Since validators are elected by stake-weighted voting from all SHARES holders, chain properties are ultimately governed by the community: users vote for validators whose parameter choices align with their vision of the public good.
+
+---
+
+## How It Works
+
+### Step 1: Validators Publish Their Preferences
+
+Each validator publishes their preferred chain properties using the `versioned_chain_properties_update_operation`:
+
+```json
+["versioned_chain_properties_update", {
+ "owner": "validator1",
+ "props": [3, {
+ "account_creation_fee": "1.000 VIZ",
+ "maximum_block_size": 131072,
+ "create_account_delegation_ratio": 10,
+ "create_account_delegation_time": 2592000,
+ "min_delegation": "1.000 VIZ",
+ "min_curation_percent": 0,
+ "max_curation_percent": 10000,
+ "bandwidth_reserve_percent": 1000,
+ "bandwidth_reserve_below": "500.000000 SHARES",
+ "flag_energy_additional_cost": 0,
+ "vote_accounting_min_rshares": 5000000,
+ "committee_request_approve_min_percent": 1000,
+ "inflation_validator_percent": 2000,
+ "inflation_ratio_committee_vs_reward_fund": 5000,
+ "inflation_recalc_period": 806400,
+ "data_operations_cost_additional_bandwidth": 0,
+ "validator_miss_penalty_percent": 100,
+ "validator_miss_penalty_duration": 86400,
+ "create_invite_min_balance": "10.000 VIZ",
+ "committee_create_request_fee": "100.000 VIZ",
+ "create_paid_subscription_fee": "100.000 VIZ",
+ "account_on_sale_fee": "10.000 VIZ",
+ "subaccount_on_sale_fee": "100.000 VIZ",
+ "validator_declaration_fee": "10.000 VIZ",
+ "withdraw_intervals": 28
+ }]
+}
+```
+
+The `[3, {...}]` format indicates the version — `3` means `chain_properties_hf9`. Older versions (`0` = init, `1` = hf4, `2` = hf6) are accepted for backward compatibility. **Newer versions exist:** `4` = `chain_properties_hf13` and `5` = `chain_properties_pm` (HF14 Prediction Markets — adds the PM consensus params, see [the dedicated section below](#prediction-market-parameters-hf14)). A validator must publish the highest version whose hardfork is active.
+
+### Step 2: Median Calculation
+
+Every time the validator schedule is updated, the blockchain runs `update_median_validator_props()`. For **each property independently**:
+
+1. Collect the property value from every active validator
+2. Sort the values
+3. Pick the **median** (the middle value)
+
+```
+Example: 5 validators set account_creation_fee to:
+ 0.5 VIZ, 1.0 VIZ, 1.0 VIZ, 2.0 VIZ, 5.0 VIZ
+ ↑
+ median = 1.0 VIZ
+```
+
+The algorithm uses `std::nth_element` with position `active.size() / 2`, which selects the value at the middle index after partial sorting.
+
+**Why median?** The median is resistant to extremes. A single validator cannot push a parameter to an absurdly high or low value — they can only shift the median by one position. To change a parameter significantly, a **majority of active validators** must agree.
+
+### Step 3: Application
+
+The calculated `median_props` is stored in the `validator_schedule_object` and used across the entire blockchain to enforce rules.
+
+---
+
+## All Governable Properties
+
+### Account & Delegation Rules
+
+| Property | Type | Default | What It Controls |
+|---|---|---|---|
+| `account_creation_fee` | asset (VIZ) | 1.000 VIZ | Minimum fee to create a new account |
+| `create_account_delegation_ratio` | uint32 | 10 | Multiplier: delegation = ratio × fee |
+| `create_account_delegation_time` | uint32 (sec) | 30 days | How long creation delegation is locked |
+| `min_delegation` | asset (VIZ) | 1.000 VIZ | Minimum amount for any delegation |
+
+**How it's used**: When someone creates a new account, they must pay at least `account_creation_fee` and provide delegation of at least `ratio × fee` in SHARES equivalent. The delegation is locked for `create_account_delegation_time`. This prevents cheap mass account creation (Sybil attacks) while keeping the network accessible.
+
+### Block Size & Bandwidth
+
+| Property | Type | Default | What It Controls |
+|---|---|---|---|
+| `maximum_block_size` | uint32 (bytes) | 131072 | Maximum block size — controls network throughput |
+| `bandwidth_reserve_percent` | int16 (bp) | 1000 (10%) | Extra bandwidth for small accounts |
+| `bandwidth_reserve_below` | asset (SHARES) | 500.000000 | Threshold for bandwidth reserve |
+| `data_operations_cost_additional_bandwidth` | uint32 (%) | 0 | Extra bandwidth cost for data-heavy operations |
+
+**How it's used**: Transaction bandwidth is allocated proportionally to SHARES. Accounts below `bandwidth_reserve_below` get an additional `bandwidth_reserve_percent` reserve so they can still transact. `maximum_block_size` directly controls how many transactions the network can process per block.
+
+### Inflation & Economics
+
+| Property | Type | Default | What It Controls |
+|---|---|---|---|
+| `inflation_validator_percent` | int16 (bp) | 2000 (20%) | Validator share of block inflation |
+| `inflation_ratio_committee_vs_reward_fund` | int16 (bp) | 5000 (50%) | How remaining inflation is split between committee fund and reward fund |
+| `inflation_recalc_period` | uint32 (blocks) | 806400 (28 days) | How often inflation parameters are recalculated |
+
+**How it's used**: Each block creates new tokens (inflation). First, `inflation_validator_percent` goes to the block-producing validator. The remainder is split: `inflation_ratio_committee_vs_reward_fund` percent goes to the committee DAO fund, the rest to the reward fund (used for awards). Validators directly control how the economy works.
+
+### Reward System
+
+| Property | Type | Default | What It Controls |
+|---|---|---|---|
+| `min_curation_percent` | int16 (bp) | 500 (5%) | Minimum curation reward share |
+| `max_curation_percent` | int16 (bp) | 500 (5%) | Maximum curation reward share |
+| `vote_accounting_min_rshares` | uint32 | 5000000 | Minimum rshares for an award to have effect |
+| `flag_energy_additional_cost` | int16 (bp) | 0 | Extra energy cost for downvoting |
+
+**How it's used**: When content receives awards, curation rewards are bounded by `[min_curation_percent, max_curation_percent]`. Awards with fewer than `vote_accounting_min_rshares` rshares produce zero reward (dust filter). `flag_energy_additional_cost` can make downvotes more expensive than upvotes.
+
+### Validator Accountability
+
+| Property | Type | Default | What It Controls |
+|---|---|---|---|
+| `validator_miss_penalty_percent` | int16 (bp) | 100 (1%) | Vote reduction for missing a block |
+| `validator_miss_penalty_duration` | uint32 (sec) | 86400 (1 day) | How long the penalty lasts |
+
+**How it's used**: When a validator misses their scheduled block, their effective votes are reduced by `validator_miss_penalty_percent` for `validator_miss_penalty_duration` seconds. This is self-governing accountability: validators vote on how harshly missed blocks are punished.
+
+### Fee Structure
+
+| Property | Type | Default | What It Controls |
+|---|---|---|---|
+| `committee_create_request_fee` | asset (VIZ) | 100.000 VIZ | Fee to create a DAO proposal |
+| `create_paid_subscription_fee` | asset (VIZ) | 100.000 VIZ | Fee to create a paid subscription |
+| `account_on_sale_fee` | asset (VIZ) | 10.000 VIZ | Fee to list an account for sale |
+| `subaccount_on_sale_fee` | asset (VIZ) | 100.000 VIZ | Fee to list subaccounts for sale |
+| `validator_declaration_fee` | asset (VIZ) | 10.000 VIZ | One-time fee for new validator registration |
+| `create_invite_min_balance` | asset (VIZ) | 10.000 VIZ | Minimum balance to create an invite |
+
+**How it's used**: All fees go to the **committee fund** (DAO treasury). Validators control how expensive various network operations are. Higher fees discourage spam; lower fees improve accessibility. The community decides the balance through validator elections.
+
+### Vesting Withdrawal
+
+| Property | Type | Default | What It Controls |
+|---|---|---|---|
+| `withdraw_intervals` | uint16 | 28 | Number of daily installments for unstaking |
+
+**How it's used**: When a user unstakes SHARES, the withdrawal happens over `withdraw_intervals` days (one installment per day). Validators can make unstaking faster or slower, affecting how liquid the network's governance token is.
+
+### Prediction Market Parameters (HF14)
+
+`chain_properties_pm` (variant `5`) inherits everything above and appends the Onix Prediction Market consensus params. They are median-voted exactly like every other property — there is **no separate PM transaction**. Categories (defaults in parentheses):
+
+> **All PM percentages use the project-wide bp scale: 10000 = 100.00%** (hundredths of a percent), like `min_curation_percent` and the other `*_percent` properties. There is no permille (‰) anywhere in PM.
+
+- **Oracle / market economics:** `pm_oracle_registration_fee` (10 VIZ), `pm_min_oracle_insurance` (5000 VIZ bond), `pm_market_creation_fee` (5 VIZ), `pm_min_liquidity` (100 VIZ), `pm_max_outcomes` (10), `pm_max_market_duration` (1 year), **`pm_max_oracle_fee_percent` (500 = 5%)**, `pm_default_time_penalty_percent` (50), `pm_max_time_penalty` (1e6). *(There is no aggregate fee cap — see below.)*
+- **Disputes:** `pm_dispute_fee` (1000 VIZ), `pm_dispute_grace_sec` (12 h), `pm_oracle_dispute_response_sec` (12 h), `pm_dispute_auto_close_sec` (14 d), `pm_dispute_vote_period_sec` (3 d), `pm_dispute_approve_min_percent` (1000 bp), `pm_oracle_penalty_percent` (500 bp), `pm_no_contest_penalty_percent` (5000 = 50% of the dispute fee), `pm_dispute_reward_multiplier` (30000 = 3× — a bp **multiplier** where 10000 = 1×, floored at 1× so a vindicated disputer recovers its fee, capped at 100×).
+- **Batch / commit-reveal:** `pm_batch_epoch_blocks` (20), `pm_reveal_window_blocks` (200), `pm_commit_no_reveal_penalty_percent` (2000 = 20%), `pm_min_batch_bet` (1 VIZ), `pm_commit_reveal_enabled` (true).
+- **Cron budget:** `pm_processing_cap_per_block` (200) — bound on deterministic per-block PM work.
+- **Lazy pool:** `pm_lazy_pool_enabled` (true), `pm_lazy_alloc_percent` (2000 bp), `pm_lazy_max_total_alloc_percent` (7000 bp), `pm_lazy_lock_sec` (7 d), `pm_lazy_recall_step_percent` (1000 bp), `pm_lazy_emergency_penalty_percent` (5000 = 50%).
+- **Leverage** (kill-switch **off** by default): `pm_leverage_enabled` (false), `pm_leverage_fund_percent` (10), `pm_leverage_max_per_position_bp` (20), `pm_leverage_pool_profit_percent` (10), `pm_leverage_safety_margin_percent` (1), `pm_leverage_max_slippage_percent` (10), `pm_leverage_min_market_liquidity` (5000 VIZ), `pm_leverage_max_position_ratio_percent` (5), `pm_leverage_expiration_buffer_sec` (1 d), `pm_leverage_m_factor_percent` (50), `pm_conversion_profit_cost_percent` (50).
+
+#### Market fees: who sets them, and the single governed cap
+
+A market charges up to **three** resolution fees (bp, 10000 = 100%). At settlement they are deducted from the **losers' pool**, and the remainder is paid to winners:
+
+| Fee on the market | Set by | Goes to | Cap |
+|---|---|---|---|
+| `oracle_fee_percent` | the **oracle** (quoted at accept) | the oracle | ≤ `pm_max_oracle_fee_percent` (500 = 5%) **and** ≤ the creator's offered ceiling |
+| `creator_fee_percent` | the creator (at create) | the creator | — (self-limiting) |
+| `liquidity_fee_percent` | the creator (at create) | the LPs (time-weighted) | — (self-limiting) |
+
+**Only one governed fee cap:** `pm_max_oracle_fee_percent`. The oracle is the neutral third party, so its fee is bounded. The creator's own `creator_fee`/`liquidity_fee` have **no governance cap** — a market that takes too much just becomes unattractive and loses bettors (market forces). The old aggregate `pm_max_total_fee` was **removed** as a redundant knob.
+
+The only hard limit on the other two is **solvency**: `oracle + creator + liquidity ≤ 100%` (10000 bp), checked statically in `validate()` so the winners' pool can never go negative.
+
+#### How the fee terms are fixed (offer → quote → freeze)
+
+The market maker and the oracle negotiate on-chain, and the agreed terms are **frozen into the market object**, so a later median shift can never change a live market's economics:
+
+1. **Create** — the creator publishes an **offer ceiling**: `oracle_fee_percent` + `oracle_fixed_fee` are the *most* it will pay the oracle, alongside its own `creator_fee_percent`/`liquidity_fee_percent`.
+2. **Accept** — the external oracle **quotes its actual terms** on `pm_oracle_accept_market` (≤ the offer, and `oracle_fee_percent ≤ pm_max_oracle_fee_percent`). The quote is the oracle's price list / reputation, not a bribe. It is frozen onto the market, the status flips to active, and a **`pm_market_accepted` virtual op** is emitted so history-parsing scripts see the launch + terms. A **self-oracle** market freezes its own terms and emits the same vop automatically at creation.
+3. **Resolve** — settlement reads only the **frozen** market fields; it never consults the live median. So the median cap matters only at the moment the oracle commits (register / accept), exactly when consent is given.
+
+> **Note on validation layers.** `validate()` is a static check (no chain state): it bounds each fee to ≤ 10000 bp and the solvency sum to ≤ 10000. The governed `pm_max_oracle_fee_percent` cap is enforced in the **evaluator** (which can read the median) at register/accept — not in `validate()`. So size the oracle fee against the median cap, not just the static 100% ceiling.
+
+---
+
+## The Governance Loop: Users → Validators → Parameters
+
+### Users Shape the Network Through Validator Selection
+
+Users cannot directly set chain properties. Instead, they **vote for validators** whose published properties match their preferences. This creates a representative governance system:
+
+```
+Users (SHARES holders)
+ │
+ ├── Vote for validators who want LOW fees
+ │ → More validators with low fee props get elected
+ │ → Median fees decrease
+ │
+ ├── Vote for validators who want HIGH inflation to reward fund
+ │ → More reward-focused validators get elected
+ │ → Inflation shifts toward reward fund
+ │
+ └── Vote for validators who want STRICT miss penalties
+ → More accountability-focused validators get elected
+ → Miss penalties increase
+```
+
+### Why This Is a Public Good Mechanism
+
+Traditional blockchains set parameters through hard-coded values or foundation decisions. VIZ makes **every parameter a public good decision**:
+
+1. **Transparency**: every validator's preferred properties are on-chain and publicly visible
+2. **Accountability**: if a validator sets harmful parameters, users can unvote them
+3. **Gradual change**: the median shifts slowly — no single validator can cause sudden parameter swings
+4. **No single point of failure**: even if some validators are compromised, the median protects the network
+5. **Aligned incentives**: validators earn block rewards, so they're incentivized to keep the network healthy
+
+### Example: How a Fee Change Happens
+
+Suppose the community wants to lower the `committee_create_request_fee` from 100 VIZ to 50 VIZ:
+
+1. Users discuss in community channels that the fee is too high
+2. Some validators update their properties: `committee_create_request_fee: "50.000 VIZ"`
+3. Users shift votes to validators who support lower fees
+4. As more low-fee validators enter the active set, the **median shifts down**
+5. Once more than half of active validators publish 50 VIZ or less, the median becomes 50 VIZ
+6. The new fee takes effect automatically — no hardfork, no governance proposal, no vote counting
+
+### Comparing Governance Models
+
+| Approach | VIZ Median Properties | Token Voting (e.g., Snapshot) | Foundation Governance |
+|---|---|---|---|
+| **Who decides** | Elected validators (indirectly: all SHARES holders) | Token holders directly | Core team / foundation |
+| **Resistance to extremes** | Strong (median) | Weak (whale dominance) | N/A (centralized) |
+| **Speed of change** | Gradual (median shifts slowly) | Fast (single vote) | Fast or slow (depends on team) |
+| **Parameter granularity** | Every parameter independently | Usually binary proposals | Any |
+| **Sybil resistance** | Built-in (stake-weighted Fair-DPOS) | Depends on implementation | N/A |
+| **Transparency** | Full (all validator props on-chain) | Partial (off-chain voting) | Low |
+
+---
+
+## Versioning and Hardfork Compatibility
+
+Properties were introduced in stages:
+
+| Version | Hardfork | Properties Added |
+|---|---|---|
+| `chain_properties_init` | Genesis | account_creation_fee, maximum_block_size, delegation params, curation, bandwidth, flag cost, vote min rshares, committee threshold |
+| `chain_properties_hf4` | HF4 | inflation_validator_percent, inflation_ratio_committee_vs_reward_fund, inflation_recalc_period |
+| `chain_properties_hf6` | HF6 | data_operations_cost_additional_bandwidth, validator_miss_penalty_percent, validator_miss_penalty_duration |
+| `chain_properties_hf9` | HF9 | create_invite_min_balance, committee_create_request_fee, create_paid_subscription_fee, account_on_sale_fee, subaccount_on_sale_fee, validator_declaration_fee, withdraw_intervals |
+| `chain_properties_hf13` | HF13 | (validator/consensus tuning fields inherited by PM) |
+| `chain_properties_pm` | HF14 | **~40 Prediction Market params** (oracle/market economics, disputes, batch/commit-reveal, cron budget, lazy pool, leverage) — see [Prediction Market Parameters](#prediction-market-parameters-hf14) |
+
+Validators publish properties using `versioned_chain_properties` — a variant that accepts any version. The evaluator validates the version against the current hardfork (you can't publish HF9 properties before HF9 activates). Properties from older versions use default values for newer fields.
+
+---
+
+## Summary
+
+Chain properties governance in VIZ is a **continuous, median-based, representative system** where:
+
+- **Validators** are the direct governors who publish their preferred parameters
+- **Users** are the ultimate governors who elect validators based on their published properties
+- **The median** ensures no single actor can impose extreme values
+- **Every parameter** — from fees to inflation to bandwidth — is a public good decision made collectively
+- **Changes happen organically**: as community preferences shift, validator elections shift, and the median follows
+
+This creates a self-regulating network where the "rules of the game" are constantly optimized by the people who have the most at stake.
diff --git a/.qoder/plans/pm-plan/lazy-pool-properties.md b/.qoder/plans/pm-plan/lazy-pool-properties.md
new file mode 100644
index 0000000000..83235be0a5
--- /dev/null
+++ b/.qoder/plans/pm-plan/lazy-pool-properties.md
@@ -0,0 +1,549 @@
+# Lazy Pool: Properties, Accounting & State Transitions
+
+## 1. Overview
+
+The Lazy Liquidity Pool is a **singleton** smart-contract-like entity (single DB row, `id=1`) that pools VIZ from multiple depositors and automatically deploys it as:
+
+1. **LP liquidity** — provides market-making capital to active prediction markets
+2. **Leverage loans** — loans VIZ to bettors opening boosted (leveraged) positions
+
+All monetary values use **milli-VIZ precision** (1 VIZ = 1000 internal units, `PRECISION = 1000`).
+
+The pool uses **Lazy Accounting** (MasterChef/Compound pattern): a single global accumulator (`reward_per_share`) enables O(1) profit distribution regardless of participant count. When profit arrives, **only** `reward_per_share` is updated — no per-user writes. Users read their rewards via `shares × (rps − snapshot) / 10^9`.
+
+---
+
+## 2. Pool Properties
+
+### 2.1 Stored Properties (Database Columns)
+
+| Column | Type | Precision | Description |
+|--------|------|-----------|-------------|
+| `total_shares` | bigint | milli-VIZ | Total share tokens outstanding. 1:1 on first deposit; subsequent deposits: `new_shares = amount × total_shares / free_balance` |
+| `free_balance` | bigint | milli-VIZ | VIZ not currently deployed — available for new allocations, leverage loans, and withdrawals. This is the **working capital** of the pool |
+| `allocated_balance` | bigint | milli-VIZ | VIZ currently deployed as LP liquidity on active markets. Returned to `free_balance` on market resolution or recall |
+| `earned_balance` | bigint | milli-VIZ | Cumulative total VIZ earned by the pool from all sources (LP profits, leverage profits, penalties, fees). **Monotonically non-decreasing** — only grows, never shrinks |
+| `leverage_fund_used` | bigint | milli-VIZ | Total active leverage loans outstanding. A sub-allocation **cap** on `free_balance`, not a separate pool |
+| `reward_per_share` | bigint | 10^9 | Accumulated reward per share (LAZY_POOL_PRECISION). **Monotonically non-decreasing** — only grows. The Lazy Accounting accumulator |
+
+### 2.2 Computed Properties (Derived at Query Time)
+
+| Property | Formula | Description |
+|----------|---------|-------------|
+| `total_value` | `free_balance + allocated_balance` | Total VIZ controlled by the pool |
+| `invested_liquidity` | `allocated_balance` | VIZ deployed as LP on active markets |
+| `invested_leverage` | `leverage_fund_used` | VIZ deployed as leverage loans |
+| `free_amount` | `free_balance − leverage_fund_used` | Truly free VIZ — not on markets, not loaned for leverage. Available for new allocations and leverage loans |
+| `leverage_fund_total` | `free_balance × F% / 100` | Cap on how much of `free_balance` may be used for leverage loans |
+| `leverage_fund_available` | `leverage_fund_total − leverage_fund_used` | Remaining leverage loan capacity |
+| `per_share_value` | `free_balance / total_shares` | Current VIZ value of one share (withdrawal exchange rate) |
+| `pool_profit_ratio` | `earned_balance / total_value` | Lifetime pool profitability (monitoring metric) |
+
+### 2.3 Example State
+
+```
+lazy_pool:
+ free_balance = 10,000,000 mVIZ (10,000 VIZ)
+ allocated_balance = 7,000,000 mVIZ ( 7,000 VIZ)
+ earned_balance = 1,500,000 mVIZ ( 1,500 VIZ) ← cumulative earnings
+ leverage_fund_used = 100,000 mVIZ ( 100 VIZ) ← active leverage loans
+ total_shares = 8,000,000 ( 8,000 shares)
+ reward_per_share = 187,500,000 (high-precision accumulator)
+
+Computed:
+ total_value = 10,000,000 + 7,000,000 = 17,000,000 mVIZ (17,000 VIZ)
+ invested_liquidity = 7,000,000 mVIZ (7,000 VIZ on active markets)
+ invested_leverage = 100,000 mVIZ ( 100 VIZ in leverage loans)
+ free_amount = 10,000,000 − 100,000 = 9,900,000 mVIZ (9,900 VIZ)
+ per_share_value = 10,000,000 / 8,000,000 = 1.25 VIZ/share
+
+Leverage fund (F% = 10%):
+ leverage_fund_total = 10,000,000 × 10% = 1,000,000 mVIZ (1,000 VIZ)
+ leverage_fund_available = 1,000,000 − 100,000 = 900,000 mVIZ (900 VIZ)
+```
+
+---
+
+## 3. Invariants
+
+These must hold after every state transition:
+
+```
+1. free_balance + allocated_balance = total_value (conservation of value)
+2. free_balance ≥ leverage_fund_used (loans cannot exceed free capital)
+3. earned_balance is monotonically non-decreasing (cumulative earnings only grow)
+4. reward_per_share is monotonically non-decreasing (accumulated rewards only grow)
+5. total_shares > 0 iff free_balance > 0 (no shares without capital)
+```
+
+---
+
+## 4. User-Level Properties
+
+Each user has the following lazy pool fields on the `users` table:
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `lazy_pool_shares` | bigint | User's share tokens (sum across all deposits) |
+| `lazy_pool_balance` | bigint | User's principal VIZ deposited (not including earnings) |
+| `lazy_pool_reward_snapshot` | bigint | Snapshot of `pool.reward_per_share` at last user action |
+| `lazy_pool_pending_rewards` | bigint | Settled but unclaimed rewards (accumulated at deposit/withdraw) |
+
+**Computed per-user:**
+
+```
+live_unsettled_reward = lazy_pool_shares × (pool.reward_per_share − lazy_pool_reward_snapshot) / 10^9
+total_user_reward = lazy_pool_pending_rewards + live_unsettled_reward
+user_share_value = lazy_pool_shares × pool.free_balance / pool.total_shares
+user_total_value = user_share_value + total_user_reward
+user_earned = pool.earned_balance × (lazy_pool_shares / pool.total_shares)
+user_principal = user_total_value − user_earned
+```
+
+---
+
+## 5. Share Calculation
+
+### 5.1 Deposit — New Shares
+
+```
+First depositor: new_shares = amount (1:1 ratio)
+Subsequent: new_shares = amount × total_shares / free_balance
+```
+
+The exchange rate (`free_balance / total_shares`) increases as the pool earns profit, so later depositors get fewer shares per VIZ — this is how early LPs capture returns.
+
+### 5.2 Withdrawal — Share Value
+
+```
+share_value = shares_to_burn × free_balance / total_shares
+```
+
+Since `free_balance` includes accumulated profit (via `reward_per_share` distributions are separate, but `free_balance` grows when profit arrives), the per-share value drifts upward over time.
+
+---
+
+## 6. State Transitions
+
+All state changes are **atomic** (single SQL transaction). Each transition is shown with its effect on every pool column.
+
+### Transition 1: User Deposit
+
+```
+User deposits amount A into the pool.
+
+BEFORE: settle user rewards (pending += shares × (rps − snapshot) / 10^9; snapshot = rps)
+
+lazy_pool:
+ free_balance += A
+ total_shares += new_shares (A × total_shares / free_balance before deposit)
+ allocated_balance — no change
+ earned_balance — no change
+ leverage_fund_used — no change
+ reward_per_share — no change
+
+Fund flow: user.balance → lazy_pool.free_balance
+```
+
+### Transition 2: Auto-Allocation to Market
+
+```
+Pool allocates alloc_amount to a new market (when oracle accepts).
+
+lazy_pool:
+ free_balance −= alloc_amount
+ allocated_balance += alloc_amount
+ total_shares — no change
+ earned_balance — no change
+ leverage_fund_used — no change
+ reward_per_share — no change
+
+Fund flow: free_balance → market CPMM reserves (as LP position, user=0)
+Effect: invested_liquidity increases. total_value unchanged.
+```
+
+### Transition 3: Market Resolves — Principal Return (No Profit)
+
+```
+Market resolves, pool LP position returns exactly the allocation amount.
+
+lazy_pool:
+ free_balance += alloc_amount
+ allocated_balance −= alloc_amount
+ total_shares — no change
+ earned_balance — no change
+ leverage_fund_used — no change
+ reward_per_share — no change
+
+Fund flow: market CPMM reserves → lazy_pool.free_balance
+Effect: invested_liquidity decreases. total_value unchanged.
+```
+
+### Transition 4: Market Resolves — With Profit
+
+```
+Market resolves, pool earns profit on LP position.
+ lp_return = amount returned from market (principal + profit)
+ lp_profit = lp_return − alloc_amount
+
+lazy_pool:
+ free_balance += lp_return (principal + profit both return to free)
+ allocated_balance −= alloc_amount (no longer allocated)
+ earned_balance += lp_profit (track cumulative earnings)
+ reward_per_share += lp_profit × 10^9 / total_shares (distribute to LP investors)
+ total_shares — no change
+ leverage_fund_used — no change
+
+Fund flow: market CPMM reserves → lazy_pool.free_balance
+ profit portion also recorded in earned_balance + distributed via reward_per_share
+Effect: principal: invested_liquidity → free
+ profit: adds to free, earned, and reward_per_share
+```
+
+### Transition 5: Graduated Recall (Idle Market)
+
+```
+Cron recalls recall_amount from an idle market's allocation.
+
+lazy_pool:
+ free_balance += recall_amount
+ allocated_balance −= recall_amount
+ total_shares — no change
+ earned_balance — no change
+ leverage_fund_used — no change
+ reward_per_share — no change
+
+Fund flow: market CPMM reserves → lazy_pool.free_balance
+Effect: Partial invested_liquidity → free. total_value unchanged.
+```
+
+### Transition 6: Leverage Position Opened
+
+```
+Bettor opens a boosted position with loan L from the pool.
+ C = bettor's collateral, L = pool's loan
+
+lazy_pool:
+ free_balance −= (C + L) (collateral C + loan L placed into AMM)
+ leverage_fund_used += L (track active loans — only L, not C)
+ total_shares — no change
+ earned_balance — no change
+ allocated_balance — no change
+ reward_per_share — no change
+
+Fund flow: lazy_pool.free_balance → market CPMM reserves
+Note: Only L counts toward leverage_fund_used (not C, the bettor's own collateral).
+ free_balance decreases by full (C+L), but leverage_fund_used only increases by L.
+ The bettor's collateral C reduces free_amount but is not a leverage obligation.
+```
+
+### Transition 7: Leverage Position Closed (Liquidation / Voluntary Close / Force-Close)
+
+```
+Pool recovers loan + profit from a closed leverage position.
+ pool_received = loan_return + pool_profit
+ pool_profit = pool_received − L
+
+lazy_pool:
+ free_balance += pool_received (loan + profit return to free)
+ leverage_fund_used −= L (free up loan capacity)
+ earned_balance += pool_profit (track cumulative earnings)
+ reward_per_share += pool_profit × 10^9 / total_shares (distribute to LP investors)
+ total_shares — no change
+ allocated_balance — no change
+
+Fund flow: market CPMM reserves → lazy_pool.free_balance
+Effect: invested_leverage decreases (leverage_fund_used −= L)
+ profit adds to free, earned, and reward_per_share
+ leverage_fund_used decreases, freeing capacity for new loans
+```
+
+### Transition 7a: Leverage Position Liquidated After Cancel-Bet (Bad Debt)
+
+When a cancel-bet on the same outcome as a leveraged position pushes it below the liquidation threshold, the cancel-bet executes first (transaction initiator gets expected price), then the position is liquidated at post-cancel-bet reserves. The pool may absorb a shortfall.
+
+**Cascade behavior:** Liquidating one position changes AMM reserves (tokens returned, VIZ removed), which can push other same-side positions below their threshold. The system uses a recursive cascade loop (`leverage_cascade_liquidate`) that re-evaluates all remaining positions after each liquidation until no more positions qualify. This prevents hidden bad debt from positions that become underwater due to the reserve changes caused by earlier liquidations in the same cascade. See [Liquidation Cascade](../.qoder/docs/leverage-risk-off-strategy.md#liquidation-cascade-long-squeeze) for full analysis.
+
+```
+ cancel_value < liquidation_threshold (position pushed below by cancel-bet)
+ pool_received = cancel_value (entire amount to pool)
+ shortfall = liquidation_threshold − cancel_value (bad debt)
+
+lazy_pool:
+ free_balance += cancel_value (remaining value returns to free)
+ free_balance −= shortfall (bad debt absorbed by pool)
+ leverage_fund_used −= L (free up loan capacity)
+ earned_balance — no change (no earnings on a loss)
+ reward_per_share — no change (no profit to distribute)
+ total_shares — no change
+ allocated_balance — no change
+
+Effect: The pool absorbs the shortfall from free_balance. This is a bounded, rare event:
+ - Bounded by SL% (cancel-bet's price impact cap)
+ - Rare: only when position near threshold + cancel-bet on same outcome is the trigger
+ - Fairness: the cancel-bettor (transaction initiator) is not penalized
+ See Leverage Risk-Off Strategy, Section 5 (Case B) for full specification.
+```
+
+### Transition 8: Leverage Position Resolved (Loss — Outcome Risk)
+
+```
+Bettor's outcome loses at resolution. Pool loses the loan L.
+
+lazy_pool:
+ leverage_fund_used −= L (loan gone, free up capacity)
+ free_balance — no change (loan was already in AMM, now lost)
+ earned_balance — no change (no earnings from this position)
+ total_shares — no change
+ allocated_balance — no change
+ reward_per_share — no change
+
+Effect: invested_leverage decreases. The loss is absorbed by the pool's total_value
+ (which implicitly decreased when the bet was placed into the AMM and the
+ outcome lost). free_balance doesn't change because the loan was already
+ removed from free_balance at opening — it was in the AMM reserves and is now
+ distributed to winning bettors at resolution.
+```
+
+### Transition 8a: Leverage Position Resolved (Win — Outcome Reward)
+
+Bettor's outcome wins at resolution. Pool receives loan + profit.
+
+**Note:** When multiple leveraged positions are force-closed together (e.g., Cron Job 11 or Case A opposing bet), the system uses a recursive cascade loop. Each force-close changes AMM reserves, potentially affecting subsequent positions. See Transition 7a cascade behavior.
+
+```
+pool_obligation = L × (1 + R%/100) (= liquidation_threshold)
+payout = redeem(tokens, outcome=winning)
+pool_received = min(payout, pool_obligation) = pool_obligation (payout ≥ threshold)
+bettor_received = payout − pool_obligation
+pool_profit = pool_received − L = L × R%/100
+
+lazy_pool:
+ free_balance += pool_received (loan + profit return to free)
+ leverage_fund_used −= L (free up loan capacity)
+ earned_balance += pool_profit (track cumulative earnings)
+ reward_per_share += pool_profit × 10^9 / total_shares (distribute to LP investors)
+ total_shares — no change
+ allocated_balance — no change
+
+Fund flow: market AMM → lazy_pool.free_balance (via resolution payout)
+Effect: invested_leverage decreases. Pool earns L × R% profit.
+ Bettor receives (payout − pool_obligation) from resolution.
+```
+
+### Transition 9: User Withdrawal (Planned)
+
+```
+User withdraws shares from the pool.
+ share_value = shares_to_burn × free_balance / total_shares
+ reward_portion = pending_rewards × withdraw_percent / 100
+ total_payout = share_value + reward_portion
+
+BEFORE: settle user rewards (pending += shares × (rps − snapshot) / 10^9; snapshot = rps)
+
+lazy_pool:
+ free_balance −= total_payout (share value + rewards paid out)
+ total_shares −= shares_to_burn (shares burned)
+ allocated_balance — no change
+ earned_balance — no change (cumulative earnings never decrease)
+ leverage_fund_used — no change
+ reward_per_share — no change
+
+Fund flow: lazy_pool.free_balance → user.balance
+Effect: User receives proportional share of free_balance + accumulated rewards.
+```
+
+### Transition 10: Convert Leveraged Position to Normal Bet
+
+A bettor converts their boosted position into a normal (non-leveraged) bet, paying the pool from their balance. This allows holding the position to resolution and capturing the full outcome payout.
+
+```
+Bettor converts leveraged position to normal bet.
+ pool_obligation = L × (1 + R%/100) (= liquidation_threshold)
+ current_profit = cancel_value − pool_obligation (bettor's unrealized gain)
+ conversion_fee = current_profit × conversion_profit_cost% / 100
+ total_user_payment = pool_obligation + conversion_fee
+ pool_profit = (pool_obligation − L) + conversion_fee
+
+lazy_pool:
+ free_balance += total_user_payment (obligation + fee from bettor's balance)
+ leverage_fund_used −= L (free up loan capacity)
+ earned_balance += pool_profit (track cumulative earnings)
+ reward_per_share += pool_profit × 10^9 / total_shares (distribute to LP investors)
+ total_shares — no change
+ allocated_balance — no change
+
+User:
+ balance −= total_user_payment
+
+Position:
+ status → converted (5)
+ Bet record updated: remove leveraged_position, keep as normal bet (100% bettor-owned)
+
+Fund flow: user.balance → lazy_pool.free_balance
+Effect: Pool receives full loan repayment + profit + conversion fee.
+ The bettor assumes 100% outcome risk at resolution.
+ conversion_profit_cost is committee-configurable (default 50%).
+```
+
+See [Leverage Risk-Off Strategy](../.qoder/docs/leverage-risk-off-strategy.md) — Convert to Normal Bet (`pm_leverage_convert`) for full specification.
+
+### Transition 11: Emergency Withdrawal (With Penalty)
+
+```
+User emergency-withdraws with penalty on locked portion's profit.
+ penalty = profit × (locked_shares / total_shares) × emergency_penalty / 100
+
+BEFORE: settle user rewards
+
+lazy_pool:
+ free_balance −= (total_value − penalty) (pay out to user)
+ total_shares −= user_total_shares (all shares burned)
+ earned_balance — no change
+ reward_per_share += penalty × 10^9 / remaining_shares (penalty redistributed)
+ allocated_balance — no change
+ leverage_fund_used — no change
+
+Fund flow: lazy_pool.free_balance → user.balance (minus penalty)
+ penalty stays in pool → redistributed to remaining LP investors via reward_per_share
+Effect: Penalty incentivizes planned withdrawals and compensates remaining LPs.
+```
+
+---
+
+## 7. Leverage Fund Allocation
+
+The leverage fund is a **sub-allocation (cap)** on `free_balance`, not a separate pool with its own balance. It defines how much of the free balance may be used for leverage loans.
+
+```
+leverage_fund_total = free_balance × F% / 100 (cap, not a separate balance)
+leverage_fund_available = leverage_fund_total − leverage_fund_used
+max_loan_per_position = leverage_fund_available × P% / 100
+```
+
+**Key principle:** All leverage returns (loan repayment + profit) flow into `lazy_pool.free_balance`. The `leverage_fund_used` counter only tracks outstanding loan obligations — decrementing it frees capacity for new loans but does not move money to a separate account. The profit (L × R%) is distributed to lazy pool investors via `reward_per_share`, exactly like any other pool earnings.
+
+**`leverage_fund_used` tracking** — updated atomically within the same transaction as the position status change:
+
+| Event | `leverage_fund_used` | Fund flow |
+|-------|----------------------|-----------|
+| `pm_leverage_open` | `+= loan` | `free_balance −= (C + L)` (bet placed into AMM) |
+| `pm_leverage_liquidate` | `−= loan` | `free_balance += pool_received` (loan + profit); `reward_per_share += profit` |
+| `pm_leverage_resolve` (win) | `−= loan` | `free_balance += pool_received` (loan + profit); `reward_per_share += profit` |
+| `pm_leverage_resolve` (loss) | `−= loan` | Pool loses L (absorbed by pool); `leverage_fund_used` still decremented |
+| `pm_leverage_close` (voluntary) | `−= loan` | `free_balance += pool_received` (loan + profit); `reward_per_share += profit` |
+
+---
+
+## 8. earned_balance and Withdrawal
+
+`earned_balance` tracks the **cumulative lifetime earnings** of the pool. It is used during withdrawal to determine what portion of a user's payout is **earned profit** (withdrawable immediately) vs. **principal return** (subject to lock period rules).
+
+```
+User's earned portion at withdrawal:
+ user_earned = earned_balance × (user_shares / total_shares)
+ user_principal = user_total_value − user_earned
+
+Where user_total_value = user_shares × free_balance / total_shares + pending_rewards
+```
+
+This separation matters for:
+
+- **Tax reporting**: earned income vs. capital return may have different tax treatment
+- **Emergency withdrawal penalty**: penalty applies only to profit on locked shares
+- **Pool health monitoring**: `earned_balance / total_value` indicates pool profitability over time
+
+### When earned_balance Changes
+
+| Transition | earned_balance change | Source |
+|------------|----------------------|--------|
+| Market resolves with profit | `+= lp_profit` | LP fee earnings on market |
+| Leverage position closed with profit | `+= pool_profit` | Leverage interest (L × R%) |
+| Emergency withdrawal penalty | — no change | Penalty goes to `reward_per_share`, not `earned_balance` |
+| User deposit | — no change | |
+| User withdrawal | — no change | Cumulative: never decreases |
+
+---
+
+## 9. Precision Reference
+
+| Constant | Value | Used For |
+|----------|-------|----------|
+| `PRECISION` | 1000 | Milli-VIZ (all monetary values in DB) |
+| `LAZY_POOL_PRECISION` | 10^9 (1,000,000,000) | `reward_per_share` accumulator integer math |
+| `PRICE_PRECISION` | 1,000,000 | Probability display (6 decimal places) |
+
+**Why 10^9 for reward_per_share?**
+
+Shares are denominated in milli-VIZ (1 VIZ = 1000 units), displayed as `1.000000`. The 10^9 multiplier is an internal integer math precision ensuring that even tiny profits distribute non-zero increments:
+
+```
+Example: 1 mVIZ profit on 1,000,000 shares
+ reward_per_share increment = 1 × 10^9 / 1,000,000 = 1000 (non-zero)
+
+If we used 10^6 instead:
+ reward_per_share increment = 1 × 10^6 / 1,000,000 = 1 (non-zero, but very coarse)
+
+If we used 10^3 (same as PRECISION):
+ reward_per_share increment = 1 × 10^3 / 1,000,000 = 0 (DUST — profit lost!)
+```
+
+The 10^9 factor provides 6 orders of magnitude between the smallest unit (1 mVIZ) and the accumulator, ensuring accurate distribution across up to ~10^6 shares without dust loss.
+
+---
+
+## 10. Auxiliary Tables
+
+### 10.1 lazy_pool_deposits (Per-Deposit Lock Tracking)
+
+| Column | Type | Description |
+|--------|------|-------------|
+| `id` | bigint PK | Auto-increment |
+| `user` | bigint | User ID |
+| `time` | int | Deposit unixtime |
+| `amount` | bigint | Principal (milli-VIZ) |
+| `shares` | bigint | Share tokens received (milli-VIZ) |
+| `unlock_time` | int | Unixtime when deposit unlocks |
+| `status` | tinyint | 0=locked, 1=unlocked, 2=withdrawn, 3=emergency_withdrawn |
+
+Each user has at most: **1 unlocked record** (status=1, consolidated) + **N locked records** (status=0). When locked deposits expire, they merge into the single unlocked record.
+
+### 10.2 lazy_pool_allocations (Per-Market Allocation Tracking)
+
+| Column | Type | Description |
+|--------|------|-------------|
+| `id` | bigint PK | Auto-increment |
+| `market` | bigint | Market ID (unique key) |
+| `amount` | bigint | Current allocated amount (milli-VIZ, decreases with recalls) |
+| `original_amount` | bigint | Original allocated amount before recalls |
+| `time` | int | Allocation unixtime |
+| `status` | tinyint | 0=active, 1=returned |
+| `returned_amount` | bigint | Amount returned including profit |
+| `bets_sum_at_check` | bigint | Cumulative bets_sum at last recall check |
+| `check_step` | int | Which 10% duration step (0=just allocated, 1..10) |
+| `last_check_time` | int | Unix timestamp of last recall check |
+| `recalled_amount` | bigint | Total amount recalled so far |
+
+---
+
+## 11. Complete Transition Matrix
+
+Summary of all pool column changes across transitions:
+
+| # | Transition | `free_balance` | `allocated_balance` | `earned_balance` | `leverage_fund_used` | `reward_per_share` | `total_shares` |
+|---|-----------|----------------|---------------------|------------------|----------------------|--------------------|----------------|
+| 1 | Deposit | `+= A` | — | — | — | — | `+= new_shares` |
+| 2 | Allocate | `−= alloc` | `+= alloc` | — | — | — | — |
+| 3 | Resolve (no profit) | `+= alloc` | `−= alloc` | — | — | — | — |
+| 4 | Resolve (with profit) | `+= return` | `−= alloc` | `+= profit` | — | `+= profit×10^9/N` | — |
+| 5 | Recall | `+= recall` | `−= recall` | — | — | — | — |
+| 6 | Leverage open | `−= (C+L)` | — | — | `+= L` | — | — |
+| 7 | Leverage close | `+= received` | — | `+= profit` | `−= L` | `+= profit×10^9/N` | — |
+| 7a | Leverage close (bad debt) | `+= cancel·−shortfall` | — | — | `−= L` | — | — |
+| 8 | Leverage resolve (loss) | — | — | — | `−= L` | — | — |
+| 8a | Leverage resolve (win) | `+= received` | — | `+= profit` | `−= L` | `+= profit×10^9/N` | — |
+| 9 | Withdraw | `−= payout` | — | — | — | — | `−= burned` |
+| 10 | Leverage convert | `+= total_payment` | — | `+= pool_profit` | `−= L` | `+= profit×10^9/N` | — |
+| 11 | Emergency withdraw | `−= (payout−penalty)` | — | — | — | `+= penalty×10^9/N'` | `−= all_shares` |
+
+Legend: `N` = total_shares, `N'` = remaining_shares after burn, `—` = no change
diff --git a/.qoder/plans/pm-plan/leverage-risk-off-strategy.md b/.qoder/plans/pm-plan/leverage-risk-off-strategy.md
new file mode 100644
index 0000000000..de439d571d
--- /dev/null
+++ b/.qoder/plans/pm-plan/leverage-risk-off-strategy.md
@@ -0,0 +1,2044 @@
+# Leverage Risk-Off Strategy with Lazy Pool
+
+## Table of Contents
+
+1. [Concept](#concept)
+2. [Architecture Overview](#architecture-overview)
+3. [Leverage Fund Allocation](#leverage-fund-allocation)
+4. [Mathematical Model](#mathematical-model)
+ - [CPMM Bet & Token Calculation](#cpmm-bet--token-calculation)
+ - [Cancel Value (Position Exit Price)](#cancel-value-position-exit-price)
+ - [Worst-Case Cancel Value After Opposing Bet](#worst-case-cancel-value-after-opposing-bet)
+ - [Liquidation Threshold](#liquidation-threshold)
+ - [Safety Margin](#safety-margin)
+ - [Maximum Available Leverage — Full Constraint System](#maximum-available-leverage--full-constraint-system)
+5. [Atomic Liquidation at Block Level](#atomic-liquidation-at-block-level)
+6. [Liquidation Outcome Distribution](#liquidation-outcome-distribution)
+7. [Resolution Handling](#resolution-handling)
+ - [Market Status Trigger for Force-Close](#market-status-trigger-for-force-close)
+8. [Protocol Settings](#protocol-settings)
+9. [Database Schema](#database-schema)
+10. [API Endpoints](#api-endpoints)
+11. [Frontend: Pre-Calculation, Slider & Cancel Warning](#frontend-pre-calculation-slider--cancel-warning)
+12. [Protocol Operations (VIZ DLT)](#protocol-operations-viz-dlt)
+13. [Numerical Examples](#numerical-examples)
+14. [Risk Analysis](#risk-analysis)
+15. [Implementation Notes](#implementation-notes)
+
+---
+
+## 1. Concept
+
+**Leverage** allows a bettor to amplify their market exposure beyond their available balance. The Lazy Pool provides the additional capital as a **co-investment** — the pool contributes a loan L, the bettor contributes collateral C, and together they place a bet of (C + L) on the chosen outcome. The pool earns a fixed profit percentage on every loan.
+
+> **⚠️ Critical distinction — leverage is a bet on market price movement, NOT on the outcome result.** All leveraged positions are force-closed before the market reaches resolution. You are betting that the market price of your chosen outcome will move in your favor *during the betting period* — you will never hold a leveraged position to resolution. Your profit or loss comes entirely from the change in market price (social belief shift), not from whether the outcome actually occurs.
+
+### Naming Convention: "Boost" (UI) vs. "Leverage" (Protocol)
+
+The user-facing term is **Boost**. The protocol/API term remains **leverage**.
+
+| Context | Term | Examples |
+|---------|------|----------|
+| **UI / Frontend / Marketing** | **Boost** | "Boost your bet", "5× Boost", "Open Boosted Position", "Close Boost" |
+| **Protocol / API / Database** | **leverage** | `pm_leverage_open`, `leverage-preview`, `leveraged_positions` table |
+
+**Why "Boost" and not "Leverage" in the UI?**
+
+1. "Leverage" (кредитное плечо) implies holding a position through dips to expiration — classic margin trading. Our positions are **force-closed before resolution** with an automatic stop-loss. This is fundamentally different.
+2. "Boost" implies a **temporary enhancement** — you boost your bet, the market moves, you capture the change. This matches the actual mechanism.
+3. "5× Boost" is more intuitive for non-traders than "5× Leverage".
+4. "Leverage" carries the expectation of "I can wait it out". "Boost" carries the expectation of "the position is amplified temporarily" — which is correct.
+5. In Russian i18n: "Усиление ставки" (bet boost) is clearer than "Кредитное плечо" (credit shoulder) for non-finance users.
+
+All internal documentation, API endpoints, protocol operations, and database schemas retain the term **leverage** for technical precision and DeFi convention. Only the **user-facing** language changes.
+
+### Two Categories of Risk
+
+| Risk Type | Pool Exposure | Status |
+|-----------|--------------|--------|
+| **Price-movement risk** (opposing bets, slippage during betting period) | Pool loses money if forced to liquidate at unfavorable price | **Structural zero** — eliminated by atomic pre-check + constraint system |
+| **Outcome risk** (bettor's outcome loses at resolution) | Pool loses the loan L | **Not eliminated** — inherent to all betting. The pool is a co-investor on the bettor's chosen outcome |
+
+The pool is protected from price-movement risk by:
+- **Pre-execution slippage calculation** — the exact CPMM formula gives deterministic price impact of any opposing bet.
+- **Atomic liquidation at block level** — if an incoming bet would push a leveraged position below its safe threshold, the protocol liquidates the position *first* (at current prices), then processes the bet. Gap risk is eliminated.
+- **Dynamic leverage caps** — maximum leverage is calculated per-market from liquidity depth, and per-pool from leverage fund availability.
+- **Safety margin** — applied during position opening to prevent positions that are too close to the liquidation edge.
+- **Force-close before expiration** — Cron Job 11 closes all leveraged positions before betting expires, converting outcome risk into price-movement risk (which is zero). The pool recovers L × (1 + R%) from every position.
+
+---
+
+## 2. Architecture Overview
+
+> **Note:** The percentages below (90% / 10%) are defaults for `leverage_fund_percent` (committee-configurable). The actual split depends on the current governance setting.
+
+```
+┌──────────────────────────────────────────────────────────────┐
+│ LAZY LIQUIDITY POOL │
+│ │
+│ Stored: │
+│ free_balance = 10,000,000 mVIZ (10,000 VIZ) │
+│ allocated_balance = 7,000,000 mVIZ ( 7,000 VIZ) │
+│ earned_balance = 1,500,000 mVIZ ( 1,500 VIZ) │
+│ leverage_fund_used = 100,000 mVIZ ( 100 VIZ) │
+│ total_shares = 8,000,000 (8,000 shares) │
+│ reward_per_share = 187,500,000 (10^9 precision) │
+│ │
+│ Computed: │
+│ total_value = free + allocated = 17,000 VIZ │
+│ invested_liquidity = allocated_balance (7,000 VIZ) │
+│ invested_leverage = leverage_fund_used ( 100 VIZ) │
+│ free_amount = free_balance − leverage_fund_used │
+│ = 10,000 − 100 = 9,900 VIZ │
+│ │ │
+│ ├── LP allocations → invested_liquidity (7,000 VIZ) │
+│ │ │
+│ └── F% of free_balance → Leverage Fund (1,000 VIZ) │
+│ │ │
+│ ├── Active loans: 100 VIZ (invested_leverage)│
+│ └── Available: 900 VIZ │
+│ │ │
+│ └── Per-position max: P% = 1,800 VIZ │
+└──────────────────────────────────────────────────────────────┘
+
+┌─ Bettor places leveraged bet ────────────────────────────────┐
+│ │
+│ Bettor collateral: 360 VIZ (from bettor's balance) │
+│ Pool loan: 1800 VIZ (from leverage fund) │
+│ Total bet: 2160 VIZ (placed on outcome A) │
+│ Leverage: 5× │
+│ Pool profit (R%): 180 VIZ (paid if bet wins/liquid.) │
+│ │
+│ ┌─ Frontend slider ────────────────────────────────────┐ │
+│ │ Leverage: [1.0×]────●────[3.0×]────[5.0×] │ │
+│ │ Loan: 1800 VIZ │ │
+│ │ Total bet: 2160 VIZ │ │
+│ │ Liq. at: cancel_value ≤ 1980 VIZ (1800+180) │ │
+│ │ Current cancel_value: 2155 VIZ ✅ │ │
+│ └──────────────────────────────────────────────────────┘ │
+└──────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## 3. Leverage Fund Allocation
+
+The leverage fund is a **sub-allocation** of the lazy pool's `free_balance`. It is not a separate pool — it's a cap on how much of the free balance can be used for leveraged loans.
+
+For a complete reference on all lazy pool properties (stored and computed) and state transitions, see [Lazy Pool: Properties, Accounting & State Transitions](../../docs/lazy-pool-properties.md).
+
+| Parameter | Symbol | Default | Description |
+|-----------|--------|---------|-------------|
+| `leverage_fund_percent` | `F%` | 10% | Percentage of lazy pool `free_balance` allocated to leverage fund |
+| `max_leverage_per_position_percent` | `P%` | 0.2% | Maximum percentage of **available** leverage fund per single position |
+| `leverage_pool_profit_percent` | `R%` | 10% | Pool's profit on each leveraged loan (paid on win or liquidation) |
+| `leverage_safety_margin_percent` | `S%` | 1% | Safety margin applied during position opening |
+| `leverage_max_slippage_percent` | `SL%` | 10% | Maximum price impact of any single bet (protocol-enforced) |
+
+**Fund state calculation:**
+
+```
+leverage_fund_total = lazy_pool.free_balance × F% / 100
+leverage_fund_used = Σ(active leveraged loans)
+leverage_fund_available = leverage_fund_total - leverage_fund_used
+
+max_loan_per_position = leverage_fund_available × P% / 100
+```
+
+**Example:**
+
+```
+lazy_pool.free_balance = 10,000,000 VIZ
+F% = 10% → leverage_fund_total = 1,000,000 VIZ
+active loans = 100,000 VIZ
+leverage_fund_available = 900,000 VIZ
+P% = 0.2% → max_loan_per_position = 1,800 VIZ
+```
+
+**`leverage_fund_used` tracking** — updated atomically within the same transaction as the position status change:
+
+| Event | `leverage_fund_used` | `earned_balance` | Fund flow |
+|-------|----------------------|------------------|-----------|
+| `pm_leverage_open` | `+= loan` | — | `free_balance -= (C + L)` (bet placed into AMM) |
+| `pm_leverage_liquidate` | `-= loan` | `+= pool_profit` | `free_balance += pool_received` (loan + profit); `reward_per_share += pool_profit` |
+| `pm_leverage_resolve` (win) | `-= loan` | `+= pool_profit` | `free_balance += pool_received` (loan + profit); `reward_per_share += pool_profit` |
+| `pm_leverage_resolve` (loss) | `-= loan` | — | Pool loses L (absorbed by pool); `leverage_fund_used` still decremented |
+| `pm_leverage_close` (voluntary) | `-= loan` | `+= pool_profit` | `free_balance += pool_received` (loan + profit); `reward_per_share += pool_profit` |
+
+> **Key principle:** The leverage fund is NOT a separate pool with its own balance — it is a **cap** on how much of `lazy_pool.free_balance` can be used for loans. All funds (loan repayment + profit) flow into `lazy_pool.free_balance`. The `leverage_fund_used` counter only tracks how much loan capacity is currently occupied; decrementing it frees up capacity for new loans but does not move money to a separate account. The profit (`pool_profit = pool_received − loan`) is recorded in `earned_balance` (cumulative pool earnings) and distributed to lazy pool investors via `reward_per_share`, exactly like any other pool earnings.
+
+---
+
+## 4. Mathematical Model
+
+### 4.1 CPMM Bet & Token Calculation
+
+All values in milli-VIZ (1 VIZ = 1000 internal units). Precision constants:
+
+```
+PRECISION = 1000 (milli-VIZ — all monetary values in DB)
+PRICE_PRECISION = 1000000 (probability display, 6 decimal places)
+LAZY_POOL_PRECISION = 10^9 (reward_per_share accumulator — internal integer math)
+```
+
+| Constant | Value | Used For | Why |
+|----------|-------|----------|-----|
+| `PRECISION` | 1,000 | All DB monetary columns | 1 VIZ = 1000 milli-VIZ (3 decimal places) |
+| `PRICE_PRECISION` | 1,000,000 | Probability display | 6 decimal places (0.000001 precision) |
+| `LAZY_POOL_PRECISION` | 10^9 | `reward_per_share` accumulator | Ensures 1 mVIZ profit distributes non-zero across ≤10^6 shares |
+
+> **Precision note:** The lazy pool's `reward_per_share` uses `LAZY_POOL_PRECISION = 10^9` (1,000,000,000). This is an **internal integer math multiplier** for the reward accumulator — not the display precision of shares. Shares are denominated in milli-VIZ (same as `PRECISION = 1000`), displayed as `1.000000` (6 decimal places). The `10^9` multiplier ensures that `reward_per_share` has enough resolution to distribute small profits accurately across many shares: `reward_per_share += leverage_profit_mVIZ × (10^9 / total_shares)`. When a user claims rewards: `user_reward = shares × (reward_per_share − snapshot) / 10^9`. The 10^9 factor is chosen so that even a 1 mVIZ profit on 1,000,000 shares produces a non-zero `reward_per_share` increment (1000 / 10^9 × 10^6 = 1, avoidable dust).
+
+**Binary CPMM (Onix Binary):**
+
+```
+k = reserve_a × reserve_b
+
+Bet amount B on outcome A:
+ reserve_b' = reserve_b + B
+ reserve_a' = floor(k / reserve_b')
+ tokens_X = reserve_a − reserve_a' (weight received)
+ price = floor(B × PRICE_PRECISION / tokens_X)
+```
+
+**Multi LMSR (Onix Multi):**
+
+```
+q_i = net quantity for outcome i
+b = liquidity parameter
+
+price(i) = exp(q_i / b) / Σ_j exp(q_j / b)
+
+buy_cost(Δ, i) = C(q + Δ·e_i) − C(q)
+ where C(q) = b × ln(Σ_j exp(q_j / b))
+
+tokens_for_amount(B, i) = binary_search:
+ find max Δ where buy_cost(Δ, i) ≤ B
+```
+
+### 4.2 Cancel Value (Position Exit Price)
+
+The cancel value is the VIZ returned when selling tokens back to the AMM via reverse CPMM/LMSR.
+
+**Binary CPMM cancel:**
+
+```
+Given: tokens X on outcome A, current reserves (reserve_a, reserve_b), k
+
+Return X tokens to reserve_a:
+ reserve_a' = reserve_a + X
+ reserve_b' = floor(k / reserve_a')
+ cancel_value = reserve_b − reserve_b'
+
+Safety floor:
+ if cancel_value ≤ 0 → cancel_value = 0
+```
+
+**Multi LMSR cancel:**
+
+```
+sell_return(Δ, i) = C(q) − C(q − Δ·e_i)
+```
+
+### 4.3 Worst-Case Cancel Value After Opposing Bet
+
+Given a leveraged position of X tokens on outcome A (already placed at total_bet = C + L), and a protocol-enforced maximum bet `M_max` on the opposing side B:
+
+```
+M_max = floor(min(reserve_a, reserve_b) × SL% / 100)
+```
+
+> **Skewed markets:** On a 95/5 market, `min(reserve_a, reserve_b)` is very small, giving a tiny M_max. This is correct — on skewed markets, even a small opposing bet moves the price significantly, so the slippage cap correctly limits the bet size. The leverage constraint becomes more restrictive on skewed markets, which is the desired behavior (thin opposing side = less leverage available).
+
+The cancel value after an opposing bet of size M on side B:
+
+```
+Binary CPMM:
+ reserve_a_M = reserve_a + M
+ reserve_b_M = floor(k / reserve_a_M)
+ reserve_a_MX = reserve_a_M + X
+ reserve_b_MX = floor(k / reserve_a_MX)
+ cancel_value(M) = reserve_b_M − reserve_b_MX
+
+Closed form:
+ cancel_value(M) = k × X / ((reserve_a + M) × (reserve_a + M + X))
+ (with floor rounding for integer precision)
+```
+
+> **Important:** `reserve_a` and `reserve_b` in these formulas refer to the market reserves **after** the leveraged bet of (C + L) has been placed (i.e., `reserve_a'` and `reserve_b'` from Section 4.1), not the original market reserves. `cancel_value_worst(L)` is computed for a position already opened at total_bet = C + L.
+
+This is a monotonically decreasing function of M. Therefore:
+
+```
+cancel_value_worst = cancel_value(M_max)
+```
+
+**LMSR Multi worst-case:**
+
+For multi-outcome markets, the worst case is a maximum bet concentrated on the **single opposing outcome** that causes the largest decrease in the leveraged position's sell return. This is typically the opposing outcome with the **highest current price** (closest to the leveraged outcome in probability space).
+
+```
+For a leveraged position on outcome i with Δ tokens:
+ M_max = lmsr_max_bet_amount(b, q, SL%)
+
+ worst_opposing = argmax_j (j ≠ i) decrease_in_sell_return(Δ, i, M_max on j)
+
+ cancel_value_worst = sell_return(Δ, i) after bet of M_max on worst_opposing
+
+ The binary search in Constraint 2 must evaluate all opposing outcomes
+ and select the one producing the lowest cancel_value.
+```
+
+### 4.4 Liquidation Threshold
+
+```
+liquidation_threshold = L × (1 + R% / 100)
+
+where:
+ L = loan amount from pool
+ R% = pool profit percentage
+```
+
+**Example:** L = 1800 VIZ, R% = 10% → liquidation_threshold = 1980 VIZ.
+
+A position is **liquidatable** when:
+
+```
+cancel_value_current ≤ liquidation_threshold
+```
+
+### 4.5 Safety Margin
+
+The safety margin `S%` is applied **only during position opening** — it prevents positions that are too close to the liquidation edge. Once a position is open, liquidation triggers at the real `liquidation_threshold` (without margin). The margin ensures there is always a buffer between the current cancel value and the threshold at opening time.
+
+```
+safe_liquidation_threshold = liquidation_threshold × (1 + S% / 100)
+```
+
+Applied when:
+- Computing max available leverage (Constraint 2)
+- Checking whether a position can be opened (Validation Rule 8)
+
+**Not** applied when:
+- Triggering liquidation (Section 5 uses `liquidation_threshold` directly)
+
+The frontend **displays** the liquidation threshold without safety margin (user sees the real number). The frontend adds a subtle note:
+
+> ℹ️ A 1% safety buffer is included during position opening to protect against simultaneous requests.
+
+### 4.6 Maximum Available Leverage — Full Constraint System
+
+For a bettor with collateral C, choosing outcome A, the maximum leverage is the minimum of four independent constraints:
+
+```
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+CONSTRAINT 1: Leverage Fund Availability
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ L_max_fund = leverage_fund_available × P% / 100
+ leverage_max_fund = L_max_fund / C
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+CONSTRAINT 2: Market Liquidity (Slippage Cap)
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ M_max = floor(min(reserve_a, reserve_b) × SL% / 100)
+
+ Find maximum L such that:
+ cancel_value_worst(L) ≥ L × (1 + R%/100) × (1 + S%/100) + ε
+
+ where ε = 0 for CPMM binary markets
+ ε = max(lmsr_rounding_margin, b × lmsr_b_scale_factor) for LMSR Multi
+ (dynamic: accounts for small-b markets where rounding scales with b)
+ (see Section 14 — Integer Rounding for rationale)
+
+ where cancel_value_worst(L) = cancel value after max opposing bet,
+ computed for a total bet of (C + L) already placed in the reserves.
+
+ This is solved via binary search over L ∈ [0, L_max_fund].
+
+ leverage_max_market = L_market / C
+```
+
+> **Defense-in-depth note:** Constraint 2 is deliberately conservative for the **prototype** (non-atomic PHP + MySQL). It protects against the scenario where the full M_max is applied in one shot **without** triggering atomic liquidation (e.g., multi-block price drift where no single bet crosses the threshold). Since atomic liquidation fires *before* the bet that crosses the threshold (Section 5), the actual cancel_value at liquidation is always HIGHER than cancel_value_worst(M_max).
+>
+> **VIZ DLT relaxation plan:** On VIZ DLT with native atomic liquidation, the full-M_max constraint is unnecessarily conservative and limits leverage to unattractive levels (e.g., 1.1×–1.3× on a deep 500K/500K market when the fund allows 5×). The following relaxation is planned for the VIZ DLT migration:
+>
+> **Option A: Fixed relaxation factor** (recommended for initial VIZ DLT deployment)
+> ```
+> M_effective = M_max × VIZ_DLT_M_FACTOR
+> leverage_constraint2_cancel_worst = cancel_value after M_effective (not M_max)
+> ```
+> Where `VIZ_DLT_M_FACTOR` is a committee-configurable parameter (default: 0.5 = 50% of M_max). This doubles the available leverage compared to the prototype while still maintaining a 50% buffer. The atomic liquidation guarantee ensures the pool never loses money — the constraint exists only to prevent positions that are *too close* to the threshold at opening.
+>
+> **Option B: Dynamic depth factor** (future optimization)
+> ```
+> depth_factor = f(liquidity_sum, active_positions, price_impact_per_unit)
+> M_effective = M_max × depth_factor
+> ```
+> Where `depth_factor` approaches 1.0 on thin markets (conservative) and 0.3–0.5 on deep markets (relaxed). This adapts to market conditions automatically. Requires more research and simulation.
+>
+> **Impact comparison** (500K/500K market, collateral = 360 VIZ, F% = 10%, P% = 0.2%):
+>
+> | Constraint 2 mode | M_used | Max leverage | User experience |
+> |-------------------|--------|-------------|----------------|
+> | Prototype (full M_max) | 50,000 VIZ | ~2.5× | Overly restrictive on deep markets |
+> | VIZ DLT Option A (50%) | 25,000 VIZ | ~4.0× | Good balance of safety and utility |
+> | VIZ DLT Option A (30%) | 15,000 VIZ | ~5.0× | Matches fund capacity |
+> | No Constraint 2 | 0 | Fund-limited only | Unsafe — no worst-case protection |
+
+```
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+CONSTRAINT 3: Maximum Position Size Relative to Market
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ total_bet ≤ liquidity_sum × leverage_max_position_ratio / 100
+ → (C + L) ≤ liquidity_sum × POS% / 100
+
+ L_max_size = floor(liquidity_sum × POS% / 100) − C
+ if L_max_size < 0: leverage unavailable (collateral alone exceeds limit)
+
+ leverage_max_size = L_max_size / C
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+CONSTRAINT 4: Market Minimum Liquidity
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ if liquidity_sum < leverage_min_market_liquidity:
+ leverage_max = 0 (leverage unavailable on this market)
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+FINAL: Maximum Leverage
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+ leverage_max = floor(min(leverage_max_fund,
+ leverage_max_market,
+ leverage_max_size) × 100) / 100
+ (rounded down to 0.01× precision for slider)
+```
+
+If `leverage_max < 1.1`, leverage is **unavailable** for this market/bettor combination.
+
+---
+
+## 5. Atomic Liquidation at Block Level
+
+The core risk-elimination mechanism: liquidation happens **before** the opposing bet changes reserves.
+
+### Block Processing Order (VIZ DLT Plugin)
+
+```
+┌─ Block N ─────────────────────────────────────────────────┐
+│ │
+│ For each transaction in block, in order: │
+│ │
+│ CASE A: pm_place_bet(outcome≠leveraged_outcome) │
+│ ───────────────────────────────────────────────────── │
+│ Opposing bet moves price against leveraged position. │
+│ Liquidate BEFORE the bet (pool guaranteed whole). │
+│ │
+│ 1. SCAN: find all leveraged positions that would be │
+│ liquidated by this bet. │
+│ (For multi-outcome: ANY bet not on the leveraged │
+│ outcome triggers the check.) │
+│ │
+│ For each leveraged position: │
+│ cancel_after = cancel_value_after_bet(M, position)│
+│ if cancel_after ≤ position.liquidation_threshold: │
+│ → mark for pre-liquidation │
+│ │
+│ 2. REPEAT (cascade loop): │
+│ a. EXECUTE one pre-liquidation (virtual operation): │
+│ → pm_leverage_liquidate(position) │
+│ → Cancel the bet at CURRENT reserves │
+│ → Distribute: pool gets min(cancel_value, L+R) │
+│ → Bettor gets remainder (if any) │
+│ → Return L to leverage_fund_available │
+│ b. Re-read market reserves (changed by liquidation) │
+│ c. Re-scan all active leveraged positions at new │
+│ reserves. If any new position ≤ threshold: │
+│ → add to pre-liquidation queue, go to (a) │
+│ UNTIL no more positions below threshold. │
+│ (See “Liquidation Cascade” below.) │
+│ │
+│ 3. EXECUTE the original pm_place_bet │
+│ (at reserves after all pre-liquidations) │
+│ │
+│ ───────────────────────────────────────────────────── │
+│ │
+│ CASE B: pm_cancel_bet that moves price against │
+│ a leveraged position │
+│ (same outcome for CPMM; any outcome for LMSR) │
+│ ───────────────────────────────────────────────────── │
+│ Cancel-bet moves price against leveraged position. │
+│ Execute cancel-bet FIRST (transaction initiator gets │
+│ the exact price they submitted at — not penalized by │
+│ someone else's leveraged position). Then liquidate. │
+│ │
+│ 1. EXECUTE pm_cancel_bet at current reserves │
+│ Cancel-bettor receives exact expected payout. │
+│ │
+│ 2. REPEAT (cascade loop): │
+│ a. SCAN: re-evaluate all active leveraged positions │
+│ on this outcome at the CURRENT reserves. │
+│ (For multi-outcome: check ALL leveraged positions,│
+│ not just on the same outcome.) │
+│ b. If any position has cancel_value ≤ threshold: │
+│ → EXECUTE post-liquidation (virtual operation): │
+│ → pm_leverage_liquidate(position) │
+│ → Cancel the bet at current reserves │
+│ → Distribute: pool gets cancel_value (may be │
+│ < L+R — see "Bad debt" below) │
+│ → Bettor gets 0 (cancel_value ≤ threshold) │
+│ → Return L to leverage_fund_available │
+│ → Re-read market reserves, go to (a) │
+│ UNTIL no more positions below threshold. │
+│ (See “Liquidation Cascade” below.) │
+│ │
+│ Bad debt handling (per liquidation): │
+│ shortfall = liquidation_threshold − cancel_value │
+│ if shortfall > 0: │
+│ lazy_pool.free_balance -= shortfall │
+│ (absorbed by pool — bounded, rare event) │
+│ │
+│ ELSE: │
+│ Process normally │
+│ │
+└───────────────────────────────────────────────────────────┘
+```
+
+**Key invariant (Case A — opposing bet):** Step 2 liquidates at pre-bet reserves. The opposing bettor in Step 3 gets a marginally better price (the market moved toward A because X tokens were returned to reserve_a). The pool is guaranteed whole — `cancel_value ≥ liquidation_threshold` at pre-bet reserves. The cascade loop in Step 2 ensures that liquidating one position (which changes reserves) doesn't leave other positions underwater — each iteration re-evaluates all remaining positions at the updated reserves.
+
+**Key invariant (Case B — cancel-bet):** The cancel-bettor gets the exact price at pre-liquidation reserves. Liquidation happens AFTER at potentially lower `cancel_value`. If `cancel_value < liquidation_threshold`, the pool absorbs the shortfall as bad debt. This is a bounded, rare event — the position was already near liquidation, and the cancel-bet just happened to be the final push. The transaction initiator must never be penalized by another user's leveraged position. The cascade loop ensures that if liquidating one position pushes others below threshold (due to reserve changes), those are caught immediately rather than left as hidden bad debt.
+
+### Liquidation Rebalancing Spread (MEV Note)
+
+When a leveraged position on A is liquidated (tokens returned to reserve_a), the opposing bet on B in Step 3 executes at a slightly better price than it would have without the liquidation. This "rebalancing spread" creates a small MEV incentive:
+
+```
+For a position of size X tokens (total_bet = C + L) on a market with
+liquidity_sum = LS:
+
+ Approximate spread = (C + L) / LS (fraction of market)
+
+ With POS% = 5%: spread ≈ 5% (price improvement for opposing bettor)
+
+ This means the opposing bettor gets ~5% better execution than they
+ would have without the liquidation.
+```
+
+**Assessment:** This is NOT a security risk for the pool (the pool recovers L + R regardless). It's a minor information asymmetry — bots that can predict which bets will trigger liquidations get a small advantage. The magnitude is bounded by POS% and is acceptable for the prototype.
+
+**Known limitation (prototype):** The opposing bettor who triggers liquidation automatically receives a ~POS% price improvement — they don't need to do anything special. This creates a systematic incentive for bots to place opposing bets that deliberately target leveraged positions near their liquidation threshold. The bot's bet on B triggers liquidation, then executes at post-liquidation price (better for the bot). This is a zero-sum transfer from the liquidated bettor to the opposing bettor — the pool is unaffected.
+
+**Mitigation on VIZ DLT:** Fix the opposing bet's price at pre-liquidation reserves ("price lock" at the time the bet was submitted). This eliminates the spread advantage entirely — the opposing bettor pays the price they expected, and the liquidation rebalancing does not create a systematic arbitrage opportunity.
+
+### Liquidation Sandwich MEV
+
+There is a second MEV vector related to liquidation — an attacker can **front-run the liquidating bet** with a bet on the leveraged outcome, slightly reducing the cancel_value received by the liquidated position:
+
+```
+Mempool:
+ Tx 1: pm_place_bet(B, amount) ← will trigger liquidation of position on A
+ Tx 2: pm_place_bet(A, amount2) ← unrelated bet on A
+
+Attacker sees Tx 1 in mempool, inserts their own bet on A BEFORE it:
+ Block order: [attacker bet on A] → [Tx 1 on B — triggers liquidation] → [Tx 2 on A]
+
+Step-by-step:
+ 1. Attacker's bet on A executes normally (buys A tokens at current price)
+ 2. Tx 1 (bet on B) arrives → atomic check fires → position liquidated
+ BUT: liquidation cancel_value is computed at reserves AFTER attacker's bet
+ (attacker's bet on A shifted reserves slightly, reducing cancel_value)
+ 3. Liquidated position returns X tokens to reserve_a → market shifts back toward A
+ 4. Tx 2 (bet on A) executes at post-liquidation price (slightly better for Tx 2)
+
+Net effect on liquidated position:
+ cancel_value is slightly LOWER than it would be without the attacker's bet
+ The attacker's bet on A shifted the price, reducing what the liquidated
+ position gets back.
+
+Maximum impact:
+ Attacker's bet is bounded by SL% (leverage_max_slippage_percent)
+ Maximum cancel_value reduction ≈ SL% × (C + L) / liquidity_sum
+ With SL% = 10% and POS% = 5%: impact ≈ 0.5% of total_bet
+ On a 2160 VIZ position: ≈ 10.8 VIZ reduction in cancel_value
+```
+
+**Assessment:**
+
+- **Pool risk:** NONE. The pool recovers L × (1 + R%) regardless — the cancel_value reduction comes entirely from the bettor's portion (`bettor_received`).
+- **Bettor risk:** Small. The maximum impact is bounded by `SL% × (C + L) / LS`. On typical markets this is <1 VIZ. The bettor was already being liquidated (position near threshold), so the additional loss is marginal.
+- **Mitigation on VIZ DLT:** The delegate can lock the liquidation cancel_value at the reserves that existed **before** any front-running transaction in the same block. This eliminates the attack entirely at the consensus level. The liquidation price is computed at the reserves from the *previous block*, not at the intra-block reserves.
+- **Prototype:** Acceptable without mitigation. The impact is bounded and the bettor was already near liquidation.
+
+> **Comparison with opening MEV (Section 12):** The `max_slippage_percent` protection for `pm_leverage_open` is **bettor-initiated** — the user chooses their tolerance. Liquidation sandwich MEV has no user opt-in because liquidation is **protocol-initiated**. Mitigation must come from the protocol level (price lock at previous block reserves on VIZ DLT).
+
+### Liquidation Cascade (Long Squeeze)
+
+When a leveraged position on outcome A is liquidated, its tokens are returned to the AMM reserves (`reserve_a ↑`, `reserve_b ↓` for CPMM). This makes outcome A cheaper — the same directional move as the trigger that caused the liquidation. Other leveraged positions **on the same outcome** see their `cancel_value` decrease, potentially falling below their own `liquidation_threshold`.
+
+This creates a **cascade** — a self-reinforcing chain of liquidations known as a "long squeeze" (analogous to a short squeeze in traditional markets, but in the opposite direction: longs are force-closed as the price drops, amplifying the downward move).
+
+**Why a cascade (not a short squeeze):**
+
+A true short squeeze is an *upward* cascade — shorts forced to buy back → price UP → more shorts forced. In this system, the cascade is a *downward* cascade — longs forced to sell → price DOWN → more longs forced. An upward cascade is **structurally impossible** because liquidating a position on A returns tokens to `reserve_a` (increasing supply), making A *cheaper*, not more expensive. For the opposing side B, `cancel_value` actually *increases* after A's liquidation — B positions are strengthened, not weakened.
+
+**Why the cascade is one-directional:**
+
+| Event | Effect on same-side (A) | Effect on opposite side (B) |
+|-------|------------------------|-----------------------------|
+| Liquidate position on A | `reserve_a ↑`, `reserve_b ↓` → cancel_value ↓ (worse) | cancel_value ↑ (better) |
+| Liquidate position on B | `reserve_b ↑`, `reserve_a ↓` → cancel_value ↓ (worse) | cancel_value ↑ (better) |
+
+Conclusion: liquidating any position only harms **same-side** positions. Cross-side positions are always strengthened. The cascade only flows in one direction — toward more same-side liquidations.
+
+**Cascade scenario:**
+
+```
+Market: CPMM binary, A at ~0.55, B at ~0.45
+
+Three leveraged positions on outcome A:
+ P1: cancel_value = 1,990 VIZ, threshold = 1,980 VIZ (margin: 10)
+ P2: cancel_value = 2,010 VIZ, threshold = 2,000 VIZ (margin: 10)
+ P3: cancel_value = 2,100 VIZ, threshold = 1,990 VIZ (margin: 110)
+
+Opposing bet on B arrives → triggers Case A check:
+ Iteration 1: P1 below threshold → liquidate at pre-bet reserves
+ → P1's tokens return to reserve_a → A gets cheaper
+ → P2's cancel_value drops from 2,010 → 1,995 (below P2's threshold!)
+ Iteration 2: P2 below threshold → liquidate at current reserves
+ → P2's tokens return to reserve_a → A gets even cheaper
+ → P3's cancel_value drops from 2,100 → 2,050 (still above threshold)
+ No more positions below threshold → cascade terminates
+
+Result: P1 and P2 liquidated, P3 survives (still above threshold)
+```
+
+**Guaranteed termination:** The cascade loop always terminates because:
+1. Each iteration liquidates ≥1 position (otherwise the loop breaks)
+2. There are a finite number of active positions
+3. Positions can only be liquidated once (status changes from 0 to 1/4)
+
+In practice, with `max_leverage_per_position_percent = 5%`, each position's price impact is at most 5% of the market. Typical markets have 2–5 leveraged positions, so the cascade rarely exceeds 2–3 iterations.
+
+**Pool safety in cascade:**
+
+- **Case A (opposing bet):** Every liquidation in the cascade occurs at pre-bet reserves. Each individual liquidation satisfies `cancel_value ≥ liquidation_threshold`. The pool is guaranteed whole on every iteration. The cascade is purely beneficial for the pool — more positions are caught before they can deteriorate further.
+- **Case B (cancel-bet):** Each successive liquidation may have a larger shortfall (bad debt) because reserves move further from the position's opening price. However, the total bad debt across the entire cascade is bounded by: `∑ shortfalls ≤ n × cancel_value_before × SL% / 100`, where `n` is the number of cascaded positions. This is because each position was at `cancel_value ≥ threshold` before the cascade began, and each liquidation's price impact is bounded by the slippage cap.
+
+**Implementation:** The cascade is implemented as `leverage_cascade_liquidate()` in `module/lazy_pool_helpers.php`. It accepts a `max_iterations` parameter (default 50) as a safety limit. The function is called by:
+- **Cancel-bet handler** (`api.php`, Case B): after the cancel-bet executes, with `reason = 'cancel_bet_liquidated'`
+- **Cron Job 11** (`cron_worker.php`): when force-closing positions near expiration, with `reason = 'force_close'`
+- **Case A (opposing bet)** on VIZ DLT: the `block_pre_apply` hook will implement the cascade loop as part of the plugin
+
+**No upward cascade (short squeeze) is possible:** Liquidating a position returns tokens to the AMM, increasing supply and reducing the token's value. This can only push same-side positions further toward liquidation (downward), never pull them back from it (upward). Opposite-side positions are always improved by the liquidation.
+
+### Virtual Operation: pm_leverage_liquidate
+
+Generated deterministically by the plugin when a leveraged position must be closed:
+
+```
+pm_leverage_liquidate {
+ position_id: uint64,
+ market_id: uint64,
+ cancel_value: uint64, // VIZ returned by reverse CPMM/LMSR
+ pool_received: uint64, // min(cancel_value, L × (1 + R%/100))
+ bettor_received: uint64, // max(0, cancel_value − pool_received)
+ reason: enum { opposing_bet_threshold, cancel_bet_threshold, expiration_approaching }
+}
+```
+
+### Cancel-Bet Liquidation Waterfall
+
+A `pm_cancel_bet` on the **same outcome** as a leveraged position moves the price against that position — returning tokens to the AMM increases supply and reduces the per-token sell value. This is the same directional effect as an opposing bet. Without the post-check, a large cancel-bet could push a leveraged position below its liquidation threshold with no liquidation triggered.
+
+**Principle:** The transaction initiator must never get a worse price because of someone else's leveraged position. The cancel-bet executes FIRST at current reserves. Liquidation happens AFTER.
+
+**Note:** The post-liquidation check now uses a **cascade loop** (see [Liquidation Cascade](#liquidation-cascade-long-squeeze) above). Each liquidation changes reserves, potentially pushing other positions below threshold. The loop re-evaluates all remaining positions after each liquidation until stable.
+
+**Waterfall scenario:**
+
+```
+Market: CPMM binary, reserve_a = 550,000, reserve_b = 450,000
+ (A at ~0.55, B at ~0.45)
+
+Leveraged position on A:
+ collateral = 360 VIZ, loan = 1,800 VIZ (5× boost)
+ cancel_value = 2,100 VIZ, liquidation_threshold = 1,980 VIZ
+ (margin above threshold: 120 VIZ)
+
+User cancels bet on A — returns 500,000 mVIZ worth of A tokens:
+ reserve_a += X_tokens (tokens returned)
+ reserve_b -= VIZ_paid_out
+
+Effect on leveraged position:
+ cancel_value drops from 2,100 → 1,950 VIZ
+ 1,950 < liquidation_threshold (1,980) → POSITION LIQUIDATED
+```
+
+**With Case B (cancel-bet first, then cascade liquidation):**
+
+```
+Block processing:
+ Transaction: pm_cancel_bet(outcome=A)
+ ──────────────────────────────────────
+ Step 1: EXECUTE pm_cancel_bet
+ Cancel-bettor returns A tokens, receives VIZ at CURRENT reserves.
+ Cancel-bettor gets the exact expected payout — not affected by
+ the leveraged position at all.
+ Reserves now: reserve_a' > reserve_a, reserve_b' < reserve_b
+
+ Step 2: CASCADE LOOP
+ Iteration 1:
+ Re-evaluate all leveraged positions on A at CURRENT reserves.
+ Position P1: cancel_value = 1,950 ≤ 1,980 → LIQUIDATE
+ cancel_value = 1,950 VIZ
+ pool_received = 1,950 VIZ (cancel_value < threshold → all to pool)
+ bettor_received = 0
+ shortfall = 1,980 − 1,950 = 30 VIZ (bad debt)
+ lazy_pool.free_balance -= 30 VIZ
+ leverage_fund_used -= 1,800 VIZ (loan returned)
+ → Reserves changed (P1 tokens returned to reserve_a)
+
+ Iteration 2:
+ Re-read market reserves. Re-evaluate remaining positions.
+ No more positions below threshold → CASCADE TERMINATES
+
+ (If there were a P2 near threshold, iteration 2 would catch it too.)
+```
+
+**Bad debt analysis:**
+
+When liquidation happens after the cancel-bet (Case B), `cancel_value` may be below `liquidation_threshold`. The shortfall is covered by the lazy pool's `free_balance`:
+
+```
+shortfall = liquidation_threshold − cancel_value
+pool_received = cancel_value (entire amount to pool)
+bettor_received = 0
+lazy_pool.free_balance -= shortfall (bad debt charge)
+lazy_pool.earned_balance unchanged (no earnings on a loss)
+```
+
+This bad debt is **bounded and rare**:
+- **Bounded:** The position was at `cancel_value ≥ liquidation_threshold` before the cancel-bet. The cancel-bet's price impact is limited by the slippage cap (SL%). So `shortfall ≤ cancel_value_before × SL% / 100`.
+- **Rare:** Only occurs when a position is near liquidation AND a cancel-bet on the same outcome is the trigger. Most positions are liquidated by opposing bets (Case A, pool guaranteed whole).
+- **Acceptable:** The pool earns R% on every position. Occasional bad debt from cancel-bet-triggered liquidations is more than offset by the pool's aggregate earnings. This is the cost of fairness to transaction initiators.
+
+**Why this order (cancel-bet first) is correct:**
+
+The cancel-bettor is an independent market participant exercising a normal right (sell tokens back). They should not be penalized — receive less VIZ — because someone else's leveraged position happens to be near liquidation. The leveraged position's near-threshold state is the bettor's own risk, not the cancel-bettor's problem.
+
+In contrast, for opposing bets (Case A), the opposing bettor actually *benefits* from the pre-liquidation order (gets better price). There is no fairness issue to fix — only a small MEV concern (addressed by price lock on VIZ DLT).
+
+**CPMM vs. LMSR multi-outcome:**
+- **CPMM binary:** Only cancel-bets on the *same* outcome as the leveraged position can push it toward liquidation. Cancel-bets on the opposing outcome *increase* the leveraged position's cancel_value (beneficial).
+- **LMSR multi-outcome:** ANY cancel-bet can affect the leveraged outcome's marginal price, so ALL cancel-bets are processed as Case B (execute first, then check and liquidate).
+
+---
+
+## 6. Liquidation Outcome Distribution
+
+### Case 1: cancel_value ≥ liquidation_threshold (normal case)
+
+```
+pool_obligation = L × (1 + R% / 100) (= liquidation_threshold)
+pool_received = min(cancel_value, pool_obligation)
+bettor_received = cancel_value − pool_received
+
+In this case, cancel_value ≥ pool_obligation, so:
+ pool_received = pool_obligation = loan + full profit
+ bettor_received = cancel_value − pool_obligation (≥ 0)
+```
+
+### Case 2: cancel_value < liquidation_threshold (mathematically impossible during betting period)
+
+```
+This case is prevented by the atomic block-level check (Section 5).
+The position is always liquidated BEFORE the opposing bet that would push
+cancel_value below the threshold. Liquidation executes at pre-bet reserves,
+where cancel_value ≥ liquidation_threshold by construction.
+
+If this case were to occur (e.g., due to a consensus-layer bug):
+ pool_received = min(cancel_value, pool_obligation) = cancel_value
+ bettor_received = 0
+ → This would be a protocol fault, not an economic risk.
+```
+
+---
+
+## 7. Resolution Handling
+
+### The CPMM/LMSR Reality
+
+When a leveraged bet of (C + L) is placed on outcome A:
+1. The amount (C + L) is added to the AMM reserves immediately.
+2. The bettor receives tokens X in return.
+3. At resolution, if A wins, tokens are redeemable. If A loses, tokens are worth 0.
+
+**The bet amount is already in the AMM reserves — it cannot be "reclaimed" from a forfeited pool.** The "priority claim on forfeited stakes" model from earlier drafts was incorrect. In CPMM/LMSR, there is no separate forfeited pool — value is distributed through the token pricing mechanism.
+
+### Correct Model: Co-Investment
+
+The leveraged position is a **co-investment** between the bettor (C) and the pool (L):
+
+```
+┌─ At opening ──────────────────────────────────────────┐
+│ Bettor puts in: C (collateral) │
+│ Pool puts in: L (loan) │
+│ Together: C + L → placed on outcome A via AMM │
+│ Tokens received: X │
+└────────────────────────────────────────────────────────┘
+
+┌─ At resolution — A wins ──────────────────────────────┐
+│ Tokens X are worth: payout = redeem(X, outcome=A) │
+│ │
+│ Pool receives: min(payout, L × (1 + R%/100)) │
+│ (priority claim — pool gets paid 1st)│
+│ Bettor receives: max(0, payout − L × (1 + R%/100)) │
+│ leverage_fund_used -= L │
+│ Position status → resolved_won │
+└────────────────────────────────────────────────────────┘
+
+┌─ At resolution — A loses ─────────────────────────────┐
+│ Tokens X are worth: 0 │
+│ │
+│ Pool receives: 0 (loan L is lost) │
+│ Bettor receives: 0 (collateral C is lost) │
+│ leverage_fund_used -= L │
+│ Position status → resolved_lost │
+│ │
+│ Pool loss = L │
+│ → This is OUTCOME RISK — the pool co-invested on │
+│ the bettor's chosen outcome and lost. │
+└────────────────────────────────────────────────────────┘
+```
+
+### Outcome Risk Mitigation: Force-Close Before Expiration
+
+Outcome risk is mitigated by Cron Job 11 (Section 15), which force-closes **all** leveraged positions before the betting period expires. This converts outcome risk into price-movement risk:
+
+```
+Before expiration:
+ cancel-bet the position → receive cancel_value
+ cancel_value ≥ liquidation_threshold (by atomic check guarantee)
+ Pool receives: L × (1 + R%) (full recovery)
+ Bettor receives: cancel_value − L × (1 + R%)
+
+No outcome risk remains — the pool's price-movement guarantee (structural zero)
+covers every position that is force-closed before resolution.
+```
+
+This is why the expiration buffer is critical: it ensures the pool recovers from every leveraged position via cancel-bet rather than leaving outcome risk open at resolution.
+
+### Market Status Trigger for Force-Close
+
+When a market transitions to a status where new leverage bets are no longer accepted (e.g., `market.status` changes from `active` to `closing`), the protocol blocks new boosted positions **immediately** but does NOT force-close existing positions right away. A grace period protects users from instant loss.
+
+**Market status timeline:**
+
+```
+active → closing → (grace period) → force-close all
+ │ │ │
+ │ │ └─ All remaining positions
+ │ │ force-closed at current price
+ │ │
+ │ └─ New boost positions BLOCKED immediately
+ │ Existing positions continue (can close voluntarily)
+ │ Grace period starts = leverage_expiration_buffer_hours
+ │
+ └─ Normal operation: new boost positions allowed
+```
+
+**Why a grace period?** Without it, a committee member could set `closing` status 10 seconds after a user opens a position. The position would be force-closed immediately at a guaranteed loss (the pool profit charge of L × R%), even though the market didn't move against the bettor at all. This is unfair and would destroy user trust.
+
+**Trigger conditions:**
+
+```
+When market.status changes to 'closing':
+ 1. IMMEDIATELY: block all new pm_leverage_open on this market
+ 2. IMMEDIATELY: notify all users with active positions (frontend + optional push)
+ 3. AFTER grace period: force-close all remaining leveraged positions
+
+Grace period = leverage_expiration_buffer_hours (default: 24 hours)
+ - Same parameter used for time-based expiration buffer
+ - Gives users time to close voluntarily (potentially at a better price)
+ - Gives the market time to move in the bettor's favor
+
+Alternative: force-close when betting_expiration − leverage_expiration_buffer_hours
+ is reached (Cron Job 11), whichever comes FIRST.
+```
+
+**Core principle — pool always recovers, bettor bears their own risk:**
+
+| Position State | Pool | Bettor |
+|----------------|------|--------|
+| In profit (cancel_value > liquidation_threshold by margin) | Receives L × (1 + R%) — full loan + profit | Receives cancel_value − L × (1 + R%) — **keeps profit** |
+| Near liquidation (cancel_value ≈ liquidation_threshold) | Receives L × (1 + R%) — full loan + profit | Receives ≈ 0 — **bears the loss** |
+
+> **Key invariant:** The pool recovers its collateral (loan L) and profit (L × R%) in **every** case. If the position is profitable for the bettor, it's profitable for everyone. If the position is at a loss, only the bettor who placed the bet bears that loss — the pool is never exposed.
+
+**Processing order — after grace period expires:**
+
+```
+1. Find all active leveraged positions on this market (status = 0)
+2. For each position:
+ a. Calculate current cancel_value at market reserves
+ b. Execute force-close:
+ pool_received = min(cancel_value, L × (1 + R%/100))
+ bettor_received = max(0, cancel_value − pool_received)
+ c. lazy_pool.free_balance += pool_received
+ lazy_pool.earned_balance += (pool_received − loan) ← track cumulative earnings
+ lazy_pool.reward_per_share += (pool_received − loan) × 10^9 / total_shares ← profit to LP investors
+ leverage_fund_used -= loan ← free up loan capacity
+ d. Credit bettor_received to bettor's balance
+ e. Position status → closed_voluntary
+3. No leveraged positions remain active on the market
+```
+
+**Why force-close at all (instead of letting profitable positions ride to resolution)?** Allowing positions to survive past the grace period would reintroduce outcome risk for the pool — if the bettor's outcome loses at resolution, the pool loses L. The force-close converts outcome risk into price-movement risk (which is zero by the structural guarantee). The grace period ensures this conversion happens fairly: users get a reasonable window to capture profit or limit losses voluntarily, and the pool is protected from outcome exposure.
+
+**Frontend notification when status → 'closing':**
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ ⚠️ MARKET IS CLOSING │
+│ │
+│ This market is no longer accepting new boosted positions. │
+│ Your boosted position(s) will be auto-closed in: │
+│ │
+│ 23h 45m remaining │
+│ │
+│ You can close voluntarily now to lock in your current │
+│ value, or wait for the market to move in your favor. │
+│ │
+│ [ CLOSE MY POSITION NOW ] [ I'LL WAIT ] │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Expected Profit Model
+
+With force-close before expiration, the pool's expected profit per leveraged position:
+
+```
+If the market moves against the bettor (cancel_value drops toward threshold):
+ → Atomic liquidation or force-close fires
+ → Pool receives L × (1 + R%) ✅ full profit
+
+If the market moves in the bettor's favor:
+ → Force-close at cancel_value > threshold
+ → Pool receives L × (1 + R%) ✅ full profit
+ → Bettor receives cancel_value − L × (1 + R%) (bettor profits too)
+
+In both cases: pool profit = L × R% (guaranteed by force-close + atomic check)
+```
+
+> **Key distinction:** The pool's "structural zero" guarantee applies to **price-movement risk** during the betting period. Outcome risk at resolution is **eliminated** by force-closing all positions before expiration. The only scenario where the pool loses L is if force-close fails (cron malfunction) or a position is somehow still open at resolution.
+
+---
+
+## 8. Protocol Settings
+
+All parameters are committee-governed (delegate median vote on VIZ DLT). Stored in `settings` table in the prototype.
+
+| Key | Default | Unit | Description |
+|-----|---------|------|-------------|
+| `leverage_fund_percent` | 10 | % | Percentage of lazy pool `free_balance` allocated to leverage fund |
+| `max_leverage_per_position_percent` | 0.2 | % | Maximum percentage of **available** leverage fund per single position |
+| `leverage_pool_profit_percent` | 10 | % | Pool's fixed profit on each leveraged loan |
+| `leverage_safety_margin_percent` | 1 | % | Safety buffer applied during position opening |
+| `leverage_max_slippage_percent` | 10 | % | Maximum price impact per bet (protocol-enforced) |
+| `leverage_min_market_liquidity` | 5000 | VIZ | Markets below this liquidity: leverage disabled |
+| `leverage_max_position_ratio` | 5 | % | Maximum leveraged position as % of market liquidity_sum |
+| `leverage_expiration_buffer_hours` | 24 | hours | Leverage disabled N hours before betting_expiration |
+| `lmsr_rounding_margin` | 50 | mVIZ | Rounding safety margin for LMSR Multi Constraint 2 (0 for CPMM). Floor value for dynamic ε |
+| `lmsr_b_scale_factor` | 0.001 | ratio | LMSR Multi — ε = max(lmsr_rounding_margin, b × factor). Covers small-b edge cases |
+| `viz_dlt_m_factor` | 1.0 (prototype) / 0.5 (VIZ DLT) | ratio | **REQUIRED for VIZ DLT.** M_effective = M_max × factor. 1.0 = full M_max (prototype, conservative); 0.5 = 50% M_max (VIZ DLT, relaxed). Without this parameter, leverage is uncompetitively low on most markets (1.1×–1.3× on deep 500K/500K markets). See Constraint 2 Defense-in-depth note |
+| `conversion_profit_cost` | 50 | % | Percentage of bettor's unrealized profit charged as conversion fee when converting boosted position to normal bet. 0% = free conversion; 50% = bettor pays half their unrealized gain |
+
+> **Note:** `lmsr_rounding_margin`, `lmsr_b_scale_factor`, and `viz_dlt_m_factor` are specified in the protocol design but not yet included in the prototype migration (`005_leverage.sql`). They will be added when LMSR leverage support is implemented and when the VIZ DLT migration begins. The prototype uses `viz_dlt_m_factor = 1.0` (full M_max) hardcoded in the constraint calculation.
+
+---
+
+## 9. Database Schema
+
+### New Table: `leveraged_positions`
+
+```sql
+CREATE TABLE `leveraged_positions` (
+ `id` bigint NOT NULL AUTO_INCREMENT,
+ `market` bigint NOT NULL,
+ `user` bigint NOT NULL,
+ `outcome_index` tinyint NOT NULL DEFAULT 0 COMMENT '0=A, 1=B, or index for multi',
+ `collateral` bigint NOT NULL COMMENT 'bettor contribution, milli-VIZ',
+ `loan` bigint NOT NULL COMMENT 'pool loan, milli-VIZ',
+ `total_bet` bigint NOT NULL COMMENT 'collateral + loan, milli-VIZ',
+ `tokens` bigint NOT NULL COMMENT 'weight/tokens received from AMM',
+ `bet_id` bigint NOT NULL COMMENT 'FK to bets table',
+ `pool_profit` bigint NOT NULL COMMENT 'loan × R% / 100, milli-VIZ',
+ `liquidation_threshold` bigint NOT NULL COMMENT 'loan + pool_profit, milli-VIZ',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '0=active, 1=liquidated, 2=resolved_won, 3=resolved_lost, 4=closed_voluntary, 5=converted',
+ `liquidated_at` int NOT NULL DEFAULT 0 COMMENT 'unix timestamp',
+ `liquidated_by_bet` bigint NOT NULL DEFAULT 0 COMMENT 'bet_id that triggered liquidation',
+ `cancel_value_at_liquidation` bigint NOT NULL DEFAULT 0,
+ `pool_received` bigint NOT NULL DEFAULT 0,
+ `bettor_received` bigint NOT NULL DEFAULT 0,
+ `time` int NOT NULL,
+ `update` int NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `market_status` (`market`,`status`),
+ KEY `user_status` (`user`,`status`),
+ KEY `bet_id` (`bet_id`)
+);
+```
+
+### New Columns on `lazy_pool`
+
+```sql
+ALTER TABLE `lazy_pool`
+ ADD COLUMN `leverage_fund_used` bigint NOT NULL DEFAULT 0 COMMENT 'Total active leveraged loans',
+ ADD COLUMN `earned_balance` bigint NOT NULL DEFAULT 0 COMMENT 'Cumulative lifetime earnings (LP profit + leverage profit + penalties). Monotonically non-decreasing';
+```
+
+> **Note:** `earned_balance` is also incremented by LP profits (market resolution) and emergency withdrawal penalties, not just leverage profits. It tracks the pool's **cumulative lifetime earnings** from all sources. See [Lazy Pool Properties](../../docs/lazy-pool-properties.md) for complete state transition documentation.
+
+### New History Types
+
+> **Conflict check:** Existing types in the codebase are 0–20 (bet_place, bet_cancel, liquidity_add/withdraw, fees, penalties, disputes). Types 30–34 have no overlap.
+
+| Type | Name | Description |
+|------|------|-------------|
+| 30 | `leverage_open` | Leveraged position opened |
+| 31 | `leverage_liquidate` | Position force-closed by protocol |
+| 32 | `leverage_resolve_win` | Position closed at resolution (bettor won) |
+| 33 | `leverage_resolve_loss` | Position closed at resolution (bettor lost) |
+| 34 | `leverage_convert` | Leveraged position converted to normal bet |
+
+> **Implementation note:** In the current prototype, the `leverage_force_close_position()` function uses type 30 for both voluntary close and force-close events (rather than a dedicated close type). This should be updated to use type 31 (liquidate) for all close events, or a new type should be added for voluntary close. The `leverage-open` API correctly uses type 30, and the `leverage-convert` API correctly uses type 34. Types 32 and 33 will be used when leverage resolution handling is implemented.
+
+### Migration
+
+Database changes should be packaged as `migrations/005_leverage.sql` following the existing migration convention (001–004).
+
+---
+
+## 10. API Endpoints
+
+| Endpoint | Method | Description |
+|----------|--------|-------------|
+| `leverage-preview` | POST | Pre-calculate leverage options for a market. Returns constraints and slider data |
+| `leverage-open` | POST | Open a leveraged position on a market |
+| `leverage-close` | POST | Bettor voluntarily closes their leveraged position (early exit) |
+| `leverage-close-preview` | POST | Pre-calculate the close result: what bettor receives, what pool receives, loss breakdown |
+| `leverage-convert-preview` | POST | Pre-calculate the convert result: total payment, conversion fee, comparison with voluntary close |
+| `leverage-convert` | POST | Convert leveraged position to normal bet (bettor pays buyout from balance) |
+| `leverage-info` | POST | Get all active leveraged positions for current user |
+| `leverage-pool-state` | POST | Get leverage fund state (available, used, total) |
+
+### `leverage-preview` — Request
+
+```json
+{
+ "method": "leverage-preview",
+ "market_id": 42,
+ "collateral": 360000,
+ "outcome_index": 0
+}
+```
+
+### `leverage-preview` — Response
+
+```json
+{
+ "ok": true,
+ "market_liquidity": 5000000,
+ "slippage_cap_viz": 500,
+ "leverage_fund_total": 1000000000,
+ "leverage_fund_available": 900000000,
+ "max_loan_from_fund": 1800000,
+ "max_loan_from_market": 2250000,
+ "max_loan_from_size": 2140000,
+ "max_loan": 1800000,
+ "max_leverage": 5.0,
+ "pool_profit_percent": 10,
+ "safety_margin_percent": 1,
+ "recommended_slippage_percent": 2,
+ "danger_zone_leverage": 4.3,
+ "auto_close_date": "2025-03-14T18:00:00Z",
+ "unavailable_reason": null,
+ "failed_constraints": [],
+ "slider_stops": [
+ {
+ "leverage": 1.0,
+ "loan": 0,
+ "total_bet": 360000,
+ "pool_profit": 0,
+ "liquidation_cancel_value": 0,
+ "current_cancel_value": 359500,
+ "worst_case_cancel_value": 340000,
+ "expected_tokens": 359800,
+ "min_tokens_at_2pct": 352604,
+ "safe": true
+ },
+ {
+ "leverage": 2.0,
+ "loan": 360000,
+ "total_bet": 720000,
+ "pool_profit": 36000,
+ "liquidation_cancel_value": 396000,
+ "current_cancel_value": 718500,
+ "worst_case_cancel_value": 560000,
+ "expected_tokens": 718200,
+ "min_tokens_at_2pct": 703836,
+ "safe": true
+ },
+ {
+ "leverage": 5.0,
+ "loan": 1800000,
+ "total_bet": 2160000,
+ "pool_profit": 180000,
+ "liquidation_cancel_value": 1980000,
+ "current_cancel_value": 2155000,
+ "worst_case_cancel_value": 1990000,
+ "expected_tokens": 2150300,
+ "min_tokens_at_2pct": 2107294,
+ "safe": true
+ }
+ ]
+}
+```
+
+### `leverage-preview` — Response (unavailable)
+
+When leverage is unavailable, `max_leverage` is < 1.1, `slider_stops` is empty, and `unavailable_reason` + `failed_constraints` explain why:
+
+```json
+{
+ "ok": true,
+ "market_liquidity": 1001000,
+ "slippage_cap_viz": 1,
+ "leverage_fund_total": 1000000000,
+ "leverage_fund_available": 900000000,
+ "max_loan_from_fund": 1800000,
+ "max_loan_from_market": 0,
+ "max_loan_from_size": 14050,
+ "max_loan": 0,
+ "max_leverage": 0.0,
+ "pool_profit_percent": 10,
+ "safety_margin_percent": 1,
+ "recommended_slippage_percent": 2,
+ "unavailable_reason": "Opposing side too thin for leveraged positions. Maximum safe opposing bet (M_max) = 1 VIZ is insufficient — any loan would fail the worst-case safety check.",
+ "failed_constraints": [
+ {
+ "constraint": 2,
+ "name": "market_liquidity",
+ "detail": "M_max = 1 VIZ (reserve_b = 1 VIZ × 10% slippage cap). cancel_value_worst = 0 for any loan > 0. Even the smallest leveraged position cannot survive a maximum opposing bet."
+ }
+ ],
+ "slider_stops": []
+}
+```
+
+**`failed_constraints` values:**
+
+| constraint | name | When it fails | Typical `unavailable_reason` |
+|------------|------|---------------|----------------------------|
+| 2 | `market_liquidity` | `cancel_value_worst(M_max) < L × (1+R%) × (1+S%)` for all L > 0 | "Opposing side too thin for leveraged positions" |
+| 3 | `position_size` | `collateral alone exceeds max position size` | "Your collateral exceeds the maximum position size for this market" |
+| 4 | `min_market_liquidity` | `liquidity_sum < leverage_min_market_liquidity` | "Market liquidity below minimum threshold (5,000 VIZ)" |
+| 1 | `fund_availability` | `leverage_fund_available × P% / 100 < minimum loan` | "Leverage fund is fully utilized. Try again later." |
+
+> **Note:** Constraint 2 is the most common failure on skewed markets. When `min(reserve_a, reserve_b)` is very small, M_max approaches zero, making it impossible for any leveraged position to survive the worst-case check. This is **correct** behavior — it means the market is too thin on one side for safe leverage — but the user needs to understand *why*.
+
+```json
+{
+ "method": "leverage-open",
+ "market_id": 42,
+ "outcome_index": 0,
+ "collateral": 360000,
+ "leverage": 5.0,
+ "max_slippage_percent": 2
+}
+```
+
+The backend computes `loan = collateral × (leverage − 1)` and `min_tokens = expected_tokens × (1 − max_slippage_percent / 100)` from the current market reserves. Both are passed to the on-chain `pm_leverage_open` operation.
+
+### `leverage-close-preview` — Response
+
+```json
+{
+ "ok": true,
+ "position_id": 7,
+ "cancel_value": 2050000,
+ "pool_obligation": 1980000,
+ "pool_receives": 1980000,
+ "bettor_receives": 70000,
+ "collateral": 360000,
+ "loan": 1800000,
+ "loss_vs_collateral": 290000,
+ "loss_breakdown": {
+ "market_movement_loss": 110000,
+ "pool_profit_charge": 180000,
+ "total_loss": 290000,
+ "total_loss_pct": 80.6
+ }
+}
+```
+
+---
+
+## 11. Frontend: Pre-Calculation, Slider & Cancel Warning
+
+### Data Flow
+
+```
+┌─ Frontend Loads ────────────────────────────────────────────┐
+│ │
+│ 1. Fetch market reserves (load-markets / load-market) │
+│ 2. Fetch leverage-pool-state │
+│ 3. User enters collateral amount │
+│ 4. Call leverage-preview(collateral, market_id, outcome) │
+│ 5. Render slider from response │
+│ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Slider UI Component
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ 🚀 BOOST YOUR BET │
+│ │
+│ Your collateral: 360.000 VIZ │
+│ │
+│ Boost: [1.0×]─────────●──────────[5.0×] │
+│ ▲ ▲ │
+│ no boost max avail │
+│ │
+│ Selected: 3.5× │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ Pool loan: 900.000 VIZ (interest: 90.000 VIZ) │ │
+│ │ Total bet: 1260.000 VIZ │ │
+│ │ │ │
+│ │ Current position value: 1255.000 VIZ │ │
+│ │ Auto-close at: 990.000 VIZ ←───────── │ │
+│ │ │ │
+│ │ ⚠️ If market moves against you, your boosted │ │
+│ │ position will auto-close at 990.000 VIZ. │ │
+│ │ You will lose your 360.000 VIZ collateral. │ │
+│ │ The pool is guaranteed to recover its loan. │ │
+│ │ │ │
+│ │ ℹ️ Boost is a bet on PRICE MOVEMENT (social belief │ │
+│ │ shift), not on the outcome result. Your position │ │
+│ │ will be auto-closed on 2025-03-14 at 18:00 UTC, │ │
+│ │ 24h before market resolution. You profit from │ │
+│ │ market price changes, not from the final outcome. │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ Slippage tolerance: [0.5%] [1%] [2%] [3%] ← auto: 2% │
+│ │
+│ ☐ I understand: this position will be auto-closed │
+│ on 2025-03-14 at 18:00 UTC (24h before resolution). │
+│ This is a bet on price movement, not on the outcome. │
+│ │
+│ [ OPEN BOOSTED POSITION ] ← disabled until checkbox ✓ │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Cancel-Bet Warning Dialog
+
+When a bettor clicks "Cancel bet" on a leveraged position, the frontend calls `leverage-close-preview` and shows a detailed warning:
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ ⚠️ CLOSE BOOSTED POSITION │
+│ │
+│ You are about to close your 3.5× boosted position. │
+│ │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ Position value (cancel): 2,050.000 VIZ │ │
+│ │ │ │
+│ │ Pool receives: 1,980.000 VIZ │ │
+│ │ ├─ Loan repayment: 1,800.000 VIZ │ │
+│ │ └─ Pool profit (10%): 180.000 VIZ │ │
+│ │ │ │
+│ │ You receive: 70.000 VIZ │ │
+│ │ │ │
+│ │ ─────────────────────────────────────────────── │ │
+│ │ Your collateral: 360.000 VIZ │ │
+│ │ Your loss: −290.000 VIZ (−80.6%) │ │
+│ │ │ │
+│ │ Loss breakdown: │ │
+│ │ Market moved against you: −110 VIZ │ │
+│ │ Pool profit charge: −180 VIZ │ │
+│ │ │ │
+│ │ ⚠️ With 1× (no boost), your loss would be │ │
+│ │ only −110 VIZ. Boost increased your loss │ │
+│ │ by 180 VIZ (the pool's profit charge). │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ [ CANCEL ] [ CONFIRM CLOSE ] │
+└─────────────────────────────────────────────────────────────┘
+```
+
+**Key UX requirement:** The dialog must clearly show:
+1. **How much the bettor receives** vs. their original collateral
+2. **The pool's profit charge** — this is the extra loss from leverage (would not exist at 1×)
+3. **Comparison with 1× loss** — "Without leverage, you'd only lose X"
+4. **Loss as percentage** of collateral
+5. **Auto-close notice** — the specific auto-close date/time must appear in **four places**:
+ - **`leverage-preview` response** — `auto_close_date` field (ISO 8601). Frontend uses this for all displays below.
+ - **Open position dialog** — before the user can submit, they must acknowledge with a checkbox: *"I understand: this position will be auto-closed on [date] at [time] UTC, 24h before resolution. This is a bet on price movement, not on the outcome."* The **OPEN BOOSTED POSITION** button is disabled until the checkbox is checked.
+ - **`leverage-info` (active positions list)** — each position card shows: *"Auto-closes: [date] at [time] UTC"* with a countdown timer.
+ - **Market card** (market list / market detail) — if the user has active boosted positions on this market, the card shows a prominent *"⚠️ Your boost auto-closes in [countdown]"* banner. This is the most visible touchpoint — users see it when browsing markets, not only when opening the boost dialog.
+
+ The date is computed as `market.betting_expiration − leverage_expiration_buffer_hours`.
+
+### Slider Behavior
+
+- **Discrete stops** at 0.01× intervals from 1.00× to max_leverage (e.g., 1.00, 1.01, 1.02, ..., 2.50).
+- Each stop pre-calculated by the backend in `leverage-preview`.
+- **Performance note:** The binary search in Constraint 2 runs only **once** to find `leverage_max`. Each slider stop is a **direct formula evaluation** (O(1) arithmetic for CPMM, or LMSR computation) — not a separate binary search. For max_leverage = 5.0× at 0.01× steps, that's 400 direct evaluations plus 1 binary search, not 400 binary searches. A single `leverage-preview` call completes in <50ms.
+- **Implementation:** The current prototype generates **all** 0.01× stops server-side in `leverage_compute_max_leverage()` and returns them as a dense array in the `slider_stops` response. The frontend iterates over this array for the slider UI. A future optimization may switch to sparse stops with client-side interpolation for reduced response size, but the dense approach keeps the frontend logic simpler.
+- **Danger zone:** The response includes `danger_zone_leverage` — the exact leverage value where `current_cancel_value` first comes within 5% of `liquidation_threshold`. This is the safe→unsafe boundary. The frontend colors the slider **red** for all leverage values ≥ `danger_zone_leverage`. If `danger_zone_leverage` is not present (or > max_leverage), no red zone exists on this market.
+- **"Boost unavailable"** state: `max_leverage < 1.1` → slider disabled, `unavailable_reason` displayed to the user with a clear explanation of which constraint failed and why. Example: *"Boost unavailable: opposing side too thin (reserve = 1 VIZ). Maximum safe opposing bet = 0.1 VIZ, insufficient for any leveraged position."*
+- **Multiple constraints** can fail simultaneously. The frontend shows the **most relevant** reason (highest priority: min_market_liquidity → market_liquidity → fund_availability → position_size). The full `failed_constraints` array is available for a "show details" expandable section.
+
+### Safety Margin Display
+
+The 1% safety margin is applied **server-side** during position opening. The user sees the **real** liquidation threshold. The frontend adds a subtle note:
+
+> ℹ️ A 1% safety buffer is included during position opening to protect against simultaneous requests.
+
+---
+
+## 12. Protocol Operations (VIZ DLT)
+
+### Dedicated Operations (consensus-validated)
+
+| Operation | Fields | Description |
+|-----------|--------|-------------|
+| `pm_leverage_open` | `market`, `outcome_index`, `collateral`, `loan`, `min_tokens`, `max_slippage_percent` | Open leveraged position. Protocol validates: collateral ≥ 0, loan within limits, position passes worst-case safety check, actual slippage ≤ max_slippage_percent |
+| `pm_leverage_close` | `position_id`, `min_return` | Bettor voluntarily closes position early. **Rejected if cancel_value < liquidation_threshold** |
+| `pm_leverage_convert` | `position_id`, `conversion_profit_cost` | Convert leveraged position to normal bet. Bettor pays `L × (1+R%) + current_profit × conversion_profit_cost%` from balance. **Rejected if cancel_value < liquidation_threshold or current_profit ≤ 0** |
+| `pm_leverage_liquidate` | *virtual* | Generated by protocol when opposing bet triggers liquidation threshold. See [Section 5](#atomic-liquidation-at-block-level) |
+| `pm_leverage_resolve` | *virtual* | Generated at market resolution. See [Section 7](#resolution-handling) |
+
+### Voluntary Close (`pm_leverage_close`)
+
+A bettor may voluntarily close their leveraged position only when `cancel_value ≥ liquidation_threshold`. This preserves the pool's structural zero guarantee for price-movement risk.
+
+```
+cancel_value = current cancel value of the position
+pool_obligation = L × (1 + R%/100) (= liquidation_threshold)
+
+REJECTED if cancel_value < pool_obligation:
+ → Structural impossibility on VIZ DLT. This condition NEVER occurs:
+ if any opposing bet pushed cancel_value below the threshold,
+ the position would have already been atomically liquidated
+ BEFORE that bet was processed (Section 5) — this is guaranteed
+ at the block level by the VIZ DLT delegate.
+ → By the time a voluntary close request reaches consensus,
+ the position either exists with cancel_value ≥ threshold,
+ or it was already liquidated (close fails with "position
+ no longer active").
+ → The rejection guard exists for the prototype (non-atomic PHP)
+ where race conditions are possible. On VIZ DLT it is redundant.
+
+ACCEPTED if cancel_value ≥ pool_obligation: (the only possible path on VIZ DLT)
+ pool_received = pool_obligation (pool gets loan + profit)
+ bettor_received = cancel_value − pool_obligation (≥ 0)
+ leverage_fund_used -= L
+ status → closed_voluntary
+
+Race with Cron Job 11 (prototype):
+ If the cron job force-closes the position between the user's
+ cancel_value read and the status UPDATE, the user's UPDATE
+ returns affected_rows = 0. Instead of showing an error:
+
+ IF affected_rows = 0:
+ re-read position status
+ IF status IN (4, 1, 2, 3): (closed_voluntary, liquidated, resolved_won, resolved_lost)
+ return SUCCESS with actual bettor_received from the position record
+ (the cron job or liquidation already credited the bettor)
+ ELSE:
+ return ERROR "position not found"
+ END IF
+
+ The user should NEVER see an error for a valid close request —
+ only a confirmation with the actual payout amount (which may
+ differ slightly from the preview if the market moved between
+ the preview and the actual close).
+```
+
+> **Why keep the guard in the spec?** On VIZ DLT, `cancel_value < liquidation_threshold` is **structurally impossible** for active positions — the atomic block-level check (Section 5) guarantees this when each new block is produced. If an opposing bet would push cancel_value below the threshold, the position is liquidated *first* at pre-bet reserves. So the only two outcomes for a `pm_leverage_close` request on VIZ DLT are: **succeed** (cancel_value ≥ threshold) or **fail with "position no longer active"** (already liquidated by an earlier transaction in this or a previous block). The guard is retained for the prototype (PHP + MySQL, no atomic execution) and as a correctness assertion on VIZ DLT.
+
+### Validation Rules (pm_leverage_open)
+
+The operation is **rejected at consensus level** if any of the following fail:
+
+```
+1. market.status == 1 (active only — 'closing' and 'closed' are rejected)
+2. current_time < market.betting_expiration - leverage_expiration_buffer_hours × 3600
+3. market.liquidity_sum ≥ leverage_min_market_liquidity
+4. collateral ≥ 0 AND collateral ≤ user.balance
+5. loan ≤ leverage_fund_available × P% / 100
+6. loan / collateral ≤ leverage_max_market (from constraint system)
+7. total_bet ≤ market.liquidity_sum × leverage_max_position_ratio / 100
+8. cancel_value_worst(total_bet) ≥ loan × (1 + R%/100) × (1 + S%/100) + ε
+ where ε = 0 for CPMM
+ ε = max(lmsr_rounding_margin, b × 0.001) for LMSR Multi
+ (dynamic: accounts for small-b markets where rounding scales with b)
+9. actual_slippage ≤ max_slippage_percent
+```
+
+Rule 8 is the **hard safety guarantee** (pool protection). If it fails, the operation is rejected — the position cannot be opened.
+
+Rule 9 is the **front-running protection** (bettor protection). This is a user-specified slippage tolerance — standard AMM practice.
+
+### Front-Running Protection (MEV) for Position Opening
+
+When a large `pm_leverage_open` transaction appears in the mempool, an attacker can attempt a **sandwich attack**:
+
+```
+1. Attacker sees pm_leverage_open(total_bet = 2160 VIZ on A) in mempool
+2. Attacker inserts pm_place_bet(B, 500 VIZ) BEFORE the open tx (front-run)
+ → shifts price against A, reducing tokens the leveraged bettor receives
+3. Attacker inserts pm_place_bet(A, 500 VIZ) AFTER the open tx (back-run)
+ → shifts price back toward A, profiting from the round-trip
+
+Net result: the leveraged bettor gets fewer tokens than expected (worse price).
+This is NOT a threat to the pool (the pool's structural zero is unaffected),
+but it IS a threat to the user.
+```
+
+**Mitigation: `max_slippage_percent` parameter**
+
+The user specifies the maximum acceptable price impact when opening. The protocol calculates the actual slippage at execution time and rejects the operation if it exceeds the limit:
+
+```
+expected_tokens = tokens calculated at current reserves (at submission time)
+actual_tokens = tokens calculated at reserves at execution time (including
+ any front-running bets processed earlier in the block)
+
+slippage_pct = (expected_tokens - actual_tokens) / expected_tokens × 100
+
+if slippage_pct > max_slippage_percent:
+ REJECT — "Price moved too much. Try again with higher slippage tolerance
+ or wait for market conditions to stabilize."
+```
+
+**Recommended defaults:**
+
+| Market State | Recommended `max_slippage_percent` | Rationale |
+|--------------|--------------------------------------|----------|
+| Normal liquidity | 1% | Small positions on deep markets rarely see >1% slippage |
+| Medium liquidity | 2% | Standard AMM practice for moderate-sized positions |
+| Low liquidity / high leverage | 3-5% | Large positions relative to market depth cause more slippage |
+
+> **Why not use `min_tokens`?** The operation already has a `min_tokens` field (minimum acceptable tokens received). `max_slippage_percent` is a more user-friendly interface — the user thinks in percentages ("I accept up to 2% worse price") rather than absolute token counts. The frontend converts `max_slippage_percent` to `min_tokens` internally using the current market reserves from `leverage-preview`.
+>
+> Both parameters are included in the on-chain operation for compatibility: `min_tokens` is the consensus-level check, `max_slippage_percent` is the user-facing parameter. The frontend sets `min_tokens = expected_tokens × (1 - max_slippage_percent / 100)`.
+
+---
+
+## 13. Numerical Examples
+
+### Example 1: Deep Market, Leverage Capped by Market Depth
+
+> This example shows that even with ample leverage fund capacity (5× available), market depth can limit the effective leverage to 2.5×. The 5× target fails the safety check.
+
+```
+Market state:
+ reserve_a = 500,000,000 (500,000 VIZ in milli-VIZ)
+ reserve_b = 500,000,000 (500,000 VIZ in milli-VIZ)
+ k = 250,000,000,000,000,000 (reserve_a × reserve_b)
+ liquidity_sum = 1,000,000 VIZ
+ SL% = 10% → M_max = 50,000 VIZ (50,000,000 mVIZ)
+
+Lazy pool:
+ free_balance = 10,000,000 VIZ
+ F% = 10% → leverage_fund_total = 1,000,000 VIZ
+ leverage_fund_used = 100,000 VIZ
+ leverage_fund_available = 900,000 VIZ
+ P% = 0.2% → max_loan_per_position = 1,800 VIZ
+
+Bettor: collateral = 360 VIZ
+
+Constraint 1 (fund): L_max = 1,800 VIZ → leverage = 1800/360 = 5.0×
+Constraint 3 (size): 1,000,000 × 5% = 50,000 VIZ > 2160 → no limit
+Constraint 2 (market): Binary search for L...
+
+ Try L = 1800 VIZ, total_bet = 2160 VIZ (2,160,000 mVIZ):
+
+ Tokens received (bet 2160 VIZ on A):
+ reserve_b' = 500,000,000 + 2,160,000 = 502,160,000
+ reserve_a' = 250,000,000,000,000,000 / 502,160,000 ≈ 497,849,700
+ X = 500,000,000 − 497,849,700 ≈ 2,150,300 tokens
+
+ Current cancel_value:
+ reserve_a_c = 497,849,700 + 2,150,300 = 500,000,000
+ reserve_b_c = 250,000,000,000,000,000 / 500,000,000 = 500,000,000
+ cancel_value = 502,160,000 − 500,000,000 = 2,160,000 mVIZ = 2160 VIZ
+
+ Worst-case cancel_value (M_max = 50,000 VIZ = 50,000,000 mVIZ on B):
+ cancel_worst = k × X / ((reserve_a' + M_max) × (reserve_a' + M_max + X))
+ ≈ 2.5e17 × 2,150,300 / ((497,849,700 + 50,000,000) × (497,849,700 + 50,000,000 + 2,150,300))
+ ≈ 5.376e23 / (547,849,700 × 550,000,000)
+ ≈ 5.376e23 / 3.013e17
+ ≈ 1,784,000 mVIZ = 1784 VIZ
+
+ Safety check:
+ liquidation_threshold = 1800 × 1.10 = 1980 VIZ
+ safe_threshold = 1980 × 1.01 = 1999.8 VIZ
+ cancel_worst = 1784 VIZ < 1999.8 VIZ ❌ FAIL
+
+ → Leverage 5.0× NOT SAFE at this market depth. Reduce.
+
+ Try L = 900 VIZ, total_bet = 1260 VIZ:
+
+ X ≈ 1,258,000 tokens (similar calculation)
+ cancel_worst ≈ 1245 VIZ
+ liquidation_threshold = 900 × 1.10 = 990 VIZ
+ safe_threshold = 990 × 1.01 = 999.9 VIZ
+ cancel_worst = 1245 > 999.9 ✅ PASS
+
+ → leverage_max_market = 900/360 = 2.5×
+
+Constraint 4: liquidity_sum = 1,000,000 ≥ 5,000 ✅
+
+FINAL: leverage_max = min(5.0, 2.5, unlimited) = 2.5×
+```
+
+### Example 2: Thin Market — Leverage Unavailable
+
+```
+Market state:
+ reserve_a = 100,000 (100 VIZ)
+ reserve_b = 100,000 (100 VIZ)
+ liquidity_sum = 200 VIZ
+
+Constraint 4 check:
+ 200 < 5000 → leverage_unavailable = true
+
+Response: "Leverage is not available for this market.
+ Minimum required liquidity: 5,000 VIZ."
+```
+
+### Example 3: Leverage Fund Limit Hit
+
+```
+leverage_fund_available = 500 VIZ
+P% = 0.2% → max_loan_per_position = 1 VIZ
+
+Bettor collateral = 100 VIZ
+max_leverage = 1 VIZ / 100 VIZ = 0.01× → unavailable
+
+Response: "Leverage is temporarily unavailable.
+ The leverage fund is nearly fully utilized.
+ Try again later or with a smaller position."
+```
+
+### Example 4: Cancel Bet with Profit for User
+
+This example shows a leveraged position where the market moved in the bettor's favor. When the market status changes to "closing", the position is force-closed and the bettor receives a profit.
+
+```
+Market state (deep, same as Example 1):
+ reserve_a = 500,000,000 mVIZ (500,000 VIZ)
+ reserve_b = 500,000,000 mVIZ (500,000 VIZ)
+ k = 250,000,000,000,000,000
+
+Bettor opens 5× leveraged position on outcome A:
+ collateral = 360 VIZ = 360,000 mVIZ
+ loan = 1,800 VIZ = 1,800,000 mVIZ
+ total_bet = 2,160 VIZ = 2,160,000 mVIZ
+ R% = 10% → pool_profit = 180 VIZ
+ liquidation_threshold = 1,980 VIZ
+
+After placing bet on A:
+ reserve_b' = 500,000,000 + 2,160,000 = 502,160,000
+ reserve_a' ≈ 497,849,700
+ X ≈ 2,150,300 tokens
+ cancel_value at open ≈ 2,160 VIZ (≈ total bet, as expected)
+
+── Market moves in bettor's favor ──
+50,000 VIZ (50,000,000 mVIZ) in additional bets placed on outcome A
+by other users. This shifts the market toward outcome A, increasing
+the value of the bettor's tokens:
+
+ reserve_b'' = 502,160,000 + 50,000,000 = 552,160,000
+ reserve_a'' = floor(k / 552,160,000) ≈ 452,813,305
+
+── Market status → "closing" ──
+Force-close triggered for all leveraged positions.
+
+Cancel value at current reserves (return X = 2,150,300 tokens):
+ reserve_a_c = 452,813,305 + 2,150,300 = 454,963,605
+ reserve_b_c = floor(k / 454,963,605) ≈ 549,525,305
+ cancel_value = 552,160,000 − 549,525,305 = 2,634,695 mVIZ ≈ 2,635 VIZ
+
+Distribution:
+ cancel_value = 2,635 VIZ
+ liquidation_threshold = 1,980 VIZ (= L × 1.10)
+
+ Pool receives: 1,980 VIZ (1,800 loan + 180 profit)
+ Bettor receives: 2,635 − 1,980 = 655 VIZ
+
+ Original collateral: 360 VIZ
+ Net profit: 655 − 360 = +295 VIZ (+81.9% on collateral)
+
+┌─ Close Summary ──────────────────────────────────────────┐
+│ │
+│ Position value (cancel): 2,635.000 VIZ │
+│ │
+│ Pool receives: 1,980.000 VIZ │
+│ ├─ Loan repayment: 1,800.000 VIZ │
+│ └─ Pool profit (10%): 180.000 VIZ │
+│ │
+│ You receive: 655.000 VIZ │
+│ │
+│ ───────────────────────────────────────────────────── │
+│ Your collateral: 360.000 VIZ │
+│ Your profit: +295.000 VIZ (+81.9%) │
+│ │
+│ ✅ Market moved in your favor. │
+│ After pool charges (180 VIZ), you still profit. │
+│ │
+└──────────────────────────────────────────────────────────┘
+
+Compare with the loss case (Section 11 — Cancel Warning Dialog):
+ In that example, the market moved AGAINST the bettor.
+ cancel_value = 2,050 VIZ → bettor receives 70 VIZ (loss of 290 VIZ).
+
+The key difference: market direction determines the bettor's outcome,
+but the pool ALWAYS recovers L × (1 + R%) regardless of direction.
+
+If the position is in profit → it is profit for everyone:
+ Pool gets loan + profit, bettor keeps the surplus.
+If the position is at a loss → only the bettor bears it:
+ Pool gets loan + profit, bettor loses part or all of collateral.
+```
+
+---
+
+## 14. Risk Analysis
+
+### Eliminated Risks
+
+| Risk | How Eliminated |
+|------|---------------|
+| **Gap risk** (price jumps past liquidation threshold) | Atomic block-level check: liquidation executes *before* the triggering bet. Cancel value is taken at pre-bet reserves |
+| **Pool principal loss (price movement)** | Position cannot be opened unless `cancel_value_worst ≥ loan × (1+R%) × (1+S%)`. Voluntary close rejected if cancel_value < threshold. Force-close before expiration converts outcome risk to price risk |
+| **Fund overshoot** | Per-position limit (`P%`) bounds any single loan. Total fund cap (`F%`) bounds aggregate exposure |
+| **Thin market exploitation** | `leverage_min_market_liquidity` blocks leverage on markets below threshold |
+| **Last-minute manipulation** | `leverage_expiration_buffer_hours` blocks new leveraged positions near expiration |
+| **Outcome risk (normal operation)** | Cron Job 11 force-closes ALL leveraged positions before expiration, converting outcome risk into price-movement risk (which is zero) |
+
+### Why Residual Risks Don't Exist (Price-Movement)
+
+**Multiple opposing bets in one block — impossible by design:**
+
+On VIZ DLT, the delegate processes transactions sequentially within the block they sign. The atomic liquidation check fires *before each opposing bet individually*:
+
+```
+Block N:
+ Tx 1: pm_place_bet(B, 40 VIZ) → check → cancel_after > threshold ✅ → execute bet
+ Tx 2: pm_place_bet(B, 50 VIZ) → check → cancel_after ≤ threshold → LIQUIDATE FIRST at current reserves → execute bet
+```
+
+After Tx 1 executes, Tx 2's check runs against the NEW reserves (post-Tx 1). If Tx 2 would push the position below threshold, liquidation fires *before* Tx 2 — at reserves that already include Tx 1's impact, but STILL above the liquidation threshold (because Tx 1 passed its own check). The position is never liquidated at a disadvantageous price.
+
+**Integer rounding (floor dust):**
+
+**CPMM (binary markets):** `floor()` operations discard <1 mVIZ per operation. A full lifecycle (open → liquidate) involves ~5 floor calls, losing at most ~5 mVIZ = 0.005 VIZ. On a 100 VIZ loan with 10% profit (10 VIZ), this is 0.05% of profit. Negligible. The pool earns 9.995% instead of 10% — acceptable.
+
+**LMSR Multi — larger rounding error:** The LMSR calculation involves significantly more floating-point→integer conversions:
+
+```
+Sources of rounding error in LMSR:
+ 1. exp(q_i / b) computation: each call loses ≤1 mVIZ
+ 2. C(q) = b × ln(Σ exp(q_j / b)): floor after ln, floor after × b
+ 3. Binary search in tokens_for_amount(): stops at ±1 token precision
+ 4. sell_return(Δ, i) = C(q) − C(q − Δ·e_i): two C() calls, each with own rounding
+
+Per-operation error: ~2-5 mVIZ (vs. ~1 mVIZ for CPMM)
+Full lifecycle: ~10-25 mVIZ = 0.010-0.025 VIZ
+Worst case (10 outcomes, small b): up to ~50 mVIZ = 0.050 VIZ
+```
+
+On a 100 VIZ loan with 10% profit (10 VIZ), the worst case of 0.050 VIZ is 0.5% of profit. Still acceptable for the pool — but this error can affect **Constraint 2** on thin markets where cancel_value_worst is close to the threshold.
+
+**Mitigation: rounding margin ε in Constraint 2 for LMSR**
+
+For LMSR Multi markets, an additional rounding margin is added to Constraint 2:
+
+```
+For CPMM:
+ cancel_value_worst(L) ≥ L × (1 + R%/100) × (1 + S%/100)
+ (no rounding margin needed — error is negligible)
+
+For LMSR Multi:
+ cancel_value_worst(L) ≥ L × (1 + R%/100) × (1 + S%/100) + ε
+
+ where ε = max(lmsr_rounding_margin, b × 0.001)
+
+ Dynamic rationale:
+ lmsr_rounding_margin = 50 mVIZ (default) covers typical markets
+ b × 0.001 covers edge cases where b is small and rounding scales:
+ b = 100 VIZ → b × 0.001 = 100 mVIZ > 50 mVIZ → ε = 100 mVIZ
+ b = 10,000 VIZ → b × 0.001 = 10,000 mVIZ = 10 VIZ > 50 mVIZ → ε = 10 VIZ
+ b = 50 VIZ → b × 0.001 = 50 mVIZ = 50 mVIZ → ε = 50 mVIZ (no change)
+
+ Note: leverage_min_market_liquidity (5,000 VIZ) provides partial protection —
+ markets with very small b are typically below the minimum liquidity threshold.
+ However, liquidity_sum in LMSR ≠ b directly, so the dynamic ε is a safety net.
+```
+
+This ensures that even with worst-case LMSR rounding, the pool still recovers its full obligation. The ε margin is conservative — in practice, the error is usually <25 mVIZ, but 50 mVIZ accounts for edge cases with many outcomes and small `b`.
+
+| Parameter | Default | Applies to |
+|-----------|---------|------------|
+| `lmsr_rounding_margin` | 50 mVIZ (0.050 VIZ) | LMSR Multi — minimum ε floor |
+| `lmsr_b_scale_factor` | 0.001 (0.1%) | LMSR Multi — ε = max(margin, b × factor) |
+| CPMM rounding margin | 0 (not needed) | CPMM binary markets |
+
+**Simultaneous openings (fund overshoot):**
+
+Protocol validates `leverage_fund_used + new_loan ≤ leverage_fund_total` at consensus level. If two `pm_leverage_open` operations arrive in the same block, the second one sees the updated `leverage_fund_used` from the first and is rejected if the fund is exhausted. No overshoot possible.
+
+### Outcome Risk (Eliminated by Force-Close)
+
+Leverage does not protect against **outcome risk**: if the bettor's chosen outcome loses at resolution, the pool loses the loan L. However:
+
+- **Cron Job 11 force-closes ALL positions before expiration** → pool recovers L × (1 + R%) via cancel-bet → outcome risk is converted to price-movement risk (which is zero)
+- The only scenario where the pool loses L is if force-close **fails** (cron malfunction, database error)
+
+**Cron Job 11 is the single non-structural failure point.** Unlike atomic liquidation (guaranteed by VIZ DLT at block level), force-close depends on an external process. Mitigation layers:
+
+| Layer | Mechanism | Effect |
+|-------|-----------|--------|
+| **1. Redundant execution** | Run Cron Job 11 on ≥ 2 independent servers (different hosting providers). Each instance checks and force-closes independently — no coordination needed (idempotent by position_id) | Eliminates single-server failure |
+| **2. On-chain fallback (VIZ DLT)** | Delegate plugin includes a `block_pre_apply` hook that checks: if `block.timestamp > market.betting_expiration − buffer` AND leveraged positions still exist → auto-force-close within the block itself | Makes force-close a consensus guarantee, not just a cron job. Cron Job 11 becomes a "nice to have" for early closure |
+| **3. Pool profit absorbs losses** | The pool earns R% on every position. Across many positions, total profit = Σ(L_i × R%). If Cron Job 11 fails and the pool loses L on a position, the loss is absorbed directly by the lazy pool's free_balance — the same pool that earns the leverage profit. No separate insurance fund needed — the profit itself is the buffer. Net effect over many positions: pool still profits as long as total R% earnings exceed occasional L losses | No extra fund, no diversion. The lazy pool's profit from successful positions naturally offsets rare losses |
+| **4. Monitoring & alerting** | Prometheus/Grafana alert: `active_leveraged_positions > 0 AND time_to_expiration < buffer` → immediate escalation | Reduces time-to-detection from hours to minutes |
+| **5. Market status trigger** | When `market.status → 'closing'` (Section 7), all positions are force-closed regardless of time-to-expiration. This trigger is application-level (not cron-dependent) and fires as soon as the oracle/committee sets the closing status | Provides an additional non-cron trigger |
+
+> **On VIZ DLT (post-migration):** Layer 2 makes Cron Job 11 failure irrelevant — the delegate enforces force-close as a consensus rule within each block. The cron job becomes a convenience for early closure, not a safety-critical component. The pool's structural zero guarantee extends from price-movement risk to outcome risk as well, since no position can survive past the expiration buffer.
+
+### Liquidation Rebalancing Spread
+
+When a leveraged position is liquidated, the opposing bettor gets a marginally better price (~POS% better execution). This creates a small MEV incentive but does not affect pool safety. See Section 5 for detailed analysis.
+
+**Known limitation (prototype):** The opposing bettor who triggers liquidation automatically receives the price improvement — no front-running required. This creates a systematic incentive for bots to target leveraged positions near their threshold. Zero-sum transfer from the liquidated bettor to the opposing bettor. Mitigated on VIZ DLT by price lock at pre-liquidation reserves.
+
+### Liquidation Sandwich MEV
+
+An attacker can front-run the liquidating bet with a bet on the leveraged outcome, slightly reducing the cancel_value at liquidation. Maximum impact bounded by `SL% × (C + L) / LS`. Not a pool risk (pool recovers L + R regardless) — the small reduction comes from the bettor's portion. Mitigated on VIZ DLT by locking liquidation price at previous block reserves. See Section 5 (Liquidation Sandwich MEV) for full analysis.
+
+### Market Integrity: cancel_value = 0 (Total Price Collapse)
+
+In extreme cases, a market outcome's price can collapse to near-zero (e.g., `reserve_b → 0` after massive betting on A). In this scenario, `cancel_value = 0` for all positions on the worthless side.
+
+**For leveraged positions:** If `cancel_value = 0`, the position would be liquidated immediately by the atomic check (since `0 < liquidation_threshold`). The pool receives `pool_received = min(0, L × (1 + R%)) = 0` — the pool loses the entire loan L.
+
+**Assessment:** This is not a specification risk — it's a market integrity risk that affects all participants equally. A market where one side's price is 0 means that side has been fully resolved by market forces. The same loss (L) would occur for any co-investor on the losing side, leveraged or not.
+
+**Mitigation:** The constraint system (Constraint 2 + Constraint 4) prevents leverage on markets thin enough for this to occur rapidly. The `leverage_min_market_liquidity` (5,000 VIZ) ensures a minimum depth. On deep markets, a price collapse to 0 requires massive coordinated selling — which triggers atomic liquidation at intermediate prices, limiting the pool's loss to a fraction of L.
+
+### Constraint 2 Conservatism
+
+Constraint 2 (cancel_value_worst ≥ threshold after M_max) is deliberately conservative for the prototype — atomic liquidation fires before the full M_max is applied, so the actual cancel_value at liquidation is always higher. This means effective leverage could be higher than the constraint allows.
+
+**Practical impact:** On deep markets (500K/500K VIZ), the full-M_max constraint can limit leverage to 1.1×–1.3× even when the fund allows 5×. This makes the product unattractive — users don't understand why they can only get 1.5× on a deep market.
+
+**Mitigation:** The conservatism is intentional for the prototype (non-atomic PHP). On VIZ DLT, a `VIZ_DLT_M_FACTOR` parameter (default 0.5) reduces M_max to `M_effective = M_max × factor`, allowing 2–3× more leverage while maintaining pool safety via atomic liquidation. See Section 4.6 (Constraint 2, Defense-in-depth note) for the full relaxation plan.
+
+### Profit Distribution
+
+```
+leverage_profit = pool_received − loan (at liquidation, force-close, or resolution)
+
+Fund flow on position close (with profit):
+ pool_received → lazy_pool.free_balance (all funds return to the common pool)
+ leverage_profit → lazy_pool.earned_balance (track cumulative pool earnings)
+ leverage_profit → lazy_pool.reward_per_share (profit distributed to LP investors)
+ leverage_fund_used -= loan (free up capacity for new loans)
+```
+
+No separate leverage fund balance, no diversion. The leverage fund is a **cap** on `free_balance`, not a separate account. All funds return to the lazy pool; `earned_balance` tracks cumulative lifetime earnings; `reward_per_share` distributes profit to investors; `leverage_fund_used` is merely a capacity counter.
+
+**On position loss (resolved_lost):** `reward_per_share` does NOT change. The loss of loan L is already reflected in `free_balance` (which decreased when the bet was placed into the AMM and the outcome lost). The loss is absorbed by the pool's total_value — it reduces the per-share value for all LP investors proportionally, but is NOT distributed as a negative `reward_per_share` (the accumulator is monotonically non-decreasing). This is consistent with how LP losses work in any AMM: losses reduce the pool's NAV, not the reward accumulator.
+
+---
+
+## 15. Implementation Notes
+
+### Pool Operation Atomicity
+
+**All operations that modify `lazy_pool` columns** (`free_balance`, `allocated_balance`, `earned_balance`, `leverage_fund_used`, `reward_per_share`, `total_shares`) **MUST execute within a single database transaction with a row-level lock on `lazy_pool` (id = 1).**
+
+This guarantees:
+- `free_balance` and `leverage_fund_used` change atomically (no partial state where `free_balance` decreased but `leverage_fund_used` not yet incremented)
+- `earned_balance` and `reward_per_share` update together with `free_balance` (no inconsistent snapshots)
+- Concurrent `pm_leverage_open` operations see the latest `leverage_fund_used` (preventing fund overshoot)
+
+```sql
+-- Required pattern for all lazy pool mutations:
+START TRANSACTION;
+SELECT * FROM `lazy_pool` WHERE `id` = 1 FOR UPDATE; -- row-level lock
+-- ... compute values ...
+UPDATE `lazy_pool` SET `free_balance` = ..., `leverage_fund_used` = ... WHERE `id` = 1;
+COMMIT;
+```
+
+On VIZ DLT, this atomicity is provided by the consensus engine — all state changes within a single operation are applied atomically within the block.
+
+### Backend (prototype)
+
+All leverage calculations live in `module/lazy_pool_helpers.php` (shared with the cron worker):
+
+```php
+function lazy_pool_settle_rewards(&$user_arr, $pool) // Settle accumulated rewards into user's pending_rewards
+function lazy_pool_oracle_multiplier($db, $oracle_id) // Oracle allocation multiplier based on active markets + fault stamps
+function lazy_pool_add_fault_stamp($db, $oracle_id, ...) // Add fault penalty stamp for an oracle
+function lazy_pool_expire_fault_stamps($db) // Expire fault stamps whose time has passed
+function lazy_pool_graduated_recall_check($db, $alloc, $market) // Graduated recall check for idle market allocations
+function leverage_distribute_profit($db, $pool_profit, $total_shares) // Atomically distribute leverage profit to lazy pool
+function leverage_fund_state($db) // Get current leverage fund state from lazy_pool
+function leverage_compute_cancel_value_cpmm($market, $tokens, $outcome_index) // Cancel value for CPMM binary
+function leverage_compute_cancel_value_lmsr($market, $tokens, $outcome_index) // Cancel value for LMSR multi
+function leverage_compute_cancel_value($market, $tokens, $outcome_index) // Dispatch to CPMM or LMSR
+function leverage_compute_cancel_value_worst($market, $collateral, $loan, $outcome_index, $M_max) // Worst-case cancel value (Constraint 2)
+function leverage_compute_max_leverage($db, $market, $collateral, $outcome_index) // Full constraint system + slider stops
+function leverage_estimate_tokens_cpmm($market, $amount, $outcome_index) // Estimate tokens for CPMM bet
+function leverage_force_close_position($db, $position, $market, $reason) // Force-close a leveraged position (shared for cron, voluntary, liquidation)
+function leverage_cascade_liquidate($db, $market_id, $outcome_index, $reason, $max_iterations) // Recursive cascade liquidation loop
+```
+
+API endpoints are in `module/api.php` (8 endpoints: `leverage-preview`, `leverage-open`, `leverage-close-preview`, `leverage-close`, `leverage-convert-preview`, `leverage-convert`, `leverage-info`, `leverage-pool-state`).
+
+The `load-market-enriched` API response includes `my_leveraged_positions` — the user's active leveraged positions on the market with auto-close countdown data.
+
+### Frontend (app.js)
+
+Boost UI implemented inline in `app.js` (not a separate module):
+- State variables: `boost_preview_data`, `boost_active_market_ids`, `boost_active_loaded`
+- `render_boost_form(market_id, mtype, outcomes)` — generate boost form HTML with slider, collateral input, slippage chips, auto-close checkbox
+- `boost_fetch_preview(form)` — call `leverage-preview` API, configure slider, set auto-close date
+- `boost_render_detail(form, leverage_01, data)` — render position details at given leverage level; interpolate between slider stops
+- `boost_format_failed_constraints(constraints)` — format failure messages by priority
+- `boost_open_action(el)` — call `leverage-open` API with collateral, leverage, max_slippage_percent
+- `boost_close_action(position_id)` — call `leverage-close-preview`, show modal with loss breakdown
+- `boost_close_confirmed(el)` — call `leverage-close` API with min_return
+- `boost_convert_action(position_id)` — call `leverage-convert-preview`, show modal with fee breakdown
+- `boost_convert_confirmed(el)` — call `leverage-convert` API with conversion_profit_cost
+- `load_boost_info()` — call `leverage-info` API, populate boost positions table in profile
+- CSS styles in `app.css` (`.boost-form`, `.boost-slider`, `.boost-banner`, `.boost-positions-table`)
+- i18n strings in `i18n/en.json` and `i18n/ru.json` under `boost.*` namespace (98 keys each)
+
+### Cron Job
+
+**Job 11 — Leverage expiration check**: Runs every 5 minutes. Finds leveraged positions on markets where `betting_expiration − leverage_expiration_buffer_hours` has passed, **or** where `market.status` has changed to `closing` (after the grace period). Force-closes **all** of them (cancel-bet at current price). This converts outcome risk into price-movement risk, ensuring the pool recovers L × (1 + R%) from every position.
+
+**Idempotency mechanism:** When running on ≥ 2 servers (Section 14, Layer 1), each instance must atomically claim the position before processing it. This prevents double-close (two servers trying to close the same position simultaneously):
+
+```sql
+-- Step 1: Atomically claim the position (status 0 = active → 4 = closed_voluntary)
+UPDATE leveraged_positions
+SET status = 4, `update` = UNIX_TIMESTAMP()
+WHERE id = ? AND status = 0;
+
+-- Step 2: Check if we claimed it
+IF affected_rows = 0 THEN
+ -- Position already processed by another instance (or liquidated/resolved)
+ SKIP this position
+END IF
+
+-- Step 3: We claimed it — now compute cancel_value and distribute funds
+-- (SELECT current reserves, compute cancel_value, credit pool/bettor)
+```
+
+The `WHERE status = 0` condition ensures that:
+- If another cron instance already closed the position → `affected_rows = 0` → skip
+- If the position was atomically liquidated by a bet → `affected_rows = 0` → skip
+- If the position was voluntarily closed by the bettor → `affected_rows = 0` → skip
+
+Only one process ever transitions the position from `status = 0` to `status = 4`. No coordination between servers needed — the database row-level lock on the UPDATE provides the atomic guarantee.
+
+> **Transaction requirement:** The UPDATE + cancel_value calculation + fund distribution must all be within a single database transaction. If the transaction fails (e.g., deadlock), the position remains at `status = 0` and will be picked up on the next cron run (5 minutes later). No data corruption possible.
+
+> **Design choice:** Close ALL positions when the market enters "closing" status, not just those where cancel_value < threshold. This eliminates outcome risk entirely for the pool. The bettor receives cancel_value − pool_obligation, which may be positive (if the market moved in their favor) or zero (if near liquidation). See Section 7 (Market Status Trigger for Force-Close) and Example 4 for a profit-case walkthrough.
+
+### Convert to Normal Bet (`pm_leverage_convert`)
+
+A bettor may convert their leveraged position into a normal (non-leveraged) bet. This allows them to hold the position to resolution and capture the full outcome payout, instead of being force-closed before expiration. The pool is paid in full plus a conversion fee.
+
+**Mechanics:**
+
+```
+Given: leveraged position with loan L, pool profit R%, current cancel_value
+
+pool_obligation = L × (1 + R%/100) (= liquidation_threshold)
+current_profit = cancel_value − pool_obligation (bettor's unrealized gain)
+conversion_fee = current_profit × conversion_profit_cost% / 100
+
+total_user_payment = pool_obligation + conversion_fee
+```
+
+**The bettor pays from their balance:**
+1. **Loan repayment + pool profit**: `L × (1 + R%)` — the pool's full claim (same as liquidation)
+2. **Conversion fee**: `current_profit × conversion_profit_cost%` — a fee for the privilege of holding to resolution
+
+**Fund flow:**
+```
+lazy_pool.free_balance += total_user_payment
+lazy_pool.earned_balance += (pool_obligation − L) + conversion_fee
+lazy_pool.reward_per_share += ((pool_obligation − L) + conversion_fee) × 10^9 / total_shares
+leverage_fund_used -= L
+user.balance -= total_user_payment
+
+Position status → converted (5)
+Bet record updated: remove leveraged_position, keep as normal bet (100% bettor-owned)
+```
+
+**Why the conversion fee?** Without it, a bettor whose position is in profit would always convert for free — they'd pay `L × (1 + R%)` from their balance, which is what the pool would get from a force-close anyway. The conversion fee (default 50% of unrealized profit) ensures the pool captures additional revenue for giving up its guaranteed position. This is fair: the bettor is buying the *option* to hold to resolution, and paying a share of their unrealized gain for that privilege.
+
+**Validation rules:**
+```
+1. position.status == 0 (active)
+2. cancel_value ≥ pool_obligation (position must be in or near profit)
+3. current_profit > 0 (conversion only makes sense when position has unrealized gain)
+4. user.balance ≥ total_user_payment (bettor can afford the buyout)
+5. conversion_profit_cost == settings.conversion_profit_cost (bettor must specify the correct fee %)
+6. market.status == 1 (active — conversion on 'closing' markets also allowed, see below)
+```
+
+**On market status → 'closing':** Conversion is allowed during the grace period. This gives the bettor a choice: let the cron force-close (receive `cancel_value − pool_obligation` in VIZ), or convert (pay `pool_obligation + conversion_fee` from balance, hold tokens to resolution). The bettor picks whichever is more favorable given their conviction about the outcome.
+
+**Comparison with voluntary close:**
+
+| Action | Pool receives | Bettor receives | Bettor risk at resolution |
+|--------|--------------|-----------------|--------------------------|
+| Voluntary close | `L × (1 + R%)` | `cancel_value − L × (1 + R%)` in VIZ | None — already cashed out |
+| Convert to normal | `L × (1 + R%) + conversion_fee` | Tokens held to resolution | Outcome risk: if loses, tokens worth 0 |
+| Force-close (cron) | `L × (1 + R%)` | `cancel_value − L × (1 + R%)` in VIZ | None — already cashed out |
+
+**Numerical example** (continuing Example 4):
+
+```
+Position: 5× boost on A, collateral = 360 VIZ, loan = 1800 VIZ
+Market moved in bettor's favor:
+ cancel_value = 2,635 VIZ
+ pool_obligation = 1800 × 1.10 = 1,980 VIZ
+ current_profit = 2,635 − 1,980 = 655 VIZ
+
+Option 1 — Voluntary close:
+ Bettor receives: 655 VIZ (in hand now)
+ If A wins at resolution: misses additional upside
+
+Option 2 — Convert to normal (conversion_profit_cost = 50%):
+ conversion_fee = 655 × 50% = 327.5 VIZ
+ total_user_payment = 1,980 + 327.5 = 2,307.5 VIZ
+ Bettor pays 2,307.5 VIZ from balance
+ Bettor keeps tokens worth full outcome payout at resolution
+
+ If A wins: tokens redeemable at ~1.0 per token → payout > cancel_value
+ Net result: payout − 2,307.5 (could be much more than 655 VIZ)
+ If A loses: tokens worth 0 → net loss = 360 (collateral) + 2,307.5 (payment) = −2,667.5 VIZ
+ (vs. voluntary close where bettor would have +655 VIZ guaranteed)
+
+ The bettor is betting their 327.5 VIZ conversion fee (plus the 655 VIZ they'd
+ have gotten from voluntary close) on the outcome actually occurring.
+```
+
+**Frontend dialog:**
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ 🔄 CONVERT BOOST TO NORMAL BET │
+│ │
+│ Your 5× boosted position is currently in profit. │
+│ You can convert it to a normal bet and hold to resolution.│
+│ │
+│ ┌─────────────────────────────────────────────────────┐ │
+│ │ Current position value: 2,635.000 VIZ │ │
+│ │ Your unrealized profit: 655.000 VIZ │ │
+│ │ │ │
+│ │ You pay from balance: │ │
+│ │ Loan + pool profit: 1,980.000 VIZ │ │
+│ │ Conversion fee (50%): 327.500 VIZ │ │
+│ │ ───────────────────────────────────────────── │ │
+│ │ Total: 2,307.500 VIZ │ │
+│ │ │ │
+│ │ After conversion: │ │
+│ │ ✓ You own 100% of the position │ │
+│ │ ✓ No auto-close — held to resolution │ │
+│ │ ✓ Full payout if your outcome wins │ │
+│ │ ✗ Full loss if your outcome loses │ │
+│ └─────────────────────────────────────────────────────┘ │
+│ │
+│ ⚠️ Converting means you take on outcome risk. │
+│ If you're wrong, you lose your payment + collateral. │
+│ │
+│ [ KEEP AS BOOSTED ] [ CONVERT TO NORMAL BET ] │
+└─────────────────────────────────────────────────────────────┘
+```
+
+**New position status:**
+
+| Status | Name | Description |
+|--------|------|-------------|
+| 0 | active | Position open, leverage active |
+| 1 | liquidated | Force-closed by atomic liquidation |
+| 2 | resolved_won | Resolved at market end, bettor won |
+| 3 | resolved_lost | Resolved at market end, bettor lost |
+| 4 | closed_voluntary | Bettor voluntarily closed (cancel-bet) |
+| 5 | converted | Bettor converted to normal bet (tokens held to resolution) |
+
+**New history type:**
+
+| Type | Name | Description |
+|------|------|-------------|
+| 34 | `leverage_convert` | Leveraged position converted to normal bet |
+
+### VIZ DLT Roadmap
+
+Required and planned changes for the VIZ DLT migration of the leverage system:
+
+| Priority | Feature | Description | Status |
+|----------|---------|-------------|--------|
+| **P0** | `viz_dlt_m_factor` | Set to 0.5 (default) on VIZ DLT. Without this, leverage is uncompetitively low (1.1×–1.3× on deep markets). **Required for launch.** | Specified |
+| **P0** | Atomic liquidation in `block_pre_apply` | Delegate plugin enforces liquidation before opposing bet. Makes force-close a consensus guarantee. | Specified |
+| **P0** | On-chain force-close fallback | `block_pre_apply` hook auto-force-closes positions past expiration buffer. Eliminates Cron Job 11 as single point of failure. | Specified |
+| **P1** | Liquidation price lock | Lock liquidation cancel_value at previous block reserves. Eliminates liquidation sandwich MEV and rebalancing spread MEV. | Specified |
+| **P1** | `pm_leverage_open` slippage protection | `max_slippage_percent` / `min_tokens` validated at consensus level. | Specified |
+| **P2** | Convert to Normal Bet | Allow bettors to buy out the pool's share and hold to resolution. Key retention feature for advanced users. | **Specified** |
+| **P2** | Dynamic depth factor | `depth_factor = f(liquidity_sum, active_positions)` for Constraint 2 relaxation. Adapts to market conditions automatically. | Future work |
+
+---
+
+## Summary
+
+| Property | Value |
+|----------|-------|
+| **Implementation status** | ✅ **Implemented** (prototype). Migration 005, 8 API endpoints, frontend UI, cron job, test coverage (scenarios 46–52) |
+| **Pool risk (price movement)** | Structural zero — eliminated by atomic pre-check + voluntary close rejection + force-close |
+| **Pool risk (outcome)** | Eliminated by force-close before expiration (cron job). Residual: cron failure = operational risk |
+| **Pool risk (cancel-bet liquidation)** | Bounded bad debt — cancel-bettor executes first (fairness). Rare shortfall absorbed by free_balance. Bounded by SL% |
+| **Gap risk** | Eliminated by atomic block-level pre-check |
+| **Liquidation MEV** | Small rebalancing spread (~POS%) for opposing bettors. Not a pool risk |
+| **Max leverage** | Dynamic: min of fund availability, market depth, position size ratio |
+| **Safety margin** | 1% buffer during position opening only |
+| **Fund cap** | 10% of lazy pool free_balance (`leverage_fund_total = free_balance × F% / 100`) |
+| **Free amount** | `free_balance − leverage_fund_used` (truly available capital, not loaned) |
+| **Earned** | `earned_balance` tracks cumulative lifetime pool earnings (never decreases) |
+| **Per-position cap** | 0.2% of available leverage fund |
+| **Min market liquidity** | 5,000 VIZ (committee-configurable) |
+| **Max position size** | 5% of market liquidity_sum (committee-configurable) |
+| **Expiration buffer** | 24 hours (no new positions near expiration; force-close all existing) |
+| **Voluntary close** | Only when cancel_value ≥ liquidation_threshold (preserves structural zero) |
+| **Frontend** | Slider 1.0×–max. Real-time liquidation price. Cancel-bet warning with loss breakdown. Auto-close notice in 4 places. Convert-to-normal dialog. 98 i18n strings per language |
+| **Backend** | `module/lazy_pool_helpers.php` (16 functions), `module/api.php` (8 endpoints) |
+| **Migration** | `migrations/005_leverage.sql` |
+| **VIZ DLT P0** | `viz_dlt_m_factor = 0.5` (required), atomic liquidation, on-chain force-close |
diff --git a/.qoder/plans/pm-plan/lmsr-fixed-point-spec.md b/.qoder/plans/pm-plan/lmsr-fixed-point-spec.md
new file mode 100644
index 0000000000..fc78088dfa
--- /dev/null
+++ b/.qoder/plans/pm-plan/lmsr-fixed-point-spec.md
@@ -0,0 +1,406 @@
+> **Status:** Normative reference for the deterministic LMSR pricing math used by:
+> - the PHP back-end ([module/lmsr_fixed.php](../module/lmsr_fixed.php)),
+> - the in-browser front-end ([market_math.js](../market_math.js)),
+> - the future C++ VIZ-DLT consensus implementation.
+>
+> All three implementations **MUST** produce **bit-identical** outputs given identical inputs. This document is the single source of truth; it resolves [§7.2 of the VIZ-DLT protocol spec](viz-dlt-prediction-market-protocol-spec.md#72-floating-point-determinism-in-consensus-hardest).
+>
+> If you change any number in §3, §4 or §5 you change consensus. Any change MUST be gated behind a hardfork.
+
+---
+
+# Onix LMSR — Canonical Fixed-Point Specification
+
+## 0. Scope
+
+The Logarithmic Market Scoring Rule (LMSR) priced multi-outcome markets through `exp` and `ln`. IEEE-754 `double` is **not** bit-deterministic across compilers, CPUs and `libm` versions, so the existing `module/lmsr_math.php` and `market_math.js` cannot be used to compute on-chain consensus values. This document specifies a fixed-point integer algorithm — using only `+`, `−`, `×`, `÷` (truncating), and bit shifts on big integers — that is reproducible to the **last bit**.
+
+The CPMM (binary) path is already integer-clean and is **not** changed by this document.
+
+## 1. Domain & Number Formats
+
+### 1.1 Public units (unchanged)
+
+| Symbol | Meaning | Type | Precision |
+|--------|---------|------|-----------|
+| `q[i]` | Outstanding tokens of outcome `i` | int64 | milli-VIZ (×1000) |
+| `b` | LMSR liquidity parameter | int64 | milli-VIZ (×1000) |
+| `amount` | VIZ committed to a bet | int64 | milli-VIZ (×1000) |
+| `price[i]` | Outcome probability | int64 | ×10⁶ (1e6 = 100%) |
+| `cost`, `buy_cost`, `sell_return`, `tokens` | Returned amounts | int64 | milli-VIZ (×1000) |
+
+`LMSR_PRECISION = 1000` and `LMSR_PRICE_PRECISION = 1_000_000` are unchanged from the existing PHP/JS API.
+
+### 1.2 Internal fixed-point format `Q96`
+
+All transcendental work happens in **signed fixed-point with 96 fractional bits** stored in a wide signed integer (≥ 192 bits). We name this format `Q96`.
+
+```
+value_real = value_int / 2^96 // value_int is a signed bigint
+```
+
+Constants used throughout:
+
+```
+ONE = 1 << 96 // = 2^96
+HALF = 1 << 95 // = 2^95
+LN2 = 0xB17217F7D1CF79ABC9E3B398 // ⌊ln(2) · 2^96⌋ — 24 hex digits = 96 bits
+ // = 54916777467707473351141471128 (decimal)
+ // Verified: 54916777467707473351141471128 / 2^96
+ // = 0.693147180559945309417232121457
+ // true ln(2) ≈ 0.693147180559945309417232121458 (next bit rounds down → truncation, by §3.5)
+MAX_Q96 = (1 << 191) - 1 // hard cap; over → overflow error
+MIN_Q96 = -(1 << 191) // hard cap
+```
+
+> **Why 96 bits?** With Q96, `exp(x)` has ≤ 1 ULP of relative error after the polynomial in §3.1 with 24 Taylor terms (`x ≤ ln(2)/2 ≈ 0.347`). After 30 terms the residual is `< 2^-100`, far below Q96. ULPs and rounding rules are fixed in §3.5 so the same residual is dropped on every platform.
+
+> **Why not Q64?** Q64 leaves 64 fractional bits; LMSR needs `exp` of values up to ≈ `q_max / b`. For our domain (see §1.4) `q/b` reaches a few hundred. Pricing differences below `2^-30 · b` are economically meaningless, but rounding **inside** `log-sum-exp` accumulates: `Σ exp(x_j)` with N=10 terms each having ≤ 1 ULP of error makes the final `ln(·)` carry ≤ N ULPs of error. We need ≥ 80 fractional bits to guarantee that the LMSR cost rounded back to milli-VIZ matches across implementations. **Q96 has comfortable head-room.**
+
+> **Why not Q128 / decimal-18?** Bigger words, more `mul/div` cost, no measurable gain. Q96 fits in a 192-bit accumulator (3 × `uint64`) which is a natural width on 64-bit hardware (C++ uses `boost::multiprecision::int256_t` or a 4-limb intrinsic; PHP uses `gmp`; JS uses native `BigInt`).
+
+### 1.3 Required big-integer width
+
+| Operation | Minimum signed-integer width |
+|-----------|------------------------------|
+| Q96 storage / accumulator | **192 bits** |
+| Q96 × Q96 → Q96 (intermediate) | **256 bits** |
+| `exp` Taylor accumulator | 256 bits |
+| `lse` exponent shift `x − max_x` | 256 bits |
+
+C++ reference implementation **MUST** use `boost::multiprecision::int256_t` (or a hand-rolled 4×u64 type) for the multiplier, then truncate to 192 bits when storing.
+
+### 1.4 Input domain & overflow guards
+
+LMSR is undefined for `b ≤ 0`; the spec rejects with `lmsr_b_invalid` (no fallback).
+
+For sanity, **before** any internal Q96 conversion the implementation MUST check:
+
+```
+0 < b ≤ MAX_B (MAX_B = 2^53, ~ 9·10^15 milli-VIZ)
+0 ≤ q[i] ≤ MAX_Q (MAX_Q = 2^53)
+N (outcomes) in [2 .. 16]
+```
+
+These limits keep `q/b` ≤ `MAX_Q / 1` = `2^53` in the worst case. We additionally cap the LSE exponent shift (§3.2) so `|x_max − x_min| < EXP_DOMAIN_LIMIT = 200 · ONE` (≈ `e^200 ≈ 7e86`, well below the bigint width). Anything beyond returns the canonical overflow code.
+
+## 2. Conversion Helpers
+
+```text
+to_q96(x_milli) := (int)(x_milli) << 96 / 1000 // divide by LMSR_PRECISION inside Q96
+ = ((int)x_milli * ONE) / 1000 // truncated toward 0 (signed-floor for ≥0 inputs)
+from_q96(v_q) := (v_q * 1000) >> 96 // back to milli-VIZ, truncated toward 0
+```
+
+Both use **truncation toward zero** (a.k.a. C99 integer division, not Python floor). Implementations on platforms where `/` rounds differently (some big-integer libraries) MUST manually emulate truncation.
+
+`mul_q(a, b) := (a * b) >> 96` — multiplied in the wide accumulator first, then arithmetic right shift. Implementations MUST use **arithmetic right shift** (sign-extending) to preserve sign.
+
+`div_q(a, b) := (a << 96) / b` — left-shift to 192-bit accumulator, divide once. Truncating toward zero.
+
+## 3. Core Transcendentals
+
+### 3.1 `exp_q(x)` — exponential in Q96
+
+**Domain:** `x` ∈ Q96, with `|x| ≤ 130 · ONE` (≈ `e^130 ≈ 2·10^56`, fits in 192 bits with head-room). Beyond the domain the implementation returns `LMSR_OVERFLOW`.
+
+**Algorithm:**
+
+```
+1. Range reduction:
+ k = round_to_nearest_even(x / LN2) // integer (use signed div with banker rounding spec'd in §3.5)
+ r = x - k * LN2 // |r| ≤ LN2/2 ≈ 0.3466 in real units
+2. Polynomial:
+ acc = ONE // term[0] = 1
+ term = ONE
+ for n in 1..30: // FIXED iteration count = 30
+ term = mul_q(term, r) / n // term · r / n (n is small int; do mul first)
+ acc = acc + term // signed add
+3. Scaling:
+ result = acc << k if k ≥ 0
+ = acc >> (-k) if k < 0 // arithmetic right shift
+4. Return result.
+```
+
+**Constants:**
+- `LN2` is exactly the integer in §1.2 (frozen). Implementations **MUST NOT** recompute it.
+- The Taylor loop bound is fixed at **30 iterations**, regardless of `r`. Empirically iterations 25–30 contribute < 2⁻¹⁰⁰ of the result, but every implementation must run all 30 to guarantee identical truncation behaviour.
+- `term · r / n`: do the `mul_q` first (`term * r >> 96`), THEN integer-divide by `n` (truncating toward zero). Order matters: dividing first loses bits.
+
+**Rounding:** every intermediate keeps Q96; only `mul_q` introduces a single arithmetic right-shift truncation per iteration. No double-rounding.
+
+### 3.2 `ln_q(x)` — natural logarithm in Q96
+
+**Domain:** `x > 0` in Q96. For `x ≤ 0` return `LMSR_OVERFLOW`.
+
+**Algorithm (Briggs/atanh, deterministic):**
+
+```
+1. Range reduction to m ∈ [1, 2):
+ p = msb(x) - 96 // integer; m = x / 2^p ∈ [1,2)
+ m = (p ≥ 0) ? (x >> p) : (x << -p)
+2. Substitute z = (m - ONE) / (m + ONE) // |z| < 1/3 in Q96
+ num = m - ONE
+ den = m + ONE
+ z = div_q(num, den)
+3. atanh series:
+ z2 = mul_q(z, z)
+ acc = z // term = z
+ term = z
+ for k in 1..50: // FIXED iteration count = 50
+ term = mul_q(term, z2) // term *= z^2
+ denom = (2*k + 1) // 3, 5, 7, ...
+ acc = acc + term / denom // truncated divide
+4. ln_m = 2 * acc // ln(m) = 2 · atanh((m-1)/(m+1))
+5. ln(x) = p * LN2 + ln_m // signed; p can be negative
+```
+
+**Iteration count = 50** is fixed. With `|z| < 1/3` the residual after 50 odd-power terms is `< 3^-100 ≈ 2^-158`, well below Q96's resolution.
+
+**`msb(x)`** = position of the highest set bit (0-indexed). Big-int libraries provide this directly (`gmp`: `mpz_sizeinbase(_, 2) - 1`; `BigInt`: bit-length helper; C++: `__builtin_clzll`-based ladder over limbs). The function MUST NOT call any floating-point primitive.
+
+### 3.3 `lse_q(xs)` — log-sum-exp with shift
+
+```
+1. m = max(xs) // signed Q96
+2. acc = 0
+ for x in xs:
+ d = x - m // d ≤ 0
+ if d < -EXP_DOMAIN_LIMIT: continue // contributes < 2^-200, drop deterministically
+ acc += exp_q(d)
+3. return m + ln_q(acc)
+```
+
+**Important:** the "drop tiny terms" rule with cutoff `−EXP_DOMAIN_LIMIT = −200·ONE` is **mandatory** and MUST be applied in every implementation in the same loop order. We drop on `d < -EXP_DOMAIN_LIMIT` (strict `<`), keep on `d ≥ -EXP_DOMAIN_LIMIT`.
+
+The loop iterates over `xs` in **input order** (the order in which `q[]` was supplied). Re-ordering changes summation rounding.
+
+### 3.4 `lmsr_cost_q(q[], b)` and helpers
+
+```
+ratios[i] = (q[i] * ONE) / b // truncated toward zero. ratios[i] ∈ Q96.
+ // Direct one-step form: avoids double-truncation
+ // that to_q96/to_q96/div_q would introduce for
+ // q[i] / b not a multiple of 1/1000.
+lse = lse_q(ratios) // Q96
+cost_milli= (b * lse) / ONE // truncated toward zero. Result is int64 milli-VIZ.
+ // Equivalent to: arithmetic right shift by 96 of
+ // a wide signed product, with sign correction so
+ // the rule is "round toward 0" (NOT toward -∞).
+return cost_milli
+```
+
+Note: `q[]` and `b` are int64 milli-VIZ throughout; `ratios[i]` and `lse` are Q96; `cost_milli` is int64 milli-VIZ. The two `1000` factors that would appear if we round-tripped through `to_q96` cancel exactly in the algebra, so we never write them. The output matches `b * ln(Σ exp(q_j/b))` (in real units) truncated to integer milli-VIZ.
+
+`lmsr_buy_cost_q` and `lmsr_sell_return_q` are subtractions of two `lmsr_cost_q` results; the `max(0, ...)` guard remains (it can only fire on adversarial inputs that violate §1.4 domain bounds).
+
+### 3.5 Rounding rules (frozen)
+
+| Operation | Rule |
+|-----------|------|
+| `mul_q(a, b)` | Arithmetic right shift by 96 = round toward `-∞` for negative results. Implementations **MUST** use ASR, not LSR. |
+| `div_q(a, b)` | C99 integer division: round toward zero. |
+| `to_q96`, `from_q96` | Round toward zero. |
+| `exp_q` Taylor `term / n` | Round toward zero. |
+| `ln_q` atanh `term / (2k+1)` | Round toward zero. |
+| `range reduction k = x / LN2` | **Round to nearest, ties to even** (banker's). One single round-to-nearest in the entire pipeline; everywhere else is truncation. |
+
+> "Round to nearest even" for the `k` in `exp_q`'s range reduction is implemented as:
+> ```
+> q = x / LN2 // truncated
+> r = x - q * LN2
+> if 2*r > LN2 or (2*r == LN2 and (q & 1)):
+> q = q + 1
+> if 2*r < -LN2 or (2*r == -LN2 and (q & 1)):
+> q = q - 1
+> k = q
+> ```
+> All comparisons are signed bigint comparisons. Identical on every platform.
+
+### 3.6 Error codes
+
+A single global enum is exposed by every implementation:
+
+```
+LMSR_OK = 0
+LMSR_OVERFLOW = 1 // x outside §1.4 domain in any internal step
+LMSR_INVALID_B = 2 // b ≤ 0
+LMSR_INVALID_Q = 3 // q[i] < 0 or > MAX_Q
+LMSR_INVALID_N = 4 // outcomes < 2 or > 16
+LMSR_DOMAIN_LN = 5 // ln_q called with x ≤ 0 (should be unreachable; guard)
+```
+
+In off-chain (PHP / JS) code these surface as exceptions; in on-chain (C++) code they fail the operation.
+
+## 4. Public API (identical signatures)
+
+The PHP / JS / C++ implementations expose the same 6 functions. Names are PHP-convention; the JS module re-exports with the same identifiers, the C++ code uses `lmsr::cost(...)` etc.
+
+| Function | Inputs | Output | Notes |
+|----------|--------|--------|-------|
+| `lmsr_cost(q[], b)` | int64 milli-VIZ | int64 milli-VIZ | §3.4 |
+| `lmsr_price(q[], b, i)` | int64 milli-VIZ, idx | int (×10⁶) | `from_q96(div_q(exp_q(x_i − m), Σ exp_q(x_j − m))) * 10^6` |
+| `lmsr_prices(q[], b)` | int64 milli-VIZ | int[] (×10⁶) | All N prices |
+| `lmsr_buy_cost(q[], b, i, Δ)` | int64 milli-VIZ | int64 milli-VIZ | `max(0, cost(q+Δ·e_i) − cost(q))` |
+| `lmsr_sell_return(q[], b, i, Δ)` | int64 milli-VIZ | int64 milli-VIZ | `max(0, cost(q) − cost(q-Δ·e_i))` |
+| `lmsr_tokens_for_amount(q[], b, i, amount)` | int64 milli-VIZ | int64 milli-VIZ | Binary search: largest Δ with `buy_cost ≤ amount` |
+
+`lmsr_b_from_liquidity(L, N)` is **not** consensus-critical (it's chosen at market creation and stored). It still uses `ln(N)` but is computed once per market, on the creator's client; the resulting `b` is part of the on-chain market object and frozen. Implementations MUST use `ln_q` for it as well so reference values match.
+
+### 4.1 `lmsr_tokens_for_amount` — deterministic binary search
+
+```
+lo = 0
+hi = clamp(amount * 10, 0, MAX_Q) // upper bound, identical to current PHP
+best = 0
+for iter in 1..100: // FIXED iteration count
+ if hi - lo <= 1: break
+ mid = (lo + hi) >> 1 // integer floor
+ if lmsr_buy_cost(q, b, i, mid) <= amount:
+ best = mid; lo = mid
+ else:
+ hi = mid
+return best
+```
+
+**Iteration count = 100** is fixed. Even when the search converges in fewer steps, all 100 iterations execute (the early break is allowed because `hi - lo ≤ 1` is symmetric and platform-independent). Empirically 60 iterations suffice, 100 is conservative.
+
+## 5. Test Vectors (consensus-fixing)
+
+Every implementation MUST pass `tests/lmsr_fixed_vectors.json` (Phase 4). The JSON contains **all** the values an implementer needs to verify primitives and end-to-end LMSR. Format is fixed:
+
+```json
+{
+ "version": 1,
+ "exp_q": [
+ { "x": "0", "expected": "79228162514264337593543950336" },
+ { "x": "54916777467707473351141471128", "expected": "158456325028528675187087900672" },
+ { "x": "-54916777467707473351141471128", "expected": "39614081257132168796771975168" }
+ ],
+ "ln_q": [ ... ],
+ "mul_q": [ ... ],
+ "div_q": [ ... ],
+ "lmsr_cost": [
+ { "q": [1000, 1000, 1000], "b": 100000, "expected": 109861 },
+ { "q": [10000, 0, 0], "b": 100000, "expected": 13500 }
+ ],
+ "lmsr_buy_cost": [ ... ],
+ "lmsr_tokens_for_amount": [ ... ]
+}
+```
+
+All numeric inputs / outputs are **decimal strings** (because Q96 values exceed 2⁵³). Equality is exact (`a === b`, no tolerance). The vector file is generated once by the PHP reference (Phase 2) and **frozen** — JS, PHP and C++ all compare against it.
+
+### 5.1 Tooling (this repo)
+
+- `tests/lmsr_vectors_generate.php` — regenerates `tests/lmsr_fixed_vectors.json` from the PHP reference (only re-run when the spec changes; bump `version` in the JSON header).
+- `tests/lmsr_fixed_vectors_verify.php` — asserts the PHP reference still matches every vector. Exit 0 on success.
+- `tests/lmsr_fixed_vectors_verify.js` — same, for the JS reference (`node tests/lmsr_fixed_vectors_verify.js`).
+- A C++ port MUST add an analogous verifier as part of its CI.
+
+## 6. C++ Implementation Guide
+
+This section is non-normative reference for the future VIZ DLT plugin author. The PHP/JS reference implementations are normative.
+
+### 6.1 Types
+
+```cpp
+#include
+using namespace boost::multiprecision;
+
+using q96 = int256_t; // store; uses 192 bits, room to spare
+using q96_wide = int512_t; // multiplier accumulator, 256 bits used
+constexpr q96 ONE = q96(1) << 96;
+constexpr q96 LN2 = q96("54916777467707473351141471128"); // §1.2
+constexpr q96 EXP_DOMAIN_LIMIT = q96(200) * ONE;
+```
+
+Use `int256_t` (not `int128_t`) because §1.3 requires 256-bit accumulators for `mul_q` and `exp` partial products.
+
+### 6.2 mul_q / div_q
+
+```cpp
+inline q96 mul_q(q96 a, q96 b) {
+ q96_wide t = q96_wide(a) * q96_wide(b);
+ return q96(t >> 96); // boost ASR; sign-preserving
+}
+inline q96 div_q(q96 a, q96 b) {
+ q96_wide t = q96_wide(a) << 96;
+ return q96(t / b); // boost: truncation toward 0
+}
+```
+
+Verify with vector file: any deviation from the test vectors indicates the boost ASR/truncation behaviour differs from the reference and MUST be fixed before merge.
+
+### 6.3 Determinism caveats specific to C++
+
+1. **Compiler flags:** prohibit `-ffast-math`, `-funsafe-math-optimizations`, `-Ofast`. Plugin Makefile MUST set `-fno-fast-math -ffp-contract=off` (defensive even though we do not call float).
+2. **No FP library calls:** confirm with `nm` that the plugin object has no references to `exp`, `log`, `pow`, `sqrt`, `expf`, `logf`, etc. CI MUST grep for these symbols and fail the build if any appear.
+3. **Endianness:** all our math is over big-integer values that boost handles abstractly. No byte-order code paths.
+4. **Bigint library version:** pin boost version in `CMakeLists.txt`. A boost upgrade must re-run the test vector suite before merge.
+5. **`mpz_*` is forbidden** in the consensus path. Boost is the only big-int dependency.
+
+### 6.4 Consensus integration
+
+LMSR is invoked from:
+- `pm_place_bet` evaluator (instant mode) — computes `tokens` for a buy.
+- `pm_batch_settle` virtual op (per-epoch) — computes uniform-price weights.
+- `pm_cancel_bet` evaluator (multi only) — computes `lmsr_sell_return`.
+- `pm_create_market` evaluator — computes `b` from `liquidity` (one-time).
+
+All four call sites MUST go through the spec'd functions. **Replay determinism**: if the spec changes, a new hardfork constant gates the new constants/iteration counts; old blocks replay against the old constants.
+
+### 6.5 Performance
+
+A worst-case `lmsr_tokens_for_amount` call: 100 binary-search iterations × 2 `lmsr_cost` × (10 outcomes × 30 Taylor steps + 50 atanh steps) ≈ 160k 256-bit `mul`s. On modern CPUs that is sub-millisecond, comparable to a single ECDSA verify, so it is acceptable inside an evaluator. Per-block cap on `pm_place_bet` count (already discussed in §7.6 of the protocol spec) keeps total cost bounded.
+
+## 7. Spec Review — items still open
+
+These do not block the implementation, but I flag them for your review:
+
+### 7.1 Iteration counts (30 Taylor / 50 atanh / 100 binary)
+
+The numbers were chosen with 5–10× safety margin against the Q96 noise floor. **Question:** drop to 24/40/80 to save ~25% CPU? Cost is only "must be ≥ N" for correctness; choosing tighter risks one-ULP differences if a test vector ever lands on a boundary. **Recommendation:** keep current values; they are negligible cost and the safety margin is cheap insurance against an undiscovered edge case.
+
+### 7.2 Q96 vs Q64
+
+Q64 would halve the bigint width and roughly halve CPU cost. **Risk:** as discussed in §1.2, log-sum-exp accumulation can lose ~log2(N) bits, so for N=16 outcomes the safety margin in Q64 shrinks to ~50 bits — still enough but tight. **Recommendation:** Q96. If a future hardfork wants more outcomes (N>16), Q96 still works; Q64 wouldn't.
+
+### 7.3 Round-to-nearest-even in `exp` range reduction
+
+The "one banker's rounding" rule (§3.5) is the single non-truncating step. **Alternative:** use truncation everywhere, accepting that `exp(x)` near `x = k·ln(2)` rounds slightly worse on one side. Empirically this affects the last 1–2 milli-VIZ on `lmsr_buy_cost ≈ b/1000`. **Recommendation:** keep banker's; it's symmetric, free to implement, and avoids a pathological accumulation when `x` happens to be a multiple of `ln(2)/N` (an attacker can construct such inputs).
+
+### 7.4 Test-vector generation source
+
+Phase 2 (PHP+GMP) will generate `tests/lmsr_fixed_vectors.json`. PHP `gmp` uses `mpn_*` from GMP, which IS bit-deterministic and available on all platforms. We then verify JS produces the same. **Question:** do we want a third independent generator (e.g. Python `gmpy2`) as a tie-breaker? **Recommendation:** add it as a CI sanity check, but the PHP output is canonical.
+
+### 7.5 What to do with `module/lmsr_math.php` after migration
+
+Phase 5 replaces all call sites. The float file becomes dead code. **Recommendation:** delete it (single commit, easy to revert if anything breaks). Do NOT keep both implementations — that's the worst possible state because `float-LMSR` and `fixed-LMSR` will silently diverge for some `q`.
+
+### 7.6 Front-end performance (mobile)
+
+JS `BigInt` is ~50× slower than `Number`. A single `lmsr_buy_cost` is ~3000 `BigInt` ops ≈ 1–3 ms on desktop, ~10 ms on a low-end phone. **For UI estimates** (live odds while typing) this is acceptable. **For batch UI updates** (recompute all 10 outcome prices on every block) it might lag. **Mitigation:** debounce, or compute only the touched outcome, or fall back to a `Number`-based "estimate" that explicitly tells the user "displayed value, on-chain value may differ by ≤ 1 milli-VIZ". I prefer the BigInt path everywhere — see §7.5; mixing breeds bugs. **Recommendation: BigInt only**, debounce in app.js where needed.
+
+### 7.7 `lmsr_b_from_liquidity` consensus status
+
+This function is called once per market at creation; the result is stored on the market object. Strictly speaking the **node** can compute it deterministically and the **client** doesn't have to; the on-chain `b` is what matters. **Recommendation:** require the client to send `b` directly, validated by `ln_q` on the node:
+```
+require: ⌊liquidity / ln_q(N)⌋ == b
+```
+This way the consensus rule is just an integer equality check, not a re-computation. Off-chain (PHP/JS) we still expose `lmsr_b_from_liquidity` for the create-market UI.
+
+## 8. Implementation Phasing
+
+1. **Phase 1 (this document).** Spec frozen. ✓
+2. **Phase 2.** [`module/lmsr_fixed.php`](../module/lmsr_fixed.php) using `gmp_*`. Implements §2–§4. ✓
+3. **Phase 3.** [`market_math.js`](../market_math.js) BigInt port. Same names, same algorithm. ✓
+4. **Phase 4.** Cross-runner: [`tests/lmsr_vectors_generate.php`](../tests/lmsr_vectors_generate.php) + [`tests/lmsr_fixed_vectors_verify.php`](../tests/lmsr_fixed_vectors_verify.php) + [`tests/lmsr_fixed_vectors_verify.js`](../tests/lmsr_fixed_vectors_verify.js). All 77/77 PHP and 74/74 JS vectors pass with strict equality. ✓
+5. **Phase 5.** [`module/api.php`](../module/api.php), [`module/cron_worker.php`](../module/cron_worker.php), [`tests/workflow_test.php`](../tests/workflow_test.php), [`module/test_parimutuel.php`](../module/test_parimutuel.php) switched to `lmsr_fixed.php`; the float file `module/lmsr_math.php` deleted. ✓
+6. **Phase 6.** Update [protocol spec §7.2](viz-dlt-prediction-market-protocol-spec.md#72-floating-point-determinism-in-consensus-hardest) to "**Resolved** — see [lmsr-fixed-point-spec.md](lmsr-fixed-point-spec.md)". The C++ plugin author follows §6 of this document.
+
+After Phase 5 the prediction-market backend has no `float`/`double` in any pricing path; the only remaining FP code is in unrelated UI helpers.
+
+---
+
+🇷🇺 Russian translation will be added as `lmsr-fixed-point-spec-ru.md` after the English version is reviewed and frozen — translating drafts wastes effort.
diff --git a/.qoder/plans/pm-plan/onix-protocol-whitepaper.md b/.qoder/plans/pm-plan/onix-protocol-whitepaper.md
new file mode 100644
index 0000000000..41b8581f98
--- /dev/null
+++ b/.qoder/plans/pm-plan/onix-protocol-whitepaper.md
@@ -0,0 +1,661 @@
+# Onix Protocol: LP-Guaranteed Prediction Markets on VIZ DLT
+
+**An Industry Whitepaper**
+
+*Anatoly Piskunov (On1x)*
+*Version 2.0 — June 2026 (on-chain / HF14)*
+
+---
+
+> **On-chain status (HF14).** This paper was first written against the centralized prototype. The
+> protocol now runs as **first-class consensus operations (`pm_*`) on VIZ DLT**, verified in
+> `consensus_sim`. Live since HF14: both market types (CPMM binary + LMSR multi), the parimutuel
+> zero-sum settlement, the **Lazy Pool**, an opt-in **leverage** subsystem, opt-in **batch /
+> commit-reveal betting** (binary), bonded oracles, and a two-mode dispute system (committee /
+> account). All percentage parameters are **basis points (bp): 10000 = 100.00%** (the prototype used
+> permille). Sections below are annotated where the live design differs from the original prototype text.
+
+## Abstract
+
+Prediction markets aggregate dispersed information into prices, producing probability estimates that consistently outperform polls, expert panels, and statistical models. Yet adoption remains constrained by a single structural problem: **liquidity providers lose money.**
+
+Uniswap v3 LPs suffer impermanent loss. LMSR market makers risk their entire subsidy. CLOB market makers face adverse selection. Every existing model asks capital providers to accept downside risk in exchange for uncertain yield — and the data shows most of them lose.
+
+The **Onix Protocol** eliminates LP risk entirely. It is a prediction market architecture where LP principal is **structurally guaranteed** — not by insurance, not by hedging, but by the payout mechanics themselves. Winners are paid exclusively from losers' forfeited stakes. LP capital provides market depth but is never used to settle bets.
+
+This paper describes the Onix Protocol's two market types — **Onix Binary** (Constant Product Market Maker) and **Onix Multi** (LMSR pricing with parimutuel settlement) — along with the market architecture, oracle and dispute resolution system, lazy liquidity pool, opt-in leverage, governance model, and its implementation on VIZ DLT as consensus-level operations.
+
+---
+
+## 1. The Problem: LP Risk in Prediction Markets
+
+Every prediction market needs liquidity. Without it, prices are meaningless — a bet that moves the market by 20% reveals the bettor's capital, not the crowd's wisdom. The fundamental question is: **who provides that liquidity, and what do they risk?**
+
+### 1.1 The Current Landscape
+
+| Platform | LP Model | LP Risk | Yield Source |
+|----------|----------|---------|-------------|
+| **Uniswap v3** | Concentrated AMM | Impermanent loss (often >5% annualized; >50% of v3 LPs underperform buy-and-hold) | Trading fees |
+| **Aave / Compound** | Lending pool | Smart contract risk, liquidation cascades | Borrower interest |
+| **Curve** | Stableswap AMM | Low IL for pegged assets, smart contract risk | Fees + CRV emissions |
+| **Standard LMSR** | Market maker subsidy | Loss up to `b × ln(N)` — the entire subsidy | Bid-ask spread |
+| **Polymarket (CLOB)** | Active market making | Inventory risk, adverse selection | Bid-ask spread |
+| **Kalshi** | No LP concept | N/A (exchange model) | N/A |
+
+The pattern is clear: providing liquidity to prediction markets requires either active management skill (CLOB), tolerance for capital loss (LMSR), or acceptance of impermanent loss (AMM). None of these are suitable for retail participants.
+
+### 1.2 Why This Matters
+
+Prediction markets work best when they are deep and liquid. Deep markets produce accurate prices, attract informed traders, and generate the information value that makes prediction markets useful as a public good. But depth requires capital, and capital requires compensation for risk.
+
+The result is a chicken-and-egg problem:
+- Thin markets → high slippage → poor UX → few bettors → low fees → no LP incentive → thin markets
+
+Breaking this cycle requires removing the risk from the LP side of the equation. If providing liquidity is risk-free, the barrier to entry drops to zero, and the flywheel can start spinning.
+
+---
+
+## 2. The Onix Protocol
+
+### 2.1 Design Principles
+
+The Onix Protocol is built on three architectural invariants:
+
+1. **LP principal is structurally safe.** This is not a risk-mitigation strategy — it is a property of the payout architecture. LP capital and bet settlement draw from physically separate pools.
+
+2. **Losers fund winners.** All payouts (winner profits, oracle fees, creator fees, LP fees) are sourced exclusively from losers' forfeited stakes. Fees are computed at resolution as `floor(losers_sum × fee_bp / 10000)` (bp: 10000 = 100.00%), never deducted at bet time.
+
+3. **Dual market types, single guarantee.** Binary markets (Onix Binary) and multi-outcome markets (Onix Multi) use different pricing formulas but share the same settlement model and the same LP guarantee.
+
+### 2.2 Onix Binary (CPMM + Parimutuel Settlement)
+
+Onix Binary uses the Constant Product Market Maker formula — the same `x * y = k` invariant used by Uniswap — as the **pricing engine** for binary outcomes, with **parimutuel settlement** (losers fund winners pro-rata by weight), the same settlement model as Onix Multi.
+
+**Mechanics:**
+
+A market maintains two reserves, `reserve_a` and `reserve_b`, with constant product `k`:
+
+```
+k = reserve_a × reserve_b
+```
+
+When a user bets `amount` on outcome A (in the implementation, side 0 → `reserve_a`), the stake enters
+that side's reserve and tokens are drawn from the **opposing** reserve:
+
+```
+new_reserve_a = reserve_a + amount
+new_reserve_b = floor(k / new_reserve_a)
+tokens_received = reserve_b − new_reserve_b
+```
+
+The `tokens_received` (called `weight`) is the user's **relative claim** on the winners' pool if outcome A wins (settlement is parimutuel — see below, identical to Onix Multi). Implied probability rises for the side that is bet (more money on A → `reserve_a` grows → `P(A)` grows):
+
+```
+P(A) = reserve_a / (reserve_a + reserve_b)
+P(B) = reserve_b / (reserve_a + reserve_b)
+```
+
+**Settlement and proof of LP safety (parimutuel):**
+
+At resolution, winners receive their stake back plus a proportional share of the losers' pool, by weight (identical to Onix Multi):
+
+```
+winners_pool = losers_sum − fees
+payout = bet_amount + (weight / total_winning_weight) × winners_pool − time_penalty_on_profit
+```
+
+LP principal `L` is returned unconditionally, and the guarantee is exact:
+
+```
+Money OUT = L + winning_bets + winners_pool + fees = L + winning_bets + losing_bets = L + all_bets = Money IN
+```
+
+Total payout is capped at `losers_sum` regardless of weights, so LP capital is never used to settle bets. The CPMM is the **pricing engine** (probability + weight); it does not gate payout. (The AM-GM relation `reserve_a + reserve_b ≥ 2√k = L` still holds for the pricing curve but is no longer relied upon for solvency.)
+
+**Worked example:**
+
+```
+Setup: 200 VIZ liquidity → reserve_a = 100, reserve_b = 100, k = 10,000
+Fees (bp): oracle 50 (0.5%), creator 50 (0.5%), liquidity 100 (1%)
+
+Alice bets 50 VIZ on A → receives weight 33.33 (price moves from 50% to 69%)
+Bob bets 80 VIZ on B → receives weight 81.82
+
+Resolution: A wins
+ Losers (Bob): 80 VIZ forfeited → losers_sum = 80
+ oracle_fee = floor(80 × 50/10000) = 0.4 VIZ
+ creator_fee = floor(80 × 50/10000) = 0.4 VIZ
+ liq_fee = floor(80 × 100/10000) = 0.8 VIZ
+ winners_pool = 80 − 1.6 = 78.4 VIZ
+
+ Alice (only winner, weight 33.33 of 33.33):
+ payout = 50 (stake) + 78.4 × (33.33/33.33) = 128.4 VIZ (minus any time penalty on profit)
+ LP return: 200 VIZ principal + share of 0.8 VIZ fee pool
+```
+
+### 2.3 Onix Multi (LMSR + Parimutuel Settlement)
+
+Onix Multi is the protocol's innovation for markets with 3–10 outcomes. It combines Hanson's Logarithmic Market Scoring Rule (LMSR, 2003) for real-time pricing with parimutuel settlement for LP safety.
+
+**Pricing (LMSR softmax):**
+
+For a market with outcomes {1, 2, ..., N}, each with quantity parameter `q_i`:
+
+```
+price(i) = exp(q_i / b) / Σ_j exp(q_j / b)
+```
+
+This is the softmax function — prices always sum to exactly 1.0 by construction. No arbitrage mechanism or split/merge operation is needed.
+
+The cost to buy Δ tokens on outcome i:
+
+```
+C(q) = b × ln(Σ_j exp(q_j / b))
+
+cost = C(q + Δ·e_i) − C(q)
+```
+
+The parameter `b` controls price sensitivity (higher b = less price impact per bet). It is funded by the LP subsidy: `b = S / ln(N)` where S is the total subsidy.
+
+**The innovation — parimutuel settlement:**
+
+In **standard LMSR**, the market maker is the counterparty to all bets. If the crowd correctly predicts the outcome, the market maker loses up to `b × ln(N)` — potentially the entire subsidy. This is why LMSR has seen limited adoption outside corporate prediction markets (Microsoft, Inkling) where the operator absorbs the loss.
+
+**Onix Multi changes the payout source.** At resolution:
+
+```
+1. Oracle declares the winning outcome
+2. Losers forfeit 100% → losers_sum
+3. Fees deducted from losers_sum (bp; 10000 = 100.00%):
+ oracle_fee = floor(losers_sum × oracle_fee_bp / 10000)
+ creator_fee = floor(losers_sum × creator_fee_bp / 10000)
+ liq_fee = floor(losers_sum × liquidity_fee_bp / 10000)
+ winners_pool = losers_sum − fees
+4. Winners receive:
+ payout = bet_amount + (tokens / total_winning_tokens × winners_pool) − time_penalty
+5. LP subsidy returned unconditionally
+```
+
+Winners are paid by losers, not by the LP. The subsidy is architecturally separate from the settlement flow.
+
+**Proof of LP principal guarantee:**
+
+1. The LP deposits `S` VIZ as subsidy, which funds market depth.
+2. During betting, users pay VIZ → receive outcome tokens. The VIZ accumulates as the betting pool.
+3. At resolution, losers' forfeited stakes fund winner payouts and fees. The subsidy `S` was never in the payout pool.
+4. The subsidy is returned to the LP unconditionally, regardless of outcome.
+
+**Comparison:**
+
+| Dimension | Standard LMSR | Onix Multi |
+|-----------|---------------|------------|
+| LP role | Counterparty to all bets | Depth deposit (not counterparty) |
+| LP max loss | `b × ln(N)` (entire subsidy) | **Zero** |
+| Winner payout | 1 token = 1 unit of currency | Token = proportional claim on losers' pool |
+| CTF split/merge needed? | Yes (enforce price sum = 1) | No (softmax guarantees it) |
+
+**Worked example (3-outcome election):**
+
+```
+Setup: b = 1000, outcomes = [A, B, C], subsidy = 1000 VIZ
+Initial: price(A) = price(B) = price(C) = 33.3%
+
+Alice bets 50 VIZ on A → ~47 tokens (price: 33% → ~38%)
+Bob bets 100 VIZ on B → ~88 tokens
+Carol bets 30 VIZ on C → ~29 tokens
+
+Resolution: A wins
+ Losers: Bob (100) + Carol (30) = 130 VIZ
+ Fees (200 bp = 2% total): 2.6 VIZ
+ winners_pool = 127.4 VIZ
+
+ Alice: 50 + (47/47 × 127.4) = 177.4 VIZ
+ LP: 1000 VIZ returned in full + share of liquidity fees
+```
+
+### 2.4 Edge Cases
+
+| Scenario | Outcome |
+|----------|---------|
+| All bets on the winner | `losers_sum = 0` → every bettor gets back exactly their bet amount. LP subsidy returned. Zero-sum. |
+| No bets on the winner | Entire losers' pool is undistributed → LP bonus. LP profits maximally. |
+| Zero-volume market | LP subsidy returned in full. No fees, no payouts. |
+| Single bettor wins | That bettor receives `bet_amount + winners_pool`. LP subsidy returned. |
+
+---
+
+## 3. Market Architecture
+
+### 3.1 Market Lifecycle
+
+```
+┌────────────┐ oracle accepts ┌──────────┐ betting expires ┌──────────┐
+│ Waiting (0) │ ───────────────► │ Active (1) │ ────────────────► │ Closed (2) │
+└────────────┘ └──────────┘ └──────────┘
+ │ │ │
+ │ oracle rejects │ early resolution │ oracle resolves
+ ▼ │ (if allowed) ▼
+┌────────────┐ └──────────────────────► ┌──────────────┐
+│ Deleted (-1) │ │ Resolved (3) │
+└────────────┘ └──────────────┘
+ │
+ grace period (12h)
+ ▼
+ ┌───────────┐
+ │ Paid out │
+ └───────────┘
+```
+
+Markets are created by a market creator, reviewed and accepted by an oracle (who stakes insurance), open for betting, resolved with an outcome, and paid out after a dispute grace period.
+
+### 3.2 Fee Model (Losers-Only Fee Extraction)
+
+A distinctive feature of the Onix Protocol is that **no fees are deducted at bet time**. The full bet amount enters the market reserves. Fees are computed only at resolution, exclusively from the losing side's forfeited stakes:
+
+```
+losers_sum (100% of losing bets)
+ ├─ oracle_fee = floor(losers_sum × oracle_fee_bp / 10000)
+ ├─ creator_fee = floor(losers_sum × creator_fee_bp / 10000)
+ ├─ liquidity_fee = floor(losers_sum × liquidity_fee_bp / 10000)
+ └─ winners_pool = losers_sum − all fees
+```
+
+This provides a structural guarantee: fees and winner payouts draw from completely separate funding sources. Fee extraction can never compete with winner obligations.
+
+**Oracle fee terms are frozen at acceptance (offer→quote).** The creator publishes a *maximum* the oracle may charge (`oracle_fee_percent` ceiling in bp + `oracle_fixed_fee` ceiling); when the oracle accepts it quotes its actual terms (≤ the creator's ceiling and ≤ the median governance cap `pm_max_oracle_fee_percent`), which are frozen onto the market and a `pm_market_accepted` virtual op is emitted. A self-oracle freezes its terms at creation. The **oracle fixed fee** (per-market) is paid from the losers' pool remainder (never minted). A **market creation fee** goes to the DAO fund as anti-spam protection.
+
+### 3.3 Time-Weighted LP Distribution
+
+LP fee shares are distributed proportionally to `amount × max(1, seconds_to_expiration)`:
+
+```
+weight_i = amount_i × max(1, sec_to_expiration_i)
+fee_share_i = floor(total_fee_pool × weight_i / Σ weight_j)
+```
+
+Early LPs earn dramatically more per unit of capital than late LPs. In a 48-hour market, an LP who deposits at hour 1 earns ~2,400x more per VIZ than one who deposits at hour 47.
+
+Each deposit is tracked as an independent position — multiple deposits by the same user are weighted and paid separately. LP principal is always returned in full, regardless of market outcome.
+
+### 3.4 Time Penalty for Late Bets
+
+To discourage last-minute betting (which carries less uncertainty risk), a configurable time penalty applies to bets placed near expiration:
+
+```
+if time_to_expiration < penalty_window:
+ ratio = 1 − (time_to_expiration / penalty_window)
+ penalty_ratio = ratio² // quadratic (default)
+ time_penalty = floor(penalty_ratio × max_penalty)
+```
+
+The penalty applies **only to profit**, never principal. A winning bettor always receives at least their original stake. The quadratic curve is gentle early in the penalty window and steep late, rewarding "somewhat late" over "extremely late."
+
+### 3.5 Position Transfers
+
+Positions are transferable between accounts via a native protocol operation:
+
+```
+pm_transfer_position { bet_id, to_user, amount, memo }
+```
+
+No slippage, no market impact — pure record reassignment. The `memo` field supports both plaintext and encrypted modes (ECIES via VIZ account memo keys), enabling P2P deals, OTC trading, and private annotations.
+
+This is the only composability feature from the Conditional Tokens Framework (Polymarket/Gnosis) that provides real user value. CTF split/merge is architecturally unnecessary — Onix pricing formulas guarantee price coherence by construction.
+
+---
+
+## 4. Oracle and Dispute Resolution
+
+### 4.1 Bonded Oracle Model
+
+Oracles in the Onix Protocol are not trusted by default — they are **bonded**. Each oracle must:
+
+- Register with a one-time fee (default 10 VIZ)
+- Deposit insurance (minimum 5,000 VIZ)
+- Accept markets explicitly (staking their insurance on each acceptance)
+- Resolve markets with an outcome and supporting evidence (decision URL)
+
+The insurance bond creates accountability: oracles who misresolve, miss deadlines, or lose disputes have their insurance slashed. The bond must exceed the oracle's potential manipulation profit for the economic security model to hold.
+
+Oracle revenue comes from two sources:
+1. **Fixed fee** (per market) — compensates for staking insurance and providing resolution
+2. **Percentage fee** (from losers' pool at resolution) — scales with market volume
+
+### 4.2 Dispute Arbitration
+
+Any bettor can challenge a resolution within a grace period by paying a dispute fee. During disputes, all payouts are frozen.
+
+Resolution runs in one of **two per-market modes**, chosen at creation:
+
+- **Committee mode (`dispute_mode = 0`, default)** — the *whole SHARES electorate* decides by
+ **stake-weighted vote** (`pm_dispute_vote`), tallied deterministically by the `pm_dispute_finalize`
+ cron at `voting_end_time`. It is an **open public hearing**: the live tally is queryable and votes are
+ **not** hidden behind commit-reveal (a deliberate, permanent choice — the DAO resolves disputes as
+ transparently as possible). Because new evidence surfaces during the hearing, **a ballot is revisable**
+ until close (a repeat vote overwrites the prior one). A voter's weight is its `effective_vesting_shares`
+ **plus its Lazy-Pool stake converted to vesting-shares**, so DAO members who park VIZ in the pool keep
+ their governance weight.
+- **Account mode (`dispute_mode = 1`)** — a single named `dispute_resolver` (recommended multisig)
+ issues the verdict (`pm_dispute_resolve`).
+
+The verdict logic in either mode:
+
+**If the oracle was wrong:**
+- The correct outcome is applied and payouts recalculated
+- The disputer receives their fee back plus a reward from oracle insurance
+- The resolver receives a share of the reward pool
+- The oracle's insurance is slashed, with optional additional penalty and ban
+
+**If the oracle was right:**
+- The disputer loses their fee (50% to resolver, 50% to oracle as compensation)
+- Original payouts proceed unchanged
+
+**Denial-of-resolution prevention:** If the resolver fails to act within 14 days, disputes auto-close: all bets and LP are refunded, the oracle is penalized, and the disputer's fee is returned. This guarantees funds are never frozen indefinitely.
+
+### 4.3 Oracle Reputation Scoring
+
+The protocol tracks 14 on-chain metrics per oracle and computes a reliability score (0–100):
+
+```
+reliability_score = clamp(0, 100,
+ 50 (base)
+ − 0.40 × dispute_loss_rate × 100
+ − 0.10 × excess_no_contest × 100
+ − 0.20 × deadline_miss_rate × 100
+ − 0.15 × (1 − dispute_response_rate) × 100
+ + volume_bonus (0–25)
+ + experience_bonus × freshness_multiplier (0–25)
+ − 15 × bans_received
+)
+```
+
+Key design choices:
+- **Rates, not counts** — 1 dispute lost out of 100 (1%) scores better than 1 out of 2 (50%)
+- **Neutral start at 50** — new oracles must earn reputation, not start at 100
+- **Freshness decay** — inactive oracles lose their experience bonus over time
+- **Volume tiers** — high-volume oracles receive bonus points for proven track record
+
+The reliability score combines with a risk factor (insurance-to-bets ratio) to produce a **composite trust score** — the primary metric shown to users.
+
+### 4.4 No-Contest and 3-Outcome Resolution
+
+An oracle who cannot verify an outcome can voluntarily declare **no-contest**, triggering refunds at a reduced penalty (50% of dispute fee from insurance — much cheaper than losing a dispute). This creates an incentive gradient:
+
+| Scenario | Oracle Cost | Ban Risk |
+|----------|------------|----------|
+| Voluntary no-contest | 500 VIZ | None |
+| Dispute loss | 1,000+ VIZ + extra penalty | Permanent or temporary |
+| Missed deadline | 250 VIZ (auto-penalty) | None (but reputation damage) |
+
+If users believe the oracle abused no-contest, they can dispute it. The resolver then chooses from **three** possible correct outcomes: A wins, B wins, or confirm no-contest. This prevents oracles from using no-contest to avoid paying out winning bettors.
+
+---
+
+## 5. Lazy Liquidity Pool
+
+### 5.1 The Capital Deployment Problem
+
+Individual LP provision requires active market selection. Most users won't manually evaluate and deposit into specific markets. The result: most markets launch with only the creator's initial liquidity, producing thin order books and high slippage.
+
+### 5.2 Automated Pool-to-Market Allocation
+
+The Lazy Liquidity Pool solves this by accepting deposits and **automatically allocating** a percentage of the pool's free balance to every new market when it activates:
+
+```
+alloc_amount = free_balance × allocation_percent / 100
+```
+
+Allocations are computed from the current free balance (not the original total), creating geometric decay — the pool can never be fully depleted:
+
+```
+After 50 markets (2% allocation each): ~357 VIZ free from original 1,000
+After 100 markets: ~133 VIZ still free
+```
+
+A maximum total allocation cap (default 70%) provides additional safety.
+
+### 5.3 Reward Distribution (MasterChef Pattern)
+
+Rewards are distributed using a single global accumulator — `reward_per_share` — enabling O(1) reward calculation regardless of participant count:
+
+```
+// When a market resolves with pool LP profit:
+pool.reward_per_share += profit × PRECISION / total_shares
+
+// Any user can compute their live rewards:
+live_reward = pending + shares × (pool.rps − user.snapshot) / PRECISION
+```
+
+User records are only updated when that user acts (deposit or withdraw). This is the same "lazy accounting" pattern used by SushiSwap's MasterChef and Compound's cToken model.
+
+### 5.4 Opportunity-Cost Protection
+
+The pool auto-allocates to every market, creating an attack vector: a malicious oracle could create long-duration zero-volume markets to lock pool capital. Three mechanisms address this:
+
+**Graduated Early Recall:** The market's duration is divided into 10 steps. At each step, if betting volume is below a threshold (1% of allocation), 10% of the current allocation is recalled to the pool. A completely idle 30-day market loses ~60% of its allocation.
+
+**Active Market Penalty:** Each additional active market from the same oracle reduces that oracle's allocation by 5% (recursive). An oracle with 10 active markets receives ~60% of the base allocation per market, incentivizing quality over quantity.
+
+**Fault Penalty Stamps:** Bad outcomes (missed deadlines, disputes lost, zero-volume resolutions) generate penalty stamps that further reduce future allocations. Stamps auto-expire after 10 days of clean operation.
+
+### 5.5 Opt-In Leverage (Lazy-Pool-Funded)
+
+The Lazy Pool serves **two roles from one `free_balance`**: silent market-LP allocations *and* funding for
+an **opt-in leverage** subsystem. A bettor can open a leveraged position (`pm_leverage_open`) where margin
+is a **loan from the pool** — no token emission, the position stays fully collateralized from the system's
+view. The binary "jump risk" that breaks CLOB liquidation engines is handled by **liquidating against
+pre-bet reserves**: an opposing-bet or settlement force-close recovers `min(cancel_value, obligation) ≥
+loan`, so the pool gets its loan plus interest back; the only bounded bad-debt path is a same-side
+`pm_cancel_bet`. A median kill-switch (`pm_leverage_enabled`, default off) blocks *new* opens, but the
+protective liquidation cascade is deliberately **not** gated by it — toggling leverage off never strips
+protection from open positions. The pool earns leverage interest in addition to LP yield, accounted via the same shared `reward_per_share` accumulator as in §5.3. Settlement of pool loans emits `pm_leverage_resolve` / `pm_leverage_liquidate`.
+
+---
+
+## 6. Governance
+
+### 6.1 Delegate-Voted Chain Parameters
+
+VIZ uses Delegated Proof of Stake (DPoS) consensus where elected delegates (validators) govern chain parameters through a median-vote mechanism:
+
+1. Each delegate publishes preferred values for all parameters
+2. The network computes the **median** of all active delegates' votes
+3. Parameters change automatically when the median shifts — no hard fork, no deployment
+
+All prediction-market parameters (fees, penalties, insurance requirements, dispute windows, lazy-pool
+settings, leverage knobs, batch/commit-reveal timing) are delegate-voted. All percentage parameters are
+in **basis points (bp), 10000 = 100.00%**:
+
+| Examples | Governance |
+|----------|-----------|
+| `pm_dispute_fee`, `pm_max_oracle_fee_percent` (bp) | Delegate median vote |
+| `pm_dispute_grace_sec`, `pm_dispute_vote_period_sec` | Delegate median vote |
+| `pm_dispute_approve_min_percent`, `pm_dispute_reward_multiplier` (bp) | Delegate median vote |
+| `pm_lazy_*` allocation/recall, `pm_leverage_*` (enabled, fund %, max position) | Delegate median vote |
+| `pm_commit_reveal_enabled`, `pm_batch_epoch_blocks`, `pm_reveal_window_blocks` | Delegate median vote |
+
+Hard forks are only needed for structural changes (new operation types, formula changes), not for economic tuning. Kill-switches (`pm_leverage_enabled`, `pm_commit_reveal_enabled`) let governance disable a whole subsystem by median vote without a fork.
+
+### 6.2 Jurisdictional Client Model
+
+VIZ DLT is infrastructure, not an operator — analogous to how Bitcoin is a ledger, not a money transmitter. The protocol is neutral and permissionless. Legal obligations attach to **client applications**, not to the consensus algorithm.
+
+Any jurisdiction can build a compliant client on VIZ DLT:
+
+| Client Component | Implementation |
+|-----------------|---------------|
+| Pre-approved oracles | Client whitelist of licensed, KYC-verified oracles |
+| Pre-approved resolvers | Government-approved dispute resolution bodies |
+| KYC/AML | Client-level identity verification |
+| Fee routing as tax revenue | `dao_fund_account_id` → state treasury account |
+| Market restrictions | Client filters by allowed categories |
+| Betting limits | Client-enforced per-user caps |
+
+The same protocol operations (`pm_place_bet`, `pm_resolve`, `pm_dispute`) work identically for permissionless and regulated clients. The difference is entirely at the client layer.
+
+---
+
+## 7. Competitive Landscape
+
+### 7.1 Platform Comparison
+
+| Dimension | Onix (Forecaster) | Polymarket | Kalshi | Standard LMSR |
+|-----------|-------------------|------------|--------|---------------|
+| **Pricing** | CPMM (binary) / LMSR softmax (multi) | CLOB | CLOB | LMSR |
+| **LP Risk** | **Zero** (structural guarantee) | Inventory risk | N/A | Up to `b × ln(N)` |
+| **LP Knowledge** | Low (deposit and earn) | High (manage orders) | N/A | Medium |
+| **Fee Model** | % of losers' pool at resolution | Bid-ask spread | Exchange fees (1-7%) | Spread |
+| **Oracle** | Per-market bonded + committee dispute | UMA Optimistic Oracle | Kalshi (CFTC-regulated) | Operator |
+| **Late Bet Penalty** | Quadratic, configurable | None | None | None |
+| **Position Transfer** | Native protocol operation + encrypted memo | CTF (ERC-1155) | None | None |
+| **Governance** | Delegate-voted parameters | Team multisig | CFTC process | Operator |
+| **Infrastructure** | VIZ DLT (consensus-level) | Polygon (smart contracts) | Proprietary servers | Various |
+
+### 7.2 Why CTF Split/Merge Is Unnecessary
+
+Polymarket uses the Gnosis Conditional Tokens Framework (CTF) where positions are ERC-1155 tokens that can be split and merged to enforce price coherence (prices sum to $1).
+
+Under the Onix Protocol, this mechanism is architecturally unnecessary:
+
+- **Onix Binary (CPMM):** `price(A) + price(B) = reserve_b/(reserve_a+reserve_b) + reserve_a/(reserve_a+reserve_b) = 1` — by definition
+- **Onix Multi (LMSR softmax):** `Σ price(i) = Σ exp(q_i/b) / Σ exp(q_j/b) = 1` — by definition of softmax
+
+No arbitrage mechanism needed. Price coherence is a mathematical property of the formulas, not an external enforcement layer.
+
+### 7.3 The Flywheel
+
+```
+Risk-free LP → lower barrier for retail LPs
+ → more liquidity deposited
+ → deeper markets, less slippage
+ → better UX for bettors
+ → more volume
+ → more fees for LPs
+ → attracts even more LPs
+```
+
+"Passive yield without impermanent loss" is the value proposition that Uniswap, Balancer, and Curve cannot offer. For the crypto-native audience, this is a compelling narrative: earn yield by providing liquidity to prediction markets, with zero risk to principal.
+
+---
+
+## 8. VIZ DLT: From Prototype to Protocol
+
+### 8.1 Current State
+
+The protocol began as a Telegram WebApp with a centralized backend (all market logic server-side) — a working prototype with known limits: no sybil resistance beyond Telegram accounts, no censorship resistance, no composability. **That migration is now done:** the full market logic runs **on VIZ DLT as consensus-validated `pm_*` operations** (HF14), exercised end-to-end in `consensus_sim`. The remainder of this section describes that on-chain architecture, now realized.
+
+### 8.2 Migration Architecture
+
+VIZ DLT is a Distributed Ledger Technology with ~3-second block times, DPoS consensus, named accounts (Graphene-style), and no general-purpose smart contracts. Prediction market operations will be implemented as **first-class consensus-validated operations** — not smart contracts, not `custom_json` payloads.
+
+| Layer | Examples | Consensus-Validated? |
+|-------|---------|---------------------|
+| **Protocol operations** | `pm_create_market`, `pm_oracle_accept_market`, `pm_place_bet`, `pm_commit_bet`/`pm_reveal_bet`, `pm_resolve_market`, `pm_dispute_create`/`pm_dispute_vote`/`pm_dispute_resolve`, `pm_lazy_deposit`/`pm_lazy_withdraw`, `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert` | Yes — every node validates |
+| **Virtual operations** | `pm_payout` (per bettor), `pm_auto_payout`, `pm_market_accepted`, `pm_dispute_finalize`, `pm_dispute_auto_close`, `pm_oracle_missed_penalty`, `pm_lazy_recall`, `pm_batch_settle`, `pm_commit_forfeit`, `pm_leverage_resolve`/`pm_leverage_liquidate` | Yes — deterministic, generated at block time |
+| **metadata / custom_json** | Dispute comments, market descriptions, UI metadata | No — display/indexing only |
+
+Every financial action (placing bets, adding liquidity, resolving markets, seizing insurance) is validated by every validator. Invalid operations are rejected before block inclusion. No Solidity, no gas estimation, no bytecode deployment.
+
+The frontend is a **fully headless web client** — no backend server, no database, no sessions. Private keys stored in the browser (encrypted), transactions signed locally and broadcast to public VIZ nodes. No Telegram dependency; the core app is platform-independent.
+
+### 8.3 Delivered Since the Prototype, and Remaining Roadmap
+
+**Delivered on-chain (HF14):**
+
+| Feature | Status |
+|---------|--------|
+| Commit-reveal + batch betting (binary, opt-in, median kill-switch) | ✅ Live |
+| Opt-in leverage (Lazy-Pool-funded, pre-bet-reserve liquidation) | ✅ Live |
+| Lazy Pool (auto-allocation, graduated recall, MasterChef accounting) | ✅ Live |
+| Per-bettor / leverage settlement virtual ops + plugin API | ✅ Live |
+| Lazy-pool stake as governance weight (PM disputes + DAO requests) | ✅ Live |
+
+**Remaining roadmap:**
+
+| Priority | Feature | Impact |
+|----------|---------|--------|
+| High | Shared liquidity pools (category-level AMMs) | Solves liquidity fragmentation at the architecture level |
+| High | Automated data oracles (exogenous feeds) | Eliminates manipulation for objective markets |
+| Medium | Tiered dispute windows (small vs large markets) | Better UX calibration |
+| Medium | LMSR batch settlement (extend batch/commit-reveal to multi) | Multi markets currently force instant betting |
+| — | Commit-reveal **dispute** voting | **Deliberately rejected** — disputes stay public hearings (see §4.2) |
+
+---
+
+## 9. What Onix Does NOT Claim
+
+Honest disclosure of tradeoffs and limitations:
+
+- **LP profit is not guaranteed.** If a market has zero losing bets, there are no fees to distribute. LP gets principal back but earns nothing.
+
+- **Platform risk exists.** Bugs, exploits, and governance attacks are separate from the market maker model. The LP guarantee is structural (payout architecture), not insured (no external guarantee fund).
+
+- **Onix Multi tokens are not fixed-value instruments.** In standard LMSR, 1 winning token = 1 unit of currency. In Onix Multi, tokens are proportional claims on the losers' pool. If all bettors pick the winner, everyone breaks even.
+
+- **LP yield depends on volume, not depth.** A market with 100,000 VIZ subsidy and one with 1,000 VIZ subsidy earn the same absolute fee if both have identical betting volume and fee rates. The subsidy provides depth, not yield.
+
+- **DPoS governance has known tradeoffs.** Fewer validators than PoW/PoS, delegate concentration risks, token-weighted voting. These are inherent to the DPoS model (shared by EOS, Hive, Tron), not VIZ-specific.
+
+- **VIZ token liquidity is currently low.** Economic guarantees (insurance bonds, dispute fees) scale with token price. The protocol assumes that utility drives demand over time — the same bet every protocol-native token project makes.
+
+---
+
+## 10. Conclusion
+
+The Onix Protocol addresses the fundamental barrier to prediction market adoption: LP risk. By structurally separating LP capital from bet settlement — in both binary (CPMM) and multi-outcome (LMSR + parimutuel) markets — Onix makes liquidity provision risk-free and accessible to retail participants.
+
+The key innovations:
+
+1. **LP principal guarantee** as an architectural invariant, not insurance
+2. **Losers-fund-winners** settlement eliminating fee competition with winner payouts
+3. **LMSR pricing with parimutuel settlement** (Onix Multi) — combining proven price discovery with LP safety
+4. **Time-weighted LP distribution** rewarding early capital commitment
+5. **Quadratic time penalty** on profit (never principal) for late bets
+6. **Lazy Liquidity Pool** with automated allocation, graduated recall, and MasterChef accounting — also funding the opt-in **leverage** subsystem (pool-funded margin, pre-bet-reserve liquidation, never bad debt outside a bounded cancel-bet path)
+7. **Bonded oracle model** with reputation scoring, offer→quote fee freezing, and a two-mode dispute system — committee disputes are **public hearings** with revisable, lazy-pool-weighted votes
+8. **Opt-in anti-MEV** — batch / commit-reveal betting (binary) with a median kill-switch
+9. **Consensus-level implementation** on VIZ DLT — no smart contracts, no gas, no external keepers; strictly **zero-sum** (the protocol never mints a token)
+
+The bet is straightforward: if zero-risk LP attracts capital, capital creates depth, depth improves prices, and prices attract bettors, then the Onix Protocol solves the prediction market liquidity problem. The protocol mechanics are mathematically verifiable. The economic hypothesis will be tested by the market.
+
+---
+
+## 11. Author and Disclosure
+
+### Author
+
+**Anatoly Piskunov** (On1x) — Russian IT innovator, Web3/DLT developer, and creator of the VIZ blockchain. His work spans distributed ledger technology, decentralized social protocols, and economic models for digital communities.
+
+Key contributions include: VIZ Blockchain (Fair DPoS, social capital primitives), the Onix Protocol (LP-guaranteed prediction markets), Voice Protocol (censorship-resistant messaging), and extensive publications on blockchain economics and Web3 architecture.
+
+Full list of publications and projects: [https://on1x.com](https://on1x.com)
+
+### Disclosure
+
+The author of Forecaster and the Onix Protocol is also the creator of VIZ DLT. The migration roadmap proposes moving the platform to a blockchain the author designed and built.
+
+This is disclosed upfront. It is also the norm: Polymarket depends on Polygon Labs' infrastructure, Kalshi runs on its own servers, Augur designed the REP token it runs on. Every platform argues for its own infrastructure. The question is not whether the author has an interest — they always do — but whether the technical claims are falsifiable. Every formula, proof, and mechanism in this paper is mathematically verifiable and the codebase is open-source.
+
+---
+
+## 12. References
+
+1. Hanson, R. (2003). *Combinatorial Information Market Design.* Information Systems Frontiers, 5(1), 107–119. — The Logarithmic Market Scoring Rule (LMSR).
+
+2. Adams, H., Zinsmeister, N., Robinson, D. (2020). *Uniswap v2 Core.* — Constant Product Market Maker (`x * y = k`).
+
+3. Adams, H., et al. (2021). *Uniswap v3 Core.* — Concentrated liquidity and impermanent loss analysis.
+
+4. Gnosis. *Conditional Tokens Framework (CTF) Documentation.* https://docs.gnosis.io/conditionaltokens/ — ERC-1155 prediction market positions.
+
+5. UMA Protocol. *Optimistic Oracle Documentation.* — Dispute escalation mechanism used by Polymarket.
+
+6. Leshner, R., Hayes, G. (2019). *Compound: The Money Market Protocol.* — cToken accumulator pattern (basis for reward_per_share).
+
+7. SushiSwap. *MasterChef Contract.* — Lazy accounting pattern for reward distribution.
+
+8. Piskunov, A. (2019). *VIZ blockchain system: technical description.* — VIZ DLT architecture, DPoS consensus, named accounts.
+
+9. Piskunov, A. (2019). *What is Fair DPoS.* — Governance innovation in delegated proof of stake.
+
+10. Piskunov, A. (2023). *VIZ as a Digital Representative Self-Governing State.* — Framework for blockchain systems as digital polities.
diff --git a/.qoder/plans/pm-plan/parimutuel-settlement.md b/.qoder/plans/pm-plan/parimutuel-settlement.md
new file mode 100644
index 0000000000..3d40b81abf
--- /dev/null
+++ b/.qoder/plans/pm-plan/parimutuel-settlement.md
@@ -0,0 +1,106 @@
+# Plan: Unified Parimutuel Settlement (make Binary settle like Multi)
+
+> **STATUS: ✅ IMPLEMENTED.** Binary now settles parimutuel in [api.php](module/api.php) (resolve block ~1661 + dispute-recalc block ~2105), and the docs (spec §5/§8/§10, whitepaper §2.2, betting-rules, governance roadmap) are updated. This file is retained as the design rationale.
+>
+> Make **Onix Binary** use the same **parimutuel settlement** as Onix Multi: a winner's payout becomes a *proportional share of the losers' pool by weight*, instead of the absolute `payout = weight`. The CPMM stays as the **pricing/probability engine**; only settlement changes.
+>
+> 🇷🇺 Russian: [plan_unified_parimutuel_binary-ru.md](plan_unified_parimutuel_binary-ru.md). Related: [plan_batch_commit_reveal_betting.md](plan_batch_commit_reveal_betting.md) §6.1 (this change makes binary snapshot-safe too).
+
+## 1. Motivation
+
+Today the two market types settle differently (verified in code):
+
+| | Current Binary — [api.php:1664](module/api.php#L1664) | Multi — [lmsr_math.php:249](module/lmsr_math.php#L249) |
+|---|---|---|
+| Payout | `payout = weight` (absolute) | `payout = amount + winners_pool × weight/Σweight` |
+| Solvency basis | curve invariant `Σweight ≤ winning_reserve + winning_bets` | parimutuel (capped at `losers_sum`) |
+
+Problems with binary's absolute-weight model:
+- **Residual is not routed to bettors.** Winners get `Σweight`, LP gets `L + fees`; the leftover (`losers' stakes − winners' profit`) is not distributed to anyone (see [betting-rules](betting-rules-and-system-overview.md) §785 — "surplus = 19 mVIZ" goes uncredited).
+- **A lone/edge winner can receive *less* than their stake** (betting-rules:788 — bet 1000 → weight 981).
+- **Absolute weight must be curve-priced**, so snapshot/batch pricing is unsafe for binary → blocks front-run protection (the entire binary branch of [batch plan §6.1](plan_batch_commit_reveal_betting.md)).
+
+## 2. The change
+
+Binary keeps the CPMM for **pricing** (probability display + assigning `weight = tokens_received`), and switches **settlement** to parimutuel — byte-for-byte the Multi model:
+
+```
+// On resolution (side A wins):
+losers_sum = b_bets_sum // all losing-side bets
+oracle_fee = floor(losers_sum × oracle_fee‰ / 1000)
+creator_fee = floor(losers_sum × creator_fee‰ / 1000)
+liq_fee = floor(losers_sum × liquidity_fee‰ / 1000)
+winners_pool = losers_sum − oracle_fee − creator_fee − liq_fee
+total_winning_weight = Σ weight over winning-side bets
+
+for each winning bet i:
+ profit_share = floor(winners_pool × weight_i / total_winning_weight)
+ penalty = floor(profit_share × time_penalty_i / 1_000_000) // profit only
+ payout_i = bet_amount_i + profit_share − penalty
+
+LP: principal returned UNCONDITIONALLY + time-weighted share of liq_fee (+ penalty pool)
+```
+
+`weight` is now purely a **relative claim** (a ratio device); reserves/`k` are a **pure pricing engine** (the analogue of `q` in LMSR). This is literally the existing `lmsr_math.php` `settle()` applied to weights that come from the CPMM instead of LMSR.
+
+## 3. What it fixes
+
+1. **One settlement model for both types:** *"the AMM assigns weights; losers fund winners pro-rata by weight."* Binary uses CPMM weights, Multi uses LMSR weights. Same `settle()`.
+2. **Exact money conservation, no leak:** `out = L + winning_bets + winners_pool + fees = L + winning_bets + losing_bets = L + all_bets = in`. The previously-uncredited residual now goes to winners.
+3. **Trivial LP guarantee** (same as Multi): total payout is capped at `losers_sum`; LP principal is untouched. The curve invariant `Σweight ≤ reserves` is **no longer needed** for solvency.
+4. **Fixes the weird edge:** all bets on the winner → `winners_pool = 0` → each winner gets their `bet_amount` back (refund), never less.
+5. **Snapshot/batch pricing becomes safe for binary** → full front-run immunity for binary too (see §6).
+
+## 4. Conservation proof
+
+```
+Money IN = L (LP) + a_bets + b_bets
+Money OUT = L (LP principal) + Σ(winning bet_amount) + winners_pool + (oracle+creator+liq fees)
+ = L + winning_bets + (losers_sum − fees) + fees
+ = L + winning_bets + losing_bets
+ = L + a_bets + b_bets = Money IN ✓ (exact, for any weights)
+```
+Solvency holds for **any** weights → off-curve (snapshot) weights are harmless. This is the property binary currently lacks.
+
+## 5. Edge cases (mirror Multi / spec §6)
+
+| Scenario | Outcome |
+|----------|---------|
+| All bets on the winner (`losers_sum = 0`) | `winners_pool = 0` → every winner refunded `bet_amount`. LP principal returned. |
+| No bets on the winner | `winners_pool` undistributed → LP bonus. |
+| Single winner | Receives `bet_amount + winners_pool`. |
+| Zero volume | LP principal returned; no fees, no payouts. |
+
+## 6. Impact on batch / commit-reveal ([batch plan §6.1](plan_batch_commit_reveal_betting.md))
+
+The binary/multi split collapses: since payout is now capped at `losers_pool` regardless of weights, **both types can use epoch-open snapshot pricing for the batch** → full intra-epoch manipulation immunity for binary too. `min_tokens` still gates slippage. The "Binary = live curve + price band" carve-out and the `batch_price_band_permille` param become unnecessary.
+
+## 7. Tradeoff (must be acknowledged)
+
+| | Current Binary | After (parimutuel) |
+|---|---|---|
+| Payout known | **at bet time** (fixed odds — `weight` locked) | at resolution (floating share) |
+| Model | fixed-odds | parimutuel (horse-racing / Multi style) |
+
+The only real cost: a bettor's payout is no longer fixed at bet time — it depends on the final `total_winning_weight` and `losers_sum`. This is the standard parimutuel tradeoff, already accepted in Multi; it rewards early/underdog bettors with a larger weight share. **Decision needed:** accept the move from fixed-odds to floating-odds binary.
+
+## 8. Reserves & k
+
+- Reserves are still updated on each bet for **pricing only** (probability + `weight`); `k` stays constant under betting (unchanged from spec §5).
+- Reserves no longer gate payout. LP principal is returned as a separate unconditional line (like the Multi subsidy), not "reconstructed from reserves."
+
+## 9. Code changes
+
+- [api.php](module/api.php) binary resolve block (1655–1742) and the dispute recalculation block (2086–2166): replace `raw_payout = weight` with the parimutuel block from [lmsr_math.php:224-280](module/lmsr_math.php#L224) (`settle()`), passing CPMM `weight` as the token field. Both binary and multi can share one `settle()` helper.
+- Apply the same edge-case handling already present in `lmsr_math.php`.
+- Remove the now-unneeded binary-specific surplus/solvency assumptions.
+
+## 10. Spec sections to update
+
+| Section | Change |
+|---------|--------|
+| §5 Onix Binary | Settlement is now parimutuel; `weight` = relative claim, not VIZ-denominated payout. |
+| §6 Onix Multi | Note both types share one settlement model. |
+| §7 Fee Structure | Already losers-funded; unify wording across types. |
+| §10 Resolution & Payout | Binary payout formula → `bet_amount + winners_pool × weight/Σweight`. |
+| batch plan §6.1 | Binary now snapshot-safe; drop the live-curve/price-band carve-out and `batch_price_band_permille`. |
diff --git a/.qoder/plans/pm-plan/reference/lmsr_fixed.php b/.qoder/plans/pm-plan/reference/lmsr_fixed.php
new file mode 100644
index 0000000000..6078a6a646
--- /dev/null
+++ b/.qoder/plans/pm-plan/reference/lmsr_fixed.php
@@ -0,0 +1,561 @@
+ $one,
+ 'HALF' => $half,
+ 'LN2' => $ln2,
+ 'NEG_LN2' => gmp_neg($ln2),
+ 'EXP_DOMAIN_LIMIT' => gmp_mul($one, gmp_init(200)), // 200 · ONE
+ 'MAX_B' => gmp_pow(2, 53), // §1.4 domain caps
+ 'MAX_Q' => gmp_pow(2, 53),
+ 'ZERO' => gmp_init(0),
+ 'ONE_INT' => gmp_init(1),
+ 'TWO_INT' => gmp_init(2),
+ 'THOUSAND' => gmp_init(1000),
+ 'PRICE_PRECISION' => gmp_init(1000000),
+ ];
+ return $c;
+}
+
+// =====================================================================
+// Q96 primitives (§2 of spec)
+// =====================================================================
+
+/**
+ * Truncated integer division (round toward zero). GMP's gmp_div_q with
+ * GMP_ROUND_ZERO is exactly this; we wrap for clarity.
+ */
+function _q_trunc_div($a, $b) {
+ return gmp_div_q($a, $b, GMP_ROUND_ZERO);
+}
+
+/**
+ * Arithmetic right shift by `bits`. Floor division by 2^bits, NOT truncation.
+ * For non-negative values ASR == truncation; for negative values ASR rounds
+ * toward -∞ (this is the spec rule for `mul_q`, §3.5).
+ */
+function _q_asr($n, $bits) {
+ return gmp_div_q($n, gmp_pow(2, $bits), GMP_ROUND_MINUSINF);
+}
+
+/**
+ * mul_q(a, b) = (a · b) >> 96 with arithmetic right shift (§3.5).
+ * Wide accumulator: GMP integers are unbounded, so a 256-bit intermediate
+ * is naturally available.
+ */
+function _mul_q($a, $b) {
+ return _q_asr(gmp_mul($a, $b), 96);
+}
+
+/**
+ * div_q(a, b) = (a << 96) / b, truncated toward zero (§3.5).
+ * Result is Q96.
+ */
+function _div_q($a, $b) {
+ return _q_trunc_div(gmp_mul($a, gmp_pow(2, 96)), $b);
+}
+
+/**
+ * MSB position (0-indexed) of a positive GMP integer. Returns -1 for 0.
+ * Used by ln_q range reduction (§3.2). No FP calls.
+ */
+function _msb($x) {
+ if (gmp_cmp($x, 0) <= 0) return -1;
+ return strlen(gmp_strval($x, 2)) - 1;
+}
+
+/**
+ * Test whether a GMP integer is odd (works for negatives too).
+ * Used by exp_q's banker's rounding (§3.5).
+ */
+function _is_odd($q) {
+ // gmp_mod for negatives: gmp_mod(-3, 2) returns 1 (PHP GMP follows sign-of-divisor).
+ // Either way the result is 0 or non-zero; we only need parity.
+ return gmp_cmp(gmp_abs(gmp_mod($q, 2)), 0) !== 0;
+}
+
+// =====================================================================
+// Range reduction for exp_q — round-to-nearest-even (banker's), §3.5
+// =====================================================================
+
+/**
+ * Round x/LN2 to nearest integer, ties to even. The single non-truncating
+ * rounding step in the entire pipeline.
+ */
+function _round_div_ln2($x) {
+ $C = _lmsr_q96_const();
+ $LN2 = $C['LN2'];
+ $NEG = $C['NEG_LN2'];
+
+ $q = _q_trunc_div($x, $LN2); // truncated toward zero
+ $r = gmp_sub($x, gmp_mul($q, $LN2)); // |r| < LN2 (sign matches truncation residual)
+ $two_r = gmp_mul($r, 2);
+
+ // Positive-side: 2r > LN2 → bump up. 2r == LN2 and q odd → bump up (ties to even).
+ $cmp_pos = gmp_cmp($two_r, $LN2);
+ if ($cmp_pos > 0 || ($cmp_pos === 0 && _is_odd($q))) {
+ $q = gmp_add($q, 1);
+ }
+ // Negative-side: 2r < -LN2 → bump down. 2r == -LN2 and q odd → bump down.
+ // Note: at most one of (cmp_pos>=0) and (cmp_neg<=0) can be strict for any single r.
+ $cmp_neg = gmp_cmp($two_r, $NEG);
+ if ($cmp_neg < 0 || ($cmp_neg === 0 && _is_odd($q))) {
+ $q = gmp_sub($q, 1);
+ }
+ return $q;
+}
+
+// =====================================================================
+// exp_q — Taylor with range reduction (§3.1)
+// =====================================================================
+
+function _exp_q($x) {
+ $C = _lmsr_q96_const();
+ // Domain check: |x| ≤ 130 · ONE
+ $abs_x = gmp_abs($x);
+ $limit = gmp_mul($C['ONE'], gmp_init(130));
+ if (gmp_cmp($abs_x, $limit) > 0) {
+ throw new Exception('LMSR_OVERFLOW: exp_q domain exceeded');
+ }
+
+ // 1. Range reduction
+ $k = _round_div_ln2($x); // signed integer
+ $r = gmp_sub($x, gmp_mul($k, $C['LN2'])); // |r| ≤ LN2/2
+
+ // 2. Polynomial — exactly 30 iterations
+ $acc = $C['ONE']; // term[0] = 1
+ $term = $C['ONE'];
+ for ($n = 1; $n <= 30; $n++) {
+ // term = mul_q(term, r) / n (mul_q first, then truncated div by integer n)
+ $term = _q_trunc_div(_mul_q($term, $r), gmp_init($n));
+ $acc = gmp_add($acc, $term);
+ }
+
+ // 3. Scale by 2^k (signed). k is small (|k| ≤ ⌈130/ln(2)⌉ ≈ 188).
+ $k_int = gmp_intval($k);
+ if ($k_int >= 0) {
+ return gmp_mul($acc, gmp_pow(2, $k_int)); // exact left shift
+ } else {
+ // Arithmetic right shift; for non-negative `acc` (true here since exp > 0)
+ // ASR == truncation, but we use ASR by spec for consistency.
+ return _q_asr($acc, -$k_int);
+ }
+}
+
+// =====================================================================
+// ln_q — atanh series with range reduction (§3.2)
+// =====================================================================
+
+function _ln_q($x) {
+ $C = _lmsr_q96_const();
+ if (gmp_cmp($x, 0) <= 0) {
+ throw new Exception('LMSR_DOMAIN_LN: ln_q called with x ≤ 0');
+ }
+
+ // 1. p = msb(x) - 96 ; m = x scaled into [ONE, 2·ONE)
+ $p = _msb($x) - 96;
+ if ($p >= 0) {
+ // m = x >> p, ASR (x is positive, == truncation)
+ $m = _q_asr($x, $p);
+ } else {
+ // m = x << (-p), exact left shift
+ $m = gmp_mul($x, gmp_pow(2, -$p));
+ }
+
+ // 2. z = (m - ONE) / (m + ONE) in Q96, |z| < 1/3
+ $num = gmp_sub($m, $C['ONE']);
+ $den = gmp_add($m, $C['ONE']);
+ $z = _div_q($num, $den);
+
+ // 3. atanh series, 50 iterations
+ $z2 = _mul_q($z, $z);
+ $acc = $z; // first term = z (k=0 → 2k+1 = 1)
+ $term = $z;
+ for ($k = 1; $k <= 50; $k++) {
+ $term = _mul_q($term, $z2); // term · z²
+ $denom = gmp_init(2 * $k + 1); // 3, 5, 7, ...
+ $acc = gmp_add($acc, _q_trunc_div($term, $denom));
+ }
+
+ // 4. ln(m) = 2 · acc
+ $ln_m = gmp_mul($acc, 2);
+
+ // 5. ln(x) = p · LN2 + ln(m)
+ $p_gmp = gmp_init($p);
+ return gmp_add(gmp_mul($p_gmp, $C['LN2']), $ln_m);
+}
+
+// =====================================================================
+// log-sum-exp (§3.3)
+// =====================================================================
+
+function _lse_q(array $ratios) {
+ $C = _lmsr_q96_const();
+ if (empty($ratios)) return $C['ZERO'];
+
+ // Find max — straightforward, deterministic
+ $max = $ratios[0];
+ foreach ($ratios as $r) {
+ if (gmp_cmp($r, $max) > 0) $max = $r;
+ }
+
+ $acc = $C['ZERO'];
+ $cutoff = gmp_neg($C['EXP_DOMAIN_LIMIT']); // -200 · ONE
+ foreach ($ratios as $r) { // input order — DO NOT sort
+ $d = gmp_sub($r, $max); // d ≤ 0
+ if (gmp_cmp($d, $cutoff) < 0) continue; // strict < (per spec §3.3)
+ $acc = gmp_add($acc, _exp_q($d));
+ }
+ if (gmp_cmp($acc, 0) <= 0) {
+ // Defensive — should be unreachable: at least the d=0 term contributes ONE.
+ return $max;
+ }
+ return gmp_add($max, _ln_q($acc));
+}
+
+// =====================================================================
+// Domain validation (§1.4)
+// =====================================================================
+
+function _validate_domain(array $q, $b) {
+ $C = _lmsr_q96_const();
+ if (count($q) < 2 || count($q) > 16) {
+ throw new Exception('LMSR_INVALID_N: outcomes must be 2..16');
+ }
+ if (gmp_cmp(gmp_init((string)$b), 0) <= 0) {
+ throw new Exception('LMSR_INVALID_B: b must be > 0');
+ }
+ if (gmp_cmp(gmp_init((string)$b), $C['MAX_B']) > 0) {
+ throw new Exception('LMSR_INVALID_B: b exceeds MAX_B (2^53)');
+ }
+ foreach ($q as $qi) {
+ $g = gmp_init((string)$qi);
+ if (gmp_cmp($g, 0) < 0) {
+ throw new Exception('LMSR_INVALID_Q: q[i] < 0');
+ }
+ if (gmp_cmp($g, $C['MAX_Q']) > 0) {
+ throw new Exception('LMSR_INVALID_Q: q[i] exceeds MAX_Q (2^53)');
+ }
+ }
+}
+
+/**
+ * Convert int64 milli-VIZ q[]/b into Q96 ratios = q[i] · ONE / b (truncated).
+ * Direct one-step form per spec §3.4 — avoids double-truncation.
+ */
+function _ratios_q96(array $q, $b) {
+ $C = _lmsr_q96_const();
+ $b_gmp = gmp_init((string)$b);
+ $ratios = [];
+ foreach ($q as $qi) {
+ $num = gmp_mul(gmp_init((string)$qi), $C['ONE']);
+ $ratios[] = _q_trunc_div($num, $b_gmp);
+ }
+ return $ratios;
+}
+
+// =====================================================================
+// Public API (§4) — same signatures as module/lmsr_math.php
+// =====================================================================
+
+/**
+ * LMSR cost C(q) = b · ln(Σ exp(q_j / b))
+ * @return int milli-VIZ, truncated toward zero
+ */
+function lmsr_cost(array $q, int $b): int {
+ if ($b <= 0) return 0;
+ _validate_domain($q, $b);
+ $C = _lmsr_q96_const();
+ $ratios = _ratios_q96($q, $b);
+ $lse = _lse_q($ratios); // Q96
+ // cost_milli = (b · lse) / ONE (truncated toward zero — see §3.4)
+ $product = gmp_mul(gmp_init((string)$b), $lse);
+ $cost = _q_trunc_div($product, $C['ONE']);
+ return intval(gmp_strval($cost));
+}
+
+/**
+ * Probability of outcome i, returned as integer ×10^6 (1e6 = 100%).
+ */
+function lmsr_price(array $q, int $b, int $i): int {
+ if ($b <= 0 || !isset($q[$i])) return 0;
+ _validate_domain($q, $b);
+ $prices = lmsr_prices($q, $b);
+ return $prices[$i] ?? 0;
+}
+
+/**
+ * All N prices in one pass. Sum of returned prices is in [LMSR_PRICE_PRECISION-N, LMSR_PRICE_PRECISION]
+ * (truncation residual ≤ N). Caller MUST tolerate this — it is consensus-fixed.
+ */
+function lmsr_prices(array $q, int $b): array {
+ $n = count($q);
+ if ($b <= 0) return array_fill(0, $n, 0);
+ _validate_domain($q, $b);
+ $C = _lmsr_q96_const();
+
+ $ratios = _ratios_q96($q, $b);
+ // Find max
+ $max = $ratios[0];
+ foreach ($ratios as $r) if (gmp_cmp($r, $max) > 0) $max = $r;
+
+ $cutoff = gmp_neg($C['EXP_DOMAIN_LIMIT']);
+ $exps = [];
+ $sum = $C['ZERO'];
+ foreach ($ratios as $r) {
+ $d = gmp_sub($r, $max);
+ if (gmp_cmp($d, $cutoff) < 0) {
+ $exps[] = $C['ZERO'];
+ } else {
+ $e = _exp_q($d);
+ $exps[] = $e;
+ $sum = gmp_add($sum, $e);
+ }
+ }
+ if (gmp_cmp($sum, 0) <= 0) {
+ return array_fill(0, $n, 0);
+ }
+ // price_i_q = exp_i / sum (Q96 division ↦ Q96)
+ // price_i_int = (price_i_q * PRICE_PRECISION) / ONE truncated
+ $out = [];
+ foreach ($exps as $e) {
+ $pq = _div_q($e, $sum); // Q96
+ $scaled= gmp_mul($pq, $C['PRICE_PRECISION']);
+ $int = _q_trunc_div($scaled, $C['ONE']);
+ $out[] = intval(gmp_strval($int));
+ }
+ return $out;
+}
+
+/**
+ * buy_cost = max(0, C(q + Δ·e_i) − C(q))
+ */
+function lmsr_buy_cost(array $q, int $b, int $i, int $delta): int {
+ if ($b <= 0 || $delta <= 0 || !isset($q[$i])) return 0;
+ $q_after = $q;
+ $q_after[$i] += $delta;
+ return max(0, lmsr_cost($q_after, $b) - lmsr_cost($q, $b));
+}
+
+/**
+ * sell_return = max(0, C(q) − C(q − Δ·e_i))
+ */
+function lmsr_sell_return(array $q, int $b, int $i, int $delta): int {
+ if ($b <= 0 || $delta <= 0 || !isset($q[$i])) return 0;
+ if ($q[$i] - $delta < 0) return 0; // can't sell more than the outcome holds
+ $q_after = $q;
+ $q_after[$i] -= $delta;
+ return max(0, lmsr_cost($q, $b) - lmsr_cost($q_after, $b));
+}
+
+/**
+ * Largest Δ such that buy_cost(Δ) ≤ amount. Deterministic binary search,
+ * fixed 100-iteration bound (§4.1).
+ */
+function lmsr_tokens_for_amount(array $q, int $b, int $i, int $amount): int {
+ if ($b <= 0 || $amount <= 0 || !isset($q[$i])) return 0;
+ _validate_domain($q, $b);
+
+ $lo = 0;
+ // Upper bound: amount × 10, clamped to MAX_Q. Mirrors the legacy heuristic
+ // and guarantees a feasible search interval (cost ≥ amount at hi).
+ $hi = $amount * 10;
+ $C = _lmsr_q96_const();
+ $max_q = intval(gmp_strval($C['MAX_Q']));
+ if ($hi > $max_q) $hi = $max_q;
+ $best = 0;
+
+ for ($iter = 0; $iter < 100; $iter++) {
+ if ($hi - $lo <= 1) break;
+ $mid = intdiv($lo + $hi, 2);
+ $cost = lmsr_buy_cost($q, $b, $i, $mid);
+ if ($cost <= $amount) {
+ $best = $mid;
+ $lo = $mid;
+ } else {
+ $hi = $mid;
+ }
+ }
+ return $best;
+}
+
+/**
+ * b = liquidity / ln(N), in milli-VIZ. Computed once per market at creation.
+ * Output truncated toward zero.
+ */
+function lmsr_b_from_liquidity(int $liquidity, int $n): int {
+ if ($liquidity <= 0 || $n <= 1) return 0;
+ if ($n > 16) throw new Exception('LMSR_INVALID_N');
+ $C = _lmsr_q96_const();
+ // ln(n) in Q96 — n is small int, scale to Q96 then ln_q
+ $n_q96 = gmp_mul(gmp_init($n), $C['ONE']);
+ $ln_n = _ln_q($n_q96); // Q96
+ // b_milli = floor(liquidity · ONE / ln_n)
+ $num = gmp_mul(gmp_init((string)$liquidity), $C['ONE']);
+ $b_q = _q_trunc_div($num, $ln_n);
+ return intval(gmp_strval($b_q));
+}
+
+/**
+ * Maximum theoretical LMSR loss. Reference only — not used in settlement.
+ */
+function lmsr_max_loss(int $b, int $n): int {
+ if ($b <= 0 || $n <= 1) return 0;
+ $C = _lmsr_q96_const();
+ $n_q96 = gmp_mul(gmp_init($n), $C['ONE']);
+ $ln_n = _ln_q($n_q96);
+ $product = gmp_mul(gmp_init((string)$b), $ln_n);
+ $loss = _q_trunc_div($product, $C['ONE']);
+ return intval(gmp_strval($loss));
+}
+
+// =====================================================================
+// Settlement and leverage helpers — pass-through from lmsr_math.php
+// (these don't call exp/ln, so they're already deterministic; we copy the
+// exact functions to make this file a single drop-in replacement)
+// =====================================================================
+
+/**
+ * Onix Multi parimutuel settlement. Identical to lmsr_math.php — kept here
+ * so callers only need to require_once one file.
+ */
+function lmsr_settlement(array $bets, int $winning_outcome, int $oracle_fee_permille, int $creator_fee_permille, int $liquidity_fee_permille): array {
+ $losers_sum = 0;
+ $winning_bets = [];
+ $total_winning_tokens = 0;
+ foreach ($bets as $bet) {
+ if (intval($bet['outcome_index']) === $winning_outcome) {
+ $winning_bets[] = $bet;
+ $total_winning_tokens += intval($bet['weight']);
+ } else {
+ $losers_sum += intval($bet['amount']);
+ }
+ }
+ $oracle_fee = intval($losers_sum * $oracle_fee_permille / 1000);
+ $creator_fee = intval($losers_sum * $creator_fee_permille / 1000);
+ $liquidity_fee = intval($losers_sum * $liquidity_fee_permille / 1000);
+ $winners_pool = $losers_sum - $oracle_fee - $creator_fee - $liquidity_fee;
+ if ($winners_pool < 0) $winners_pool = 0;
+ $payouts = [];
+ $total_distributed = 0;
+ foreach ($winning_bets as $bet) {
+ $bet_amount = intval($bet['amount']);
+ $tokens = intval($bet['weight']);
+ $profit_share = ($total_winning_tokens > 0) ? intval($winners_pool * $tokens / $total_winning_tokens) : 0;
+ $time_penalty_ratio = intval($bet['time_penalty']); // ×10^6
+ $penalty_deduction = intval($profit_share * $time_penalty_ratio / 1000000);
+ $net_profit = $profit_share - $penalty_deduction;
+ $payout = $bet_amount + $net_profit;
+ $total_distributed += $payout;
+ $payouts[] = [
+ 'user' => $bet['user'],
+ 'bet_id' => $bet['id'] ?? 0,
+ 'amount' => $bet_amount,
+ 'tokens' => $tokens,
+ 'profit_share' => $profit_share,
+ 'penalty' => $penalty_deduction,
+ 'payout' => $payout,
+ ];
+ }
+ $undistributed = max(0, $winners_pool - ($total_distributed - array_sum(array_map(function($b){ return intval($b['amount']); }, $winning_bets))));
+ return [
+ 'losers_sum' => $losers_sum,
+ 'oracle_fee' => $oracle_fee,
+ 'creator_fee' => $creator_fee,
+ 'liquidity_fee' => $liquidity_fee,
+ 'winners_pool' => $winners_pool,
+ 'total_winning_tokens' => $total_winning_tokens,
+ 'payouts' => $payouts,
+ 'undistributed' => $undistributed,
+ 'total_penalty_pool' => array_sum(array_column($payouts, 'penalty')),
+ ];
+}
+
+/**
+ * Leverage helper: max bet amount that keeps slippage within `slippage_pct`.
+ * Uses the deterministic lmsr_price / lmsr_tokens_for_amount internally,
+ * so its output is now also bit-deterministic.
+ */
+function lmsr_max_bet_amount(array $q, int $b, float $slippage_pct): int {
+ if ($b <= 0 || $slippage_pct <= 0 || empty($q)) return 0;
+ $n = count($q);
+ if ($n < 2) return 0;
+ $C = _lmsr_q96_const();
+ $max_amount = 0;
+ // slippage_pct → integer ×1e6 of fraction (e.g. 10% → 100_000 of 1_000_000 = 0.1)
+ // We compare price-change fraction (price_after - price_before)/price_before * 100 ≤ slippage_pct
+ // Implemented in integer ×10^6 price space: |dp| · 100 / p_before ≤ slippage_pct
+ // → |dp| · 100 · 1e6 ≤ slippage_pct · 1e6 · p_before
+ $sl_scaled = (int) round($slippage_pct * 1000000); // slippage in ppm of percent — note: this float
+ // is non-consensus (only an off-chain risk gate)
+ foreach ($q as $i => $qi) {
+ $lo = 0;
+ $hi = $b * 10;
+ if ($hi > intval(gmp_strval($C['MAX_Q']))) $hi = intval(gmp_strval($C['MAX_Q']));
+ $best = 0;
+ for ($iter = 0; $iter < 60; $iter++) {
+ if ($hi - $lo <= LMSR_PRECISION) break;
+ $mid = intdiv($lo + $hi, 2);
+ if ($mid <= 0) break;
+ $price_before = lmsr_price($q, $b, $i);
+ if ($price_before <= 0) { $hi = $mid; continue; }
+ $tokens = lmsr_tokens_for_amount($q, $b, $i, $mid);
+ if ($tokens <= 0) { $hi = $mid; continue; }
+ $q_after = $q;
+ $q_after[$i] += $tokens;
+ $price_after = lmsr_price($q_after, $b, $i);
+ $dp = abs($price_after - $price_before);
+ // |dp|·100·1e6 ≤ slippage_pct·1e6·price_before
+ // (multiply explicitly in int64-safe range — prices are ≤ 1e6, dp ≤ 1e6)
+ $lhs = $dp * 100; // ≤ 1e8
+ $rhs_per_million = $sl_scaled; // = slippage_pct · 1e6
+ // lhs · 1e6 / price_before ≤ rhs_per_million
+ $threshold = intdiv($rhs_per_million * $price_before, 1000000);
+ if ($lhs <= $threshold) {
+ $best = $mid; $lo = $mid;
+ } else {
+ $hi = $mid;
+ }
+ }
+ if ($best > $max_amount) $max_amount = $best;
+ }
+ return $max_amount;
+}
diff --git a/.qoder/plans/pm-plan/reference/lmsr_fixed_smoke.php b/.qoder/plans/pm-plan/reference/lmsr_fixed_smoke.php
new file mode 100644
index 0000000000..bb07ecc90b
--- /dev/null
+++ b/.qoder/plans/pm-plan/reference/lmsr_fixed_smoke.php
@@ -0,0 +1,145 @@
+ $pi) {
+ check("balanced 3-way price[$i] ≈ 333333", $pi, 333333, /*tol*/ 1);
+}
+
+// Imbalanced: q=[10000,0,0]
+$p_imb = lmsr_prices([10000, 0, 0], 100000);
+$sum_p2 = array_sum($p_imb);
+check('imbalanced prices sum ≈ 1e6', $sum_p2, 1000000, /*tol*/ 3);
+// price[0] should be ≈ exp(0.1)/(exp(0.1)+2·exp(0)) = 1.1052/(1.1052+2) = 0.3559... → ≈ 355900
+check('imbalanced price[0] ≈ 355900',
+ $p_imb[0], 355900, /*tol*/ 100); // small margin for rounding chain
+
+// ---------------------------------------------------------------------
+// 7. buy_cost monotone, tokens_for_amount ≤ amount in cost
+// ---------------------------------------------------------------------
+$bc1 = lmsr_buy_cost([1000, 1000], 100000, 0, 1000);
+$bc2 = lmsr_buy_cost([1000, 1000], 100000, 0, 2000);
+check('buy_cost monotone', ($bc2 > $bc1) ? 1 : 0, 1);
+
+$tok = lmsr_tokens_for_amount([1000, 1000], 100000, 0, 5000);
+$cost_back = lmsr_buy_cost([1000, 1000], 100000, 0, $tok);
+check('tokens_for_amount(5000): cost ≤ amount',
+ ($cost_back <= 5000) ? 1 : 0, 1);
+check('tokens_for_amount(5000): cost(tok+1) > amount',
+ (lmsr_buy_cost([1000,1000], 100000, 0, $tok+1) > 5000) ? 1 : 0, 1);
+
+// ---------------------------------------------------------------------
+// 8. lmsr_b_from_liquidity sanity
+// ---------------------------------------------------------------------
+// liquidity = 100000, N = 2 → b = 100000 / ln(2) = 100000 / 0.6931 = 144269 (truncated 144269)
+$b_from_l = lmsr_b_from_liquidity(100000, 2);
+check('b_from_liquidity(100000, 2) ≈ 144269',
+ $b_from_l, 144269, /*tol*/ 1);
+
+// liquidity = 100000, N = 4 → b = 100000 / ln(4) = 100000 / 1.3862... = 72134
+$b_from_l4 = lmsr_b_from_liquidity(100000, 4);
+check('b_from_liquidity(100000, 4) ≈ 72134',
+ $b_from_l4, 72134, /*tol*/ 1);
+
+// ---------------------------------------------------------------------
+echo "\n---\n";
+printf("PASSED: %d FAILED: %d\n", $passed, $failed);
+exit($failed === 0 ? 0 : 1);
diff --git a/.qoder/plans/pm-plan/reference/lmsr_fixed_vectors.json b/.qoder/plans/pm-plan/reference/lmsr_fixed_vectors.json
new file mode 100644
index 0000000000..8da076220c
--- /dev/null
+++ b/.qoder/plans/pm-plan/reference/lmsr_fixed_vectors.json
@@ -0,0 +1,625 @@
+{
+ "version": 1,
+ "spec": "docs/lmsr-fixed-point-spec.md",
+ "generated_by": "module/lmsr_fixed.php (PHP+GMP, normative)",
+ "numbers_are_decimal_strings": true,
+ "note": "All numeric values, including Q96 intermediates, are decimal strings. Equality is exact (===); no tolerance is permitted.",
+ "constants": {
+ "ONE": "79228162514264337593543950336",
+ "HALF": "39614081257132168796771975168",
+ "LN2": "54916777467707473351141471128",
+ "EXP_DOMAIN_LIMIT": "15845632502852867518708790067200",
+ "MAX_B": "9007199254740992",
+ "MAX_Q": "9007199254740992"
+ },
+ "mul_q": [
+ {
+ "a": "79228162514264337593543950336",
+ "b": "79228162514264337593543950336",
+ "expected": "79228162514264337593543950336"
+ },
+ {
+ "a": "79228162514264337593543950336",
+ "b": "39614081257132168796771975168",
+ "expected": "39614081257132168796771975168"
+ },
+ {
+ "a": "54916777467707473351141471128",
+ "b": "54916777467707473351141471128",
+ "expected": "38065409467179368169621602747"
+ },
+ {
+ "a": "237684487542793012780631851008",
+ "b": "79228162514264337593543950336",
+ "expected": "237684487542793012780631851008"
+ },
+ {
+ "a": "-79228162514264337593543950336",
+ "b": "39614081257132168796771975168",
+ "expected": "-39614081257132168796771975168"
+ }
+ ],
+ "div_q": [
+ {
+ "a": "79228162514264337593543950336",
+ "b": "79228162514264337593543950336",
+ "expected": "79228162514264337593543950336"
+ },
+ {
+ "a": "79228162514264337593543950336",
+ "b": "158456325028528675187087900672",
+ "expected": "39614081257132168796771975168"
+ },
+ {
+ "a": "54916777467707473351141471128",
+ "b": "79228162514264337593543950336",
+ "expected": "54916777467707473351141471128"
+ },
+ {
+ "a": "554597137599850363154807652352",
+ "b": "237684487542793012780631851008",
+ "expected": "184865712533283454384935884117"
+ },
+ {
+ "a": "-79228162514264337593543950336",
+ "b": "316912650057057350374175801344",
+ "expected": "-19807040628566084398385987584"
+ }
+ ],
+ "exp_q": [
+ {
+ "x": "0",
+ "expected": "79228162514264337593543950336"
+ },
+ {
+ "x": "54916777467707473351141471128",
+ "expected": "158456325028528675187087900672"
+ },
+ {
+ "x": "-54916777467707473351141471128",
+ "expected": "39614081257132168796771975168"
+ },
+ {
+ "x": "79228162514264337593543950336",
+ "expected": "215364474464724850177511348332"
+ },
+ {
+ "x": "-79228162514264337593543950336",
+ "expected": "29146412150787779157341161346"
+ },
+ {
+ "x": "158456325028528675187087900672",
+ "expected": "585421337433093623126455912472"
+ },
+ {
+ "x": "-396140812571321687967719751680",
+ "expected": "533835159856043089203045521"
+ },
+ {
+ "x": "27458388733853736675570735564",
+ "expected": "112045541949572279837463876445"
+ },
+ {
+ "x": "7922816251426433759354395033",
+ "expected": "87560661103336128385318961295"
+ },
+ {
+ "x": "12345678901234567890",
+ "expected": "79228162526610016495740397074"
+ },
+ {
+ "x": "-98765432109876543210",
+ "expected": "79228162415498905545227654557"
+ }
+ ],
+ "ln_q": [
+ {
+ "x": "79228162514264337593543950336",
+ "expected": "0"
+ },
+ {
+ "x": "158456325028528675187087900672",
+ "expected": "54916777467707473351141471128"
+ },
+ {
+ "x": "237684487542793012780631851008",
+ "expected": "87041032946764879767665216836"
+ },
+ {
+ "x": "792281625142643375935439503360",
+ "expected": "182429585950654714090129938590"
+ },
+ {
+ "x": "39614081257132168796771975168",
+ "expected": "-54916777467707473351141471128"
+ },
+ {
+ "x": "7922816251426433759354395033",
+ "expected": "-182429585950654714090129938636"
+ },
+ {
+ "x": "79228162514264337593543950",
+ "expected": "-547288757851964142270389816164"
+ },
+ {
+ "x": "79228162514264337593543950336000000",
+ "expected": "1094577515703928284540779631608"
+ }
+ ],
+ "lmsr_cost": [
+ {
+ "q": [
+ 0,
+ 0
+ ],
+ "b": 100000,
+ "expected": 69314
+ },
+ {
+ "q": [
+ 0,
+ 0,
+ 0
+ ],
+ "b": 100000,
+ "expected": 109861
+ },
+ {
+ "q": [
+ 10000,
+ 0,
+ 0
+ ],
+ "b": 100000,
+ "expected": 113306
+ },
+ {
+ "q": [
+ 1000,
+ 2000,
+ 3000
+ ],
+ "b": 5000,
+ "expected": 7559
+ },
+ {
+ "q": [
+ 50000,
+ 10000
+ ],
+ "b": 20000,
+ "expected": 52538
+ },
+ {
+ "q": [
+ 5000,
+ 5000,
+ 5000,
+ 5000
+ ],
+ "b": 200000,
+ "expected": 282258
+ },
+ {
+ "q": [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10
+ ],
+ "b": 100000,
+ "expected": 230264
+ },
+ {
+ "q": [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ "b": 100000,
+ "expected": 277258
+ },
+ {
+ "q": [
+ 1000000,
+ 0
+ ],
+ "b": 50000,
+ "expected": 1000000
+ },
+ {
+ "q": [
+ 2500,
+ 2500
+ ],
+ "b": 5000,
+ "expected": 5965
+ }
+ ],
+ "lmsr_prices": [
+ {
+ "q": [
+ 0,
+ 0
+ ],
+ "b": 100000,
+ "expected": [
+ 500000,
+ 500000
+ ]
+ },
+ {
+ "q": [
+ 0,
+ 0,
+ 0
+ ],
+ "b": 100000,
+ "expected": [
+ 333333,
+ 333333,
+ 333333
+ ]
+ },
+ {
+ "q": [
+ 10000,
+ 0,
+ 0
+ ],
+ "b": 100000,
+ "expected": [
+ 355913,
+ 322043,
+ 322043
+ ]
+ },
+ {
+ "q": [
+ 1000,
+ 2000,
+ 3000
+ ],
+ "b": 5000,
+ "expected": [
+ 269307,
+ 328932,
+ 401759
+ ]
+ },
+ {
+ "q": [
+ 50000,
+ 10000
+ ],
+ "b": 20000,
+ "expected": [
+ 880797,
+ 119202
+ ]
+ },
+ {
+ "q": [
+ 5000,
+ 5000,
+ 5000,
+ 5000
+ ],
+ "b": 200000,
+ "expected": [
+ 250000,
+ 250000,
+ 250000,
+ 250000
+ ]
+ },
+ {
+ "q": [
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10
+ ],
+ "b": 100000,
+ "expected": [
+ 99995,
+ 99996,
+ 99997,
+ 99998,
+ 99999,
+ 100000,
+ 100001,
+ 100002,
+ 100003,
+ 100004
+ ]
+ },
+ {
+ "q": [
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ "b": 100000,
+ "expected": [
+ 62500,
+ 62500,
+ 62500,
+ 62500,
+ 62500,
+ 62500,
+ 62500,
+ 62500,
+ 62500,
+ 62500,
+ 62500,
+ 62500,
+ 62500,
+ 62500,
+ 62500,
+ 62500
+ ]
+ },
+ {
+ "q": [
+ 1000000,
+ 0
+ ],
+ "b": 50000,
+ "expected": [
+ 999999,
+ 0
+ ]
+ },
+ {
+ "q": [
+ 2500,
+ 2500
+ ],
+ "b": 5000,
+ "expected": [
+ 500000,
+ 500000
+ ]
+ }
+ ],
+ "lmsr_buy_cost": [
+ {
+ "q": [
+ 1000,
+ 2000,
+ 3000
+ ],
+ "b": 5000,
+ "i": 0,
+ "delta": 1000,
+ "expected": 290
+ },
+ {
+ "q": [
+ 1000,
+ 2000,
+ 3000
+ ],
+ "b": 5000,
+ "i": 1,
+ "delta": 1000,
+ "expected": 351
+ },
+ {
+ "q": [
+ 0,
+ 0,
+ 0
+ ],
+ "b": 8000,
+ "i": 0,
+ "delta": 5000,
+ "expected": 2034
+ },
+ {
+ "q": [
+ 0,
+ 0,
+ 0
+ ],
+ "b": 8000,
+ "i": 2,
+ "delta": 50000,
+ "expected": 41242
+ },
+ {
+ "q": [
+ 50000,
+ 10000
+ ],
+ "b": 20000,
+ "i": 0,
+ "delta": 1000,
+ "expected": 883
+ },
+ {
+ "q": [
+ 50000,
+ 10000
+ ],
+ "b": 20000,
+ "i": 1,
+ "delta": 100000,
+ "expected": 58433
+ }
+ ],
+ "lmsr_sell_return": [
+ {
+ "q": [
+ 10000,
+ 5000,
+ 2000
+ ],
+ "b": 8000,
+ "i": 0,
+ "delta": 1000,
+ "expected": 510
+ },
+ {
+ "q": [
+ 10000,
+ 5000,
+ 2000
+ ],
+ "b": 8000,
+ "i": 1,
+ "delta": 2500,
+ "expected": 628
+ },
+ {
+ "q": [
+ 50000,
+ 10000
+ ],
+ "b": 20000,
+ "i": 0,
+ "delta": 25000,
+ "expected": 19801
+ }
+ ],
+ "lmsr_tokens_for_amount": [
+ {
+ "q": [
+ 1000,
+ 2000,
+ 3000
+ ],
+ "b": 5000,
+ "i": 0,
+ "amount": 10000,
+ "expected": 16039
+ },
+ {
+ "q": [
+ 0,
+ 0,
+ 0
+ ],
+ "b": 8000,
+ "i": 1,
+ "amount": 50000,
+ "expected": 58778
+ },
+ {
+ "q": [
+ 50000,
+ 10000
+ ],
+ "b": 20000,
+ "i": 0,
+ "amount": 30000,
+ "expected": 31999
+ },
+ {
+ "q": [
+ 0,
+ 0
+ ],
+ "b": 10000,
+ "i": 0,
+ "amount": 1000,
+ "expected": 1909
+ },
+ {
+ "q": [
+ 0,
+ 0
+ ],
+ "b": 10000,
+ "i": 0,
+ "amount": 100000,
+ "expected": 106931
+ },
+ {
+ "q": [
+ 1000000,
+ 0
+ ],
+ "b": 50000,
+ "i": 1,
+ "amount": 50000,
+ "expected": 499999
+ }
+ ],
+ "lmsr_b_from_liquidity": [
+ {
+ "liquidity": 100000,
+ "n": 2,
+ "expected": 144269
+ },
+ {
+ "liquidity": 100000,
+ "n": 3,
+ "expected": 91023
+ },
+ {
+ "liquidity": 100000,
+ "n": 4,
+ "expected": 72134
+ },
+ {
+ "liquidity": 100000,
+ "n": 10,
+ "expected": 43429
+ },
+ {
+ "liquidity": 100000,
+ "n": 16,
+ "expected": 36067
+ },
+ {
+ "liquidity": 1000000,
+ "n": 5,
+ "expected": 621334
+ },
+ {
+ "liquidity": 1234567,
+ "n": 7,
+ "expected": 634441
+ }
+ ]
+}
diff --git a/.qoder/plans/pm-plan/reference/lmsr_fixed_vectors_verify.js b/.qoder/plans/pm-plan/reference/lmsr_fixed_vectors_verify.js
new file mode 100644
index 0000000000..75b2a24cad
--- /dev/null
+++ b/.qoder/plans/pm-plan/reference/lmsr_fixed_vectors_verify.js
@@ -0,0 +1,82 @@
+/*
+ * tests/lmsr_fixed_vectors_verify.js — asserts that the JS implementation
+ * (market_math.js, BigInt) reproduces every value in
+ * tests/lmsr_fixed_vectors.json with strict equality.
+ *
+ * Run: node tests/lmsr_fixed_vectors_verify.js
+ * Exit: 0 = all pass, 1 = at least one mismatch
+ */
+'use strict';
+
+var path = require('path');
+var fs = require('fs');
+var M = require(path.join(__dirname, 'market_math.js'));
+
+var vectorsPath = path.join(__dirname, 'lmsr_fixed_vectors.json');
+if (!fs.existsSync(vectorsPath)) {
+ console.error('[FAIL] missing ' + vectorsPath + ' — run `php tests/lmsr_vectors_generate.php` first');
+ process.exit(1);
+}
+var V = JSON.parse(fs.readFileSync(vectorsPath, 'utf8'));
+
+var pass = 0, fail = 0;
+function check(label, expected, actual) {
+ var ok;
+ if (Array.isArray(expected) && Array.isArray(actual)) {
+ ok = expected.length === actual.length;
+ for (var k = 0; ok && k < expected.length; k++) {
+ ok = String(expected[k]) === String(actual[k]);
+ }
+ } else {
+ ok = String(expected) === String(actual);
+ }
+ if (ok) { pass++; return; }
+ fail++;
+ console.log(' FAIL ' + label);
+ console.log(' expected: ' + JSON.stringify(expected));
+ console.log(' actual: ' + (typeof actual === 'bigint' ? actual.toString() : JSON.stringify(actual)));
+}
+
+// ---- constants ----
+var Q = M._Q96;
+check('const ONE', V.constants.ONE, Q.ONE.toString());
+check('const HALF', V.constants.HALF, Q.HALF.toString());
+check('const LN2', V.constants.LN2, Q.LN2.toString());
+
+// ---- primitives ----
+V.mul_q.forEach(function (t, i) {
+ check('mul_q[' + i + ']', t.expected, Q.mul_q(BigInt(t.a), BigInt(t.b)).toString());
+});
+V.div_q.forEach(function (t, i) {
+ check('div_q[' + i + ']', t.expected, Q.div_q(BigInt(t.a), BigInt(t.b)).toString());
+});
+V.exp_q.forEach(function (t, i) {
+ check('exp_q[' + i + '] x=' + t.x, t.expected, Q.exp_q(BigInt(t.x)).toString());
+});
+V.ln_q.forEach(function (t, i) {
+ check('ln_q[' + i + '] x=' + t.x, t.expected, Q.ln_q(BigInt(t.x)).toString());
+});
+
+// ---- public API ----
+V.lmsr_cost.forEach(function (t, i) {
+ check('lmsr_cost[' + i + ']', t.expected, M.lmsr_cost(t.q, t.b));
+});
+V.lmsr_prices.forEach(function (t, i) {
+ check('lmsr_prices[' + i + ']', t.expected, M.lmsr_prices(t.q, t.b));
+});
+V.lmsr_buy_cost.forEach(function (t, i) {
+ check('lmsr_buy_cost[' + i + ']', t.expected, M.lmsr_buy_cost(t.q, t.b, t.i, t.delta));
+});
+V.lmsr_sell_return.forEach(function (t, i) {
+ check('lmsr_sell_return[' + i + ']', t.expected, M.lmsr_sell_return(t.q, t.b, t.i, t.delta));
+});
+V.lmsr_tokens_for_amount.forEach(function (t, i) {
+ check('lmsr_tokens_for_amount[' + i + ']', t.expected, M.lmsr_tokens_for_amount(t.q, t.b, t.i, t.amount));
+});
+V.lmsr_b_from_liquidity.forEach(function (t, i) {
+ check('lmsr_b_from_liquidity[' + i + ']', t.expected, M.lmsr_b_from_liquidity(t.liquidity, t.n));
+});
+
+var total = pass + fail;
+console.log('\nJS verifier: ' + pass + ' / ' + total + ' passed' + (fail ? ' (' + fail + ' FAILED)' : ''));
+process.exit(fail ? 1 : 0);
diff --git a/.qoder/plans/pm-plan/reference/lmsr_fixed_vectors_verify.php b/.qoder/plans/pm-plan/reference/lmsr_fixed_vectors_verify.php
new file mode 100644
index 0000000000..871eb1161b
--- /dev/null
+++ b/.qoder/plans/pm-plan/reference/lmsr_fixed_vectors_verify.php
@@ -0,0 +1,92 @@
+ $t) {
+ $r = _mul_q(gmp_init($t['a']), gmp_init($t['b']));
+ check("mul_q[$i] $t[a]·$t[b]", $t['expected'], gmp_strval($r));
+}
+foreach ($V['div_q'] as $i => $t) {
+ $r = _div_q(gmp_init($t['a']), gmp_init($t['b']));
+ check("div_q[$i] $t[a]/$t[b]", $t['expected'], gmp_strval($r));
+}
+foreach ($V['exp_q'] as $i => $t) {
+ $r = _exp_q(gmp_init($t['x']));
+ check("exp_q[$i] x=$t[x]", $t['expected'], gmp_strval($r));
+}
+foreach ($V['ln_q'] as $i => $t) {
+ $r = _ln_q(gmp_init($t['x']));
+ check("ln_q[$i] x=$t[x]", $t['expected'], gmp_strval($r));
+}
+
+// ---- public API ----
+foreach ($V['lmsr_cost'] as $i => $t) {
+ check("lmsr_cost[$i]", (int)$t['expected'], lmsr_cost($t['q'], (int)$t['b']));
+}
+foreach ($V['lmsr_prices'] as $i => $t) {
+ check("lmsr_prices[$i]", array_map('intval', $t['expected']),
+ lmsr_prices($t['q'], (int)$t['b']));
+}
+foreach ($V['lmsr_buy_cost'] as $i => $t) {
+ check("lmsr_buy_cost[$i]", (int)$t['expected'],
+ lmsr_buy_cost($t['q'], (int)$t['b'], (int)$t['i'], (int)$t['delta']));
+}
+foreach ($V['lmsr_sell_return'] as $i => $t) {
+ check("lmsr_sell_return[$i]", (int)$t['expected'],
+ lmsr_sell_return($t['q'], (int)$t['b'], (int)$t['i'], (int)$t['delta']));
+}
+foreach ($V['lmsr_tokens_for_amount'] as $i => $t) {
+ check("lmsr_tokens_for_amount[$i]", (int)$t['expected'],
+ lmsr_tokens_for_amount($t['q'], (int)$t['b'], (int)$t['i'], (int)$t['amount']));
+}
+foreach ($V['lmsr_b_from_liquidity'] as $i => $t) {
+ check("lmsr_b_from_liquidity[$i]", (int)$t['expected'],
+ lmsr_b_from_liquidity((int)$t['liquidity'], (int)$t['n']));
+}
+
+$total = $pass + $fail;
+echo "\nPHP verifier: $pass / $total passed";
+if ($fail) { echo " ($fail FAILED)\n"; exit(1); }
+echo "\n";
+exit(0);
diff --git a/.qoder/plans/pm-plan/reference/lmsr_vectors_generate.php b/.qoder/plans/pm-plan/reference/lmsr_vectors_generate.php
new file mode 100644
index 0000000000..3ba545c623
--- /dev/null
+++ b/.qoder/plans/pm-plan/reference/lmsr_vectors_generate.php
@@ -0,0 +1,181 @@
+ 1,
+ 'spec' => 'docs/lmsr-fixed-point-spec.md',
+ 'generated_by' => 'module/lmsr_fixed.php (PHP+GMP, normative)',
+ 'numbers_are_decimal_strings' => true,
+ 'note' => 'All numeric values, including Q96 intermediates, are decimal strings. Equality is exact (===); no tolerance is permitted.',
+ 'constants' => [
+ 'ONE' => gstr($ONE),
+ 'HALF' => gstr($C['HALF']),
+ 'LN2' => gstr($LN2),
+ 'EXP_DOMAIN_LIMIT' => gstr($C['EXP_DOMAIN_LIMIT']),
+ 'MAX_B' => gstr($C['MAX_B']),
+ 'MAX_Q' => gstr($C['MAX_Q']),
+ ],
+ 'mul_q' => array_map(function($p){
+ return [
+ 'a' => gstr($p[0]),
+ 'b' => gstr($p[1]),
+ 'expected' => gstr(_mul_q($p[0], $p[1])),
+ ];
+ }, $mul_q_cases),
+ 'div_q' => array_map(function($p){
+ return [
+ 'a' => gstr($p[0]),
+ 'b' => gstr($p[1]),
+ 'expected' => gstr(_div_q($p[0], $p[1])),
+ ];
+ }, $div_q_cases),
+ 'exp_q' => array_map(function($x){
+ return [ 'x' => gstr($x), 'expected' => gstr(_exp_q($x)) ];
+ }, $exp_q_cases),
+ 'ln_q' => array_map(function($x){
+ return [ 'x' => gstr($x), 'expected' => gstr(_ln_q($x)) ];
+ }, $ln_q_cases),
+ 'lmsr_cost' => array_map(function($c){
+ return [ 'q' => $c[0], 'b' => $c[1], 'expected' => lmsr_cost($c[0], $c[1]) ];
+ }, $lmsr_cost_cases),
+ 'lmsr_prices' => array_map(function($c){
+ return [ 'q' => $c[0], 'b' => $c[1], 'expected' => lmsr_prices($c[0], $c[1]) ];
+ }, $lmsr_prices_cases),
+ 'lmsr_buy_cost' => array_map(function($c){
+ return [
+ 'q' => $c[0], 'b' => $c[1], 'i' => $c[2], 'delta' => $c[3],
+ 'expected' => lmsr_buy_cost($c[0], $c[1], $c[2], $c[3]),
+ ];
+ }, $lmsr_buy_cost_cases),
+ 'lmsr_sell_return' => array_map(function($c){
+ return [
+ 'q' => $c[0], 'b' => $c[1], 'i' => $c[2], 'delta' => $c[3],
+ 'expected' => lmsr_sell_return($c[0], $c[1], $c[2], $c[3]),
+ ];
+ }, $lmsr_sell_return_cases),
+ 'lmsr_tokens_for_amount' => array_map(function($c){
+ return [
+ 'q' => $c[0], 'b' => $c[1], 'i' => $c[2], 'amount' => $c[3],
+ 'expected' => lmsr_tokens_for_amount($c[0], $c[1], $c[2], $c[3]),
+ ];
+ }, $lmsr_tokens_for_amount_cases),
+ 'lmsr_b_from_liquidity' => array_map(function($c){
+ return [ 'liquidity' => $c[0], 'n' => $c[1], 'expected' => lmsr_b_from_liquidity($c[0], $c[1]) ];
+ }, $lmsr_b_from_liq_cases),
+];
+
+$path = __DIR__ . '/lmsr_fixed_vectors.json';
+$json = json_encode($out, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
+file_put_contents($path, $json);
+fwrite(STDERR, "wrote " . strlen($json) . " bytes to $path\n");
diff --git a/.qoder/plans/pm-plan/reference/market_math.js b/.qoder/plans/pm-plan/reference/market_math.js
new file mode 100644
index 0000000000..9e73d6a7bb
--- /dev/null
+++ b/.qoder/plans/pm-plan/reference/market_math.js
@@ -0,0 +1,394 @@
+/*
+ * market_math.js — pure market math for Onix prediction markets.
+ *
+ * All amounts are integers in milli-VIZ (precision 1000). LMSR pricing is
+ * bit-deterministic via Q96 fixed-point BigInt arithmetic: this file is a
+ * 1:1 mirror of module/lmsr_fixed.php (PHP+GMP) and the future C++ VIZ DLT
+ * plugin. Every implementation MUST produce identical outputs.
+ *
+ * Two market types, ONE settlement model: the AMM assigns weights
+ * (CPMM for binary, LMSR for multi) and losers fund winners pro-rata.
+ *
+ * Loaded as a browser global (before app.js) AND as a CommonJS module (Node tests).
+ *
+ * @see docs/lmsr-fixed-point-spec.md
+ */
+(function (root) {
+ 'use strict';
+
+ // ============================================================
+ // Constants — frozen by docs/lmsr-fixed-point-spec.md
+ // ============================================================
+ var LMSR_PRECISION = 1000; // 1 VIZ = 1000 milli-VIZ
+ var LMSR_PRICE_PRECISION = 1000000; // price ×10^6, 1e6 = 100%
+
+ // Q96 anchors as BigInt
+ var ONE = 1n << 96n; // 2^96
+ var HALF = 1n << 95n; // 2^95
+ var LN2 = 0xB17217F7D1CF79ABC9E3B398n; // ⌊ln 2 · 2^96⌋
+ var NEG_LN2 = -LN2;
+ var EXP_DOMAIN_LIMIT = 200n * ONE; // §3.3 cutoff
+ var EXP_INPUT_LIMIT = 130n * ONE; // §3.1 hard cap
+ var MAX_B = 1n << 53n;
+ var MAX_Q = 1n << 53n;
+ var THOUSAND = 1000n;
+ var PRICE_PRECISION = 1000000n;
+
+ // ============================================================
+ // Q96 primitives (§2)
+ // JS BigInt: `/` truncates toward 0; `>>` is arithmetic right shift
+ // (rounds toward -∞ for negatives). These are the spec-mandated rules.
+ // ============================================================
+
+ function mul_q(a, b) {
+ // (a · b) >> 96, ASR — matches §3.5
+ return (a * b) >> 96n;
+ }
+
+ function div_q(a, b) {
+ // (a << 96) / b, truncated toward 0
+ return (a << 96n) / b;
+ }
+
+ function trunc_div(a, b) {
+ // truncation toward zero. JS BigInt `/` already truncates, BUT for negative
+ // dividend with negative remainder it returns the truncating quotient. To be
+ // identical to PHP gmp_div_q(_, _, GMP_ROUND_ZERO), we wrap.
+ return a / b;
+ }
+
+ function isOdd(n) {
+ // works for negative bigints too: parity is invariant under sign
+ return ((n % 2n) !== 0n);
+ }
+
+ function abs(n) { return n < 0n ? -n : n; }
+
+ // MSB position (0-indexed) of a positive bigint. Returns -1 for 0.
+ // Used by ln_q range reduction. No FP calls.
+ function msb(x) {
+ if (x <= 0n) return -1;
+ return x.toString(2).length - 1;
+ }
+
+ // ============================================================
+ // Range reduction for exp_q — round-to-nearest-even, §3.5
+ // ============================================================
+ function round_div_LN2(x) {
+ // q = trunc(x / LN2)
+ var q = x / LN2;
+ var r = x - q * LN2;
+ var two_r = r * 2n;
+
+ // Positive side: 2r > LN2 → bump up. 2r == LN2 and q odd → bump up (ties to even).
+ if (two_r > LN2 || (two_r === LN2 && isOdd(q))) {
+ q = q + 1n;
+ }
+ // Negative side: 2r < -LN2 → bump down. 2r == -LN2 and q odd → bump down.
+ if (two_r < NEG_LN2 || (two_r === NEG_LN2 && isOdd(q))) {
+ q = q - 1n;
+ }
+ return q;
+ }
+
+ // ============================================================
+ // exp_q — Taylor with range reduction (§3.1)
+ // ============================================================
+ function exp_q(x) {
+ if (abs(x) > EXP_INPUT_LIMIT) {
+ throw new Error('LMSR_OVERFLOW: exp_q domain exceeded');
+ }
+ // 1. Range reduction
+ var k = round_div_LN2(x);
+ var r = x - k * LN2;
+
+ // 2. Polynomial — exactly 30 iterations
+ var acc = ONE;
+ var term = ONE;
+ for (var n = 1; n <= 30; n++) {
+ // term = mul_q(term, r) / n (mul_q first; truncated divide by integer n)
+ term = mul_q(term, r) / BigInt(n);
+ acc = acc + term;
+ }
+
+ // 3. Scale by 2^k
+ if (k >= 0n) {
+ // Number(k) is safe: |k| ≤ ⌈130/ln 2⌉ ≈ 188
+ return acc << k;
+ } else {
+ return acc >> (-k);
+ }
+ }
+
+ // ============================================================
+ // ln_q — atanh series with range reduction (§3.2)
+ // ============================================================
+ function ln_q(x) {
+ if (x <= 0n) {
+ throw new Error('LMSR_DOMAIN_LN: ln_q called with x ≤ 0');
+ }
+ // 1. p = msb(x) - 96; m = x scaled into [ONE, 2·ONE)
+ var p = msb(x) - 96;
+ var m;
+ if (p >= 0) {
+ m = x >> BigInt(p); // ASR; positive ⇒ truncation
+ } else {
+ m = x << BigInt(-p);
+ }
+
+ // 2. z = (m - ONE) / (m + ONE) in Q96, |z| < 1/3
+ var num = m - ONE;
+ var den = m + ONE;
+ var z = div_q(num, den);
+
+ // 3. atanh series, 50 iterations
+ var z2 = mul_q(z, z);
+ var acc = z;
+ var term = z;
+ for (var k = 1; k <= 50; k++) {
+ term = mul_q(term, z2);
+ var denom = BigInt(2 * k + 1);
+ acc = acc + (term / denom); // truncated divide
+ }
+
+ // 4. ln(m) = 2 · acc
+ var ln_m = acc * 2n;
+
+ // 5. ln(x) = p · LN2 + ln(m)
+ return BigInt(p) * LN2 + ln_m;
+ }
+
+ // ============================================================
+ // log-sum-exp (§3.3)
+ // ============================================================
+ function lse_q(ratios) {
+ if (ratios.length === 0) return 0n;
+ var max = ratios[0];
+ for (var i = 1; i < ratios.length; i++) {
+ if (ratios[i] > max) max = ratios[i];
+ }
+ var acc = 0n;
+ var cutoff = -EXP_DOMAIN_LIMIT;
+ for (var j = 0; j < ratios.length; j++) { // input order, do NOT sort
+ var d = ratios[j] - max;
+ if (d < cutoff) continue; // strict <
+ acc = acc + exp_q(d);
+ }
+ if (acc <= 0n) return max;
+ return max + ln_q(acc);
+ }
+
+ // ============================================================
+ // Domain validation (§1.4)
+ // ============================================================
+ function validate_domain(q, b) {
+ if (q.length < 2 || q.length > 16) {
+ throw new Error('LMSR_INVALID_N: outcomes must be 2..16');
+ }
+ var bb = BigInt(b);
+ if (bb <= 0n) throw new Error('LMSR_INVALID_B: b must be > 0');
+ if (bb > MAX_B) throw new Error('LMSR_INVALID_B: b exceeds MAX_B (2^53)');
+ for (var i = 0; i < q.length; i++) {
+ var qi = BigInt(q[i]);
+ if (qi < 0n) throw new Error('LMSR_INVALID_Q: q[i] < 0');
+ if (qi > MAX_Q) throw new Error('LMSR_INVALID_Q: q[i] exceeds MAX_Q (2^53)');
+ }
+ }
+
+ function ratios_q96(q, b) {
+ var bb = BigInt(b);
+ var out = [];
+ for (var i = 0; i < q.length; i++) {
+ var num = BigInt(q[i]) * ONE;
+ out.push(num / bb); // truncated toward 0
+ }
+ return out;
+ }
+
+ // ============================================================
+ // Public API (§4) — same signatures as module/lmsr_fixed.php
+ // Inputs and outputs are Number (int milli-VIZ) for ergonomics; the
+ // BigInt math is internal. Caller never sees BigInt.
+ // ============================================================
+
+ function lmsr_cost(q, b) {
+ if (b <= 0) return 0;
+ validate_domain(q, b);
+ var ratios = ratios_q96(q, b);
+ var lse = lse_q(ratios);
+ var cost = (BigInt(b) * lse) / ONE; // truncated toward 0
+ return Number(cost);
+ }
+
+ function lmsr_prices(q, b) {
+ var n = q.length;
+ if (b <= 0) return new Array(n).fill(0);
+ validate_domain(q, b);
+ var ratios = ratios_q96(q, b);
+ var max = ratios[0];
+ for (var i = 1; i < n; i++) if (ratios[i] > max) max = ratios[i];
+ var cutoff = -EXP_DOMAIN_LIMIT;
+ var exps = [];
+ var sum = 0n;
+ for (var j = 0; j < n; j++) {
+ var d = ratios[j] - max;
+ if (d < cutoff) {
+ exps.push(0n);
+ } else {
+ var e = exp_q(d);
+ exps.push(e);
+ sum = sum + e;
+ }
+ }
+ if (sum <= 0n) return new Array(n).fill(0);
+ var out = [];
+ for (var k = 0; k < n; k++) {
+ var pq = div_q(exps[k], sum); // Q96
+ var scaled= pq * PRICE_PRECISION;
+ var pi = scaled / ONE; // truncated toward 0
+ out.push(Number(pi));
+ }
+ return out;
+ }
+
+ function lmsr_price(q, b, i) {
+ if (b <= 0 || q[i] === undefined) return 0;
+ var p = lmsr_prices(q, b);
+ return p[i] || 0;
+ }
+
+ function lmsr_buy_cost(q, b, i, delta) {
+ if (b <= 0 || delta <= 0 || q[i] === undefined) return 0;
+ var qa = q.slice(); qa[i] = qa[i] + delta;
+ return Math.max(0, lmsr_cost(qa, b) - lmsr_cost(q, b));
+ }
+
+ function lmsr_sell_return(q, b, i, delta) {
+ if (b <= 0 || delta <= 0 || q[i] === undefined) return 0;
+ if (q[i] - delta < 0) return 0;
+ var qa = q.slice(); qa[i] = qa[i] - delta;
+ return Math.max(0, lmsr_cost(q, b) - lmsr_cost(qa, b));
+ }
+
+ function lmsr_tokens_for_amount(q, b, i, amount) {
+ if (b <= 0 || amount <= 0 || q[i] === undefined) return 0;
+ validate_domain(q, b);
+ var lo = 0;
+ var hi = amount * 10;
+ var maxq = Number(MAX_Q);
+ if (hi > maxq) hi = maxq;
+ var best = 0;
+ for (var iter = 0; iter < 100; iter++) {
+ if (hi - lo <= 1) break;
+ var mid = Math.floor((lo + hi) / 2);
+ if (lmsr_buy_cost(q, b, i, mid) <= amount) { best = mid; lo = mid; }
+ else { hi = mid; }
+ }
+ return best;
+ }
+
+ function lmsr_b_from_liquidity(liquidity, n) {
+ if (liquidity <= 0 || n <= 1) return 0;
+ if (n > 16) throw new Error('LMSR_INVALID_N');
+ var nq = BigInt(n) * ONE;
+ var ln_n = ln_q(nq);
+ var num = BigInt(liquidity) * ONE;
+ return Number(num / ln_n);
+ }
+
+ function lmsr_max_loss(b, n) {
+ if (b <= 0 || n <= 1) return 0;
+ var nq = BigInt(n) * ONE;
+ var ln_n = ln_q(nq);
+ return Number((BigInt(b) * ln_n) / ONE);
+ }
+
+ // Legacy float-style log_sum_exp helper kept for compatibility with any
+ // caller that imported it directly. Implemented via the deterministic
+ // pipeline so old call sites quietly become deterministic too.
+ function lmsr_log_sum_exp(ratios) {
+ // expects an array of floats q_j/b — we round-trip through Q96 to honour
+ // the spec. Caller MUST migrate to lmsr_cost / lmsr_prices for new code.
+ if (!ratios.length) return 0;
+ var qratios = ratios.map(function(r){
+ // scale float to Q96 with truncation toward 0 (best-effort; not
+ // consensus, since the float input is non-deterministic anyway)
+ var sign = r < 0 ? -1n : 1n;
+ var abs_r = Math.abs(r);
+ var int_part = BigInt(Math.trunc(abs_r));
+ var frac = abs_r - Math.trunc(abs_r);
+ var frac_q96 = BigInt(Math.floor(frac * 1e15)) * (ONE / 1000000000000000n);
+ return sign * (int_part * ONE + frac_q96);
+ });
+ var lse = lse_q(qratios);
+ // return as Number (real units) — non-consensus convenience
+ return Number(lse) / Number(ONE);
+ }
+
+ // ============================================================
+ // Parimutuel settlement (binary AND multi) — INTEGER ONLY
+ // Unchanged from the previous market_math.js; already deterministic.
+ // ============================================================
+
+ function parimutuel_winners_pool(winning_bets_sum, total_bets_sum, fee_permille) {
+ var losers = Math.max(0, (parseInt(total_bets_sum) || 0) - (parseInt(winning_bets_sum) || 0));
+ var wp = Math.floor(losers * (1000 - (parseInt(fee_permille) || 0)) / 1000);
+ return wp > 0 ? wp : 0;
+ }
+
+ function parimutuel_payout(weight, bet_amount, winning_bets_sum, total_bets_sum, total_winning_weight, fee_permille) {
+ var wp = parimutuel_winners_pool(winning_bets_sum, total_bets_sum, fee_permille);
+ var tw = parseInt(total_winning_weight) || 0;
+ var share = (tw > 0) ? Math.floor(wp * (parseInt(weight) || 0) / tw) : 0;
+ return { winners_pool: wp, share: share, payout: (parseInt(bet_amount) || 0) + share, profit: share };
+ }
+
+ function market_fee_permille(m) {
+ return (parseInt(m.oracle_fee) || 0) + (parseInt(m.creator_fee) || 0) + (parseInt(m.liquidity_fee) || 0);
+ }
+
+ function parimutuel_estimate(m, outcome, weight, bet_amount, is_new) {
+ var fee = market_fee_permille(m), w = parseInt(weight) || 0;
+ if (parseInt(m.market_type) == 1) {
+ var oc = (m.outcomes || [])[parseInt(outcome)] || {};
+ var win_bets = parseInt(oc.bets_sum) || 0;
+ var total = parseInt(m.bets_sum) || 0;
+ var win_w = (parseInt(oc.weight_sum) || 0) + (is_new ? w : 0);
+ return parimutuel_payout(w, bet_amount, win_bets, total, win_w, fee);
+ }
+ var a_b = parseInt(m.a_bets_sum) || 0, b_b = parseInt(m.b_bets_sum) || 0;
+ var win_bets2 = (0 == outcome) ? a_b : b_b;
+ var win_w2 = ((0 == outcome) ? (parseInt(m.a_weight_sum) || 0) : (parseInt(m.b_weight_sum) || 0)) + (is_new ? w : 0);
+ return parimutuel_payout(w, bet_amount, win_bets2, a_b + b_b, win_w2, fee);
+ }
+
+ // ============================================================
+ // Exports
+ // ============================================================
+ var api = {
+ LMSR_PRECISION: LMSR_PRECISION,
+ LMSR_PRICE_PRECISION: LMSR_PRICE_PRECISION,
+ // LMSR (deterministic Q96)
+ lmsr_cost: lmsr_cost,
+ lmsr_prices: lmsr_prices,
+ lmsr_price: lmsr_price,
+ lmsr_buy_cost: lmsr_buy_cost,
+ lmsr_sell_return: lmsr_sell_return,
+ lmsr_tokens_for_amount: lmsr_tokens_for_amount,
+ lmsr_b_from_liquidity: lmsr_b_from_liquidity,
+ lmsr_max_loss: lmsr_max_loss,
+ lmsr_log_sum_exp: lmsr_log_sum_exp, // legacy compat
+ // Q96 primitives — exported for tests / advanced clients
+ _Q96: { ONE: ONE, HALF: HALF, LN2: LN2, exp_q: exp_q, ln_q: ln_q, mul_q: mul_q, div_q: div_q, msb: msb, lse_q: lse_q },
+ // Parimutuel (already integer)
+ parimutuel_winners_pool: parimutuel_winners_pool,
+ parimutuel_payout: parimutuel_payout,
+ market_fee_permille: market_fee_permille,
+ parimutuel_estimate: parimutuel_estimate
+ };
+
+ if (typeof module !== 'undefined' && module.exports) { module.exports = api; }
+ for (var key in api) { root[key] = api[key]; }
+ root.MarketMath = api;
+
+})(typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : this));
diff --git a/.qoder/plans/pm-plan/security-threat-model.md b/.qoder/plans/pm-plan/security-threat-model.md
new file mode 100644
index 0000000000..83912f6701
--- /dev/null
+++ b/.qoder/plans/pm-plan/security-threat-model.md
@@ -0,0 +1,296 @@
+# Onix Protocol: Security and Threat Model
+
+**Purpose:** Threat model for the Onix Protocol prediction market system. Structured for security audit review. Each threat follows: Attack → Preconditions → Impact → Mitigation → Residual Risk.
+
+---
+
+## 1. Asset Inventory
+
+| Asset | Location | Value at Risk |
+|-------|----------|--------------|
+| User bet balances | Market reserves (CPMM/LMSR) | Full bet amount |
+| Oracle insurance deposits | Oracle account balance | Min 5,000 VIZ per oracle |
+| LP principal | Market reserves | Full deposit amount |
+| LP fee earnings | Fee pool (computed at resolution) | Variable (losers' pool × fee‰) |
+| Dispute escrow | Dispute record | 1,000 VIZ per dispute |
+| Lazy Pool deposits | Pool free balance + market allocations | Total pool value |
+| DAO fund | System account | Accumulated fees and penalties |
+
+## 2. Trust Assumptions
+
+| Assumption | Scope | Consequence if Violated |
+|------------|-------|------------------------|
+| CPMM `x * y = k` invariant holds through all operations | Core protocol | LP principal guarantee fails |
+| `floor()` rounding is consistent across all fee calculations | Core protocol | Rounding dust exploits |
+| Oracle insurance ≥ potential manipulation profit | Economic security | Oracle manipulation becomes rational |
+| Dispute resolver acts honestly within 14 days | Dispute system | Auto-close fallback activates (bounded damage) |
+| Validators validate all `pm_*` operations correctly | VIZ DLT consensus | State divergence between nodes |
+| Virtual operations are deterministic across all nodes | VIZ DLT consensus | Consensus fork |
+
+## 3. Threat Actors
+
+| Actor | Motivation | Capabilities |
+|-------|-----------|-------------|
+| **Malicious Oracle** | Profit from misresolution | Controls resolution outcome; can create alt accounts for self-betting |
+| **Griefing Bettor** | Disrupt oracle/market operations | Can place bets, file disputes, inflate risk scores |
+| **Compromised Resolver** | Profit or sabotage | Can issue incorrect verdicts, delay indefinitely |
+| **Front-Runner** (VIZ DLT) | Extract value from pending transactions | Can observe mempool, submit transactions with favorable ordering |
+| **LP Sniper** | Disproportionate fee extraction | Can deposit small amounts immediately after market creation |
+| **Opportunity-Cost Attacker** | Lock Lazy Pool capital in idle markets | Can create markets as oracle, never generate volume |
+
+---
+
+## 4. Threats
+
+### 4.1 Oracle Manipulation (Self-Betting + Misresolution)
+
+**Attack:** Oracle accepts a market, bets heavily on one side via alt accounts, then resolves the market in favor of that side regardless of the actual outcome.
+
+**Preconditions:**
+- Oracle's potential bet winnings exceed their insurance bond
+- Market has a dispute resolver, but the resolver may be slow or compromised
+- Oracle can create alt accounts (sybil)
+
+**Impact:** Bettors on the honest side lose their full stakes. Oracle profits from misresolution minus insurance penalty.
+
+**Mitigation:**
+- Insurance slashing: committee can seize the oracle's full insurance (up to entire bond)
+- Dispute system: any bettor can challenge within 12h grace period
+- Reputation scoring: dispute losses permanently damage the oracle's reliability score
+- Committee ban powers: permanent or time-limited oracle ban
+- Risk score system: markets where oracle insurance < total bets are flagged/hidden
+
+**Residual risk:** If `bet_winnings > oracle_insurance`, manipulation is economically rational. The protocol relies on the dispute resolver catching the manipulation and the insurance bond being proportional to market volume. **Markets without adequate insurance-to-volume ratio remain vulnerable.**
+
+### 4.2 Oracle Collusion with Resolver
+
+**Attack:** Oracle and dispute resolver collude. Oracle misresolves; resolver rejects any disputes (oracle was "right"), causing disputers to lose their fees.
+
+**Preconditions:**
+- Resolver is a single account (not a genuine multisig)
+- Resolver and oracle share economic interests
+
+**Impact:** Bettors lose stakes AND dispute fees. Oracle and resolver split profits.
+
+**Mitigation:**
+- Recommended multisig for resolver (collegial decision)
+- Platform-curated resolver whitelist (prototype)
+- Delegate-curated on-chain registry (VIZ DLT)
+- 14-day auto-close: if resolver is truly inactive, dispute auto-closes with refunds
+- Community can vote out delegates who approve colluding resolvers
+
+**Residual risk:** A compromised multisig (majority of signers colluding) defeats this protection. The protocol has no algorithmic defense against resolver corruption — it relies on social accountability and governance.
+
+### 4.3 Risk Score Griefing
+
+**Attack:** Adversary inflates `total_bets` on an oracle's markets to push the global risk score (`oracle_insurance / total_bets_all_markets`) below the listing threshold (default 2.5×), hiding all of the oracle's markets from default listing.
+
+**Preconditions:**
+- Attacker has capital to bet (locked until resolution, not necessarily lost)
+- Oracle's insurance is not vastly larger than existing bets
+
+**Impact:** All of the target oracle's active markets are hidden from default listing. Censorship attack on competitor oracles.
+
+**Mitigation:**
+- Markets are hidden, not deleted — "Show risky markets" checkbox reveals them
+- Oracle can counter by depositing more insurance (instant, no fee)
+- Attacker's capital is locked until resolution
+- Bet cancellation reverses the attack effect
+- Only active markets (status=1) are filtered
+
+**Residual risk:** Attack reduces discoverability. Sophisticated users are unaffected (they can toggle the filter), but casual users may not find the markets. **Planned mitigation:** per-market risk score floor, rate-limiting via EMA, reputation-weighted listing exemptions.
+
+### 4.4 Front-Running (VIZ DLT)
+
+**Attack:** Attacker observes a pending `pm_place_bet` in the mempool, submits their own bet first (at a better price), then allows the victim's bet to execute (at a worse price). Classic sandwich attack.
+
+**Preconditions:**
+- VIZ DLT mempool is observable
+- Attacker can submit transactions with favorable ordering (e.g., via delegate collusion or network latency advantage)
+
+**Impact:** Victim receives fewer tokens than expected. Attacker profits from the price difference.
+
+**Mitigation:**
+- `min_tokens` parameter on `pm_place_bet`: rejects if tokens received < user's minimum
+- `min_return` parameter on `pm_cancel_bet`: rejects if returned amount < user's minimum
+- **Planned:** commit-reveal scheme (commit hash first, reveal bet after block confirmation)
+- **Under evaluation:** batch auction model (collect bets over N seconds, execute at uniform clearing price)
+
+**Residual risk:** `min_tokens` mitigates but doesn't prevent — attacker can still extract value within the user's slippage tolerance. **Commit-reveal is essential for on-chain fairness** and is high priority for VIZ DLT implementation.
+
+### 4.5 LP Sniping (Time-Weight Farming)
+
+**Attack:** Alt accounts deposit small amounts immediately after market creation to capture disproportionate time-weighted fee shares (high `sec_to_expiration` with minimal capital).
+
+**Preconditions:**
+- Market is newly created (maximum `sec_to_expiration`)
+- No minimum LP lock period
+
+**Impact:** Small deposits earn disproportionate absolute fees per VIZ relative to their capital commitment.
+
+**Mitigation:**
+- Market creator is already the first LP with maximum time-weight
+- Capital is locked for the full market duration (opportunity cost)
+- Fee earnings are proportional to `amount × sec_to_expiration` — small deposits earn small absolute fees
+- Minimum liquidity floor (100 VIZ) prevents dust deposits from fragmenting the pool
+
+**Residual risk:** The attack is self-limiting (small capital = small absolute returns), but it does dilute the creator's fee share. **Planned mitigations:** minimum LP lock period, sigmoid time-weight curve, maximum time-weight multiplier cap.
+
+### 4.6 LP Withdrawal Timing Attack
+
+**Attack:** LP observes the likely outcome (based on price movement near expiration) and withdraws liquidity to avoid being in the pool for an unfavorable resolution.
+
+**Preconditions:**
+- LP can withdraw while betting is open
+- LP has information about likely outcome
+
+**Impact:** Remaining participants face reduced liquidity depth. LP avoids potential losses.
+
+**Mitigation:**
+- **Hard block after betting expiration:** LP withdrawal is only allowed while `time < betting_expiration`. Once betting closes, all LP positions are locked until resolution.
+- **No impermanent loss:** LP principal is safe regardless of outcome in both Onix Binary and Multi — there is no incentive to withdraw based on odds shifting.
+- **Time-ratio penalty:** Early exit receives only `time_ratio`-discounted fees.
+- **Minimum liquidity floor:** Cannot reduce `liquidity_sum` below 100 VIZ.
+- **Reserve depletion guard:** Cannot withdraw if reserves would reach 0.
+
+**Residual risk:** During the betting period (before expiration), LPs can still withdraw. However, since LP principal is guaranteed regardless of outcome, the rational motivation to withdraw is limited to opportunity cost, not loss avoidance.
+
+### 4.7 Dispute Denial-of-Resolution
+
+**Attack:** Compromised or inactive resolver never acts on disputes, freezing all payouts indefinitely.
+
+**Preconditions:**
+- Resolver is inactive, compromised, or deliberately stalling
+- Dispute is filed and payouts are frozen
+
+**Impact:** All funds (bets + LP) frozen for the duration of the dispute.
+
+**Mitigation:**
+- **14-day auto-close:** `dispute_auto_close_days` guarantees disputes are resolved automatically if the resolver is inactive
+- Auto-close: all bets and LP refunded, oracle penalized, disputer's fee returned
+- Oracle is penalized even in auto-close (the dispute wouldn't exist without a questionable resolution)
+
+**Residual risk:** Funds are frozen for up to 14 days. This is a bounded inconvenience, not a permanent attack. The 14-day parameter is delegate-adjustable.
+
+### 4.8 Oracle Insurance Depletion
+
+**Attack:** Multiple simultaneous disputes across different markets drain the oracle's insurance to zero. Subsequent disputes have no insurance to slash.
+
+**Preconditions:**
+- Oracle has many active markets
+- Multiple disputes filed simultaneously
+
+**Impact:** Later disputes have reduced or zero reward pool (insurance exhausted). Dispute incentives break down.
+
+**Mitigation:**
+- Insurance checks use current balance at dispute resolution time
+- `reward_pool = min(dispute_fee × multiplier, oracle_insurance)` — capped at available insurance
+- Oracle cannot withdraw insurance while active markets exist
+- Low insurance triggers risk score warnings and listing filters
+
+**Residual risk:** An oracle with many markets and marginal insurance can have their economic security diluted across disputes. **Users should evaluate the oracle's insurance-to-total-volume ratio** (shown as risk score) before betting.
+
+### 4.9 Cancellation Slippage Exploitation
+
+**Attack:** Not a protocol attack, but a user-experience risk. Users cancel bets after price movement and receive significantly less than their original stake, perceiving this as a platform error.
+
+**Preconditions:**
+- Market price has moved since the user's bet (other bets placed)
+- User cancels expecting a full refund
+
+**Impact:** User receives less than original stake. Generates support tickets and trust erosion.
+
+**Mitigation:**
+- Mandatory confirmation modal showing exact return amount, slippage %, and loss
+- Red loss warning when slippage exceeds 2%
+- `min_return` parameter prevents cancellation if price moved since preview
+- Explicit explanation: "This is price movement (slippage), not a platform error"
+
+**Residual risk:** Users who don't read the modal may still be surprised. This is inherent to market-priced position exit and exists in every AMM and exchange.
+
+### 4.10 Lazy Pool Opportunity-Cost Attack
+
+**Attack:** Malicious oracle creates N long-duration markets with subjective questions, attracting lazy pool allocations that lock pool capital in zero-volume markets.
+
+**Preconditions:**
+- Oracle can create markets that the pool auto-allocates to
+- Markets have long expiration and generate no betting volume
+
+**Impact:** Pool capital locked in idle markets, earning zero fees. Depositors suffer opportunity cost.
+
+**Mitigation:**
+- **Graduated early recall:** Idle markets lose 10% allocation per 10% of duration with insufficient volume
+- **Active market penalty:** 5% recursive reduction per active market from the same oracle
+- **Fault penalty stamps:** Zero-volume resolutions generate stamps that reduce future allocations (expire after 10 days)
+- `max_total_allocation` (70%) ensures pool always retains reserves
+
+**Residual risk:** Pool capital is still locked temporarily (until recall kicks in). A determined attacker can force the pool to hold ~40% of allocation on idle markets for their full duration. The graduated recall limits but does not eliminate the opportunity cost.
+
+### 4.11 Resolver Trust and Self-Judging
+
+**Attack:** Market created with `committee_id=0` (no resolver) or with a resolver controlled by the oracle.
+
+**Preconditions:**
+- `committee_id=0` is allowed, or resolver is not genuinely independent
+
+**Impact:** No dispute recourse. Oracle can misresolve with impunity.
+
+**Mitigation:**
+- **`committee_id=0` is forbidden:** Every market must have a valid resolver
+- **Platform-curated whitelist:** UI only shows trusted, pre-approved resolvers
+- **Default resolver:** `predict-market-resolver` multisig operated by the platform
+- **No self-judging:** Resolver is always a third party with no financial stake in market outcome
+
+**Residual risk:** In the prototype, the whitelist is centrally managed. On VIZ DLT, it transitions to a delegate-curated on-chain registry. The trust anchor shifts from platform admin to elected delegates.
+
+### 4.12 VIZ DLT Consensus Security
+
+**Attack:** Malicious delegate(s) include invalid `pm_*` operations or produce incorrect virtual operations.
+
+**Preconditions:**
+- Attacker controls one or more delegates (validators)
+- Other nodes do not validate correctly
+
+**Impact:** Incorrect market state, invalid payouts, consensus fork.
+
+**Mitigation:**
+- Every `pm_*` operation is validated by **every** node, not just the validator
+- Invalid operations are rejected at block inclusion — a single corrupt delegate cannot force invalid state
+- Virtual operations are deterministic (same code, same state → same output on every node)
+- Stakeholders can vote out compromised delegates
+
+**Residual risk:** If a **majority** of delegates collude, they could theoretically alter consensus rules via a coordinated hard fork. This is the fundamental DPoS trust assumption — identical to EOS, Hive, Tron, and every other DPoS chain.
+
+---
+
+## 5. Security Invariants
+
+The following invariants should be verified in a security audit:
+
+| Invariant | Scope | Verification |
+|-----------|-------|-------------|
+| CPMM `k = reserve_a × reserve_b` maintained through all bet/cancel operations | Onix Binary | k only changes on liquidity add/withdraw |
+| `reserve_a + reserve_b ≥ initial_liquidity` after any sequence of bets | Onix Binary (LP safety) | AM-GM inequality |
+| LP subsidy returned unconditionally at resolution | Onix Multi | Subsidy is architecturally separate from payout pool |
+| `Σ price(i) = 1` for all market states | Onix Multi | Softmax property |
+| No integer overflow/underflow in fee calculations | All | All amounts use `intval()` and milli-VIZ precision |
+| Time penalty applies only to profit, never principal | All | `net_payout ≥ bet_amount` for all winners |
+| Dispute recalculation produces identical results to fresh resolution with correct outcome | Dispute system | Mathematical equivalence |
+| LP withdrawal cannot deplete reserves below zero | Liquidity | Safety check: `new_reserve_a > 0 && new_reserve_b > 0` |
+| Oracle insurance cannot be withdrawn while active markets exist | Oracle | Enforced in `oracle-withdraw-insurance` |
+| Multiple disputes cannot drain insurance below zero | Oracle | Insurance checks use current balance |
+| `committee_id=0` markets cannot be created | Resolver trust | Enforced at market creation |
+| Bet cancellation cannot return more than total reserves | Cancellation | Safety floor: `amount_returned = max(0, ...)` |
+| All `floor()` rounding is consistent; dust goes to DAO fund | Fee calculation | Verified per payout cycle |
+| Virtual operations produce identical results on all nodes | VIZ DLT | Deterministic consensus code |
+
+---
+
+## 6. Audit Trail
+
+Every state-changing operation on a market is recorded in the `market_log` table with before/after snapshots:
+
+- Fields: reserves, k value, liquidity sum, operation type, amounts, user IDs
+- Logged actions: `bet`, `cancel`, `liquidity_add`, `liquidity_withdraw`, `accept`, `reject`, `resolution`, `dispute`, `dispute_resolve`, `payout`, `penalty`
+- Purpose: complete, immutable audit trail for dispute investigation and post-mortem analysis
+- On VIZ DLT: all operations and virtual operations appear in the block stream, providing native auditability
diff --git a/.qoder/plans/pm-plan/tests/workflow_test.php b/.qoder/plans/pm-plan/tests/workflow_test.php
new file mode 100644
index 0000000000..68aac49328
--- /dev/null
+++ b/.qoder/plans/pm-plan/tests/workflow_test.php
@@ -0,0 +1,4953 @@
+ 10000, // 10 VIZ
+ 'creator_registration_fee' => 10000, // 10 VIZ
+ 'min_oracle_insurance' => 5000000, // 5000 VIZ
+ 'dispute_fee' => 1000000, // 1000 VIZ
+ 'dispute_grace_hours' => 12,
+ 'oracle_penalty_percent' => 5,
+ 'default_time_penalty_percent'=> 5, // 5% (was 10%)
+ 'max_time_penalty' => 1000000, // 100% (precision=6)
+ 'market_creation_fee' => 5000, // 5 VIZ
+ 'penalty_curve_type' => 1, // 0=linear, 1=quadratic (default)
+ 'dispute_resolver_account_id'=> 2, // dispute resolver (recommended multisig)
+ 'dao_fund_account_id' => 2, // DAO fund account for fees (market creation, dispute, penalties)
+ 'min_risk_score_betting' => 1.5, // block bets below this risk score
+ 'min_risk_score_listing' => 2.5, // hide from listing below this risk score
+ 'oracle_dispute_response_hours' => 12, // hours oracle has to respond, auto-penalty if missed
+ 'oracle_no_contest_penalty_percent' => 50, // % of dispute_fee as penalty on voluntary no-contest
+ 'dispute_auto_close_days' => 14, // days before unresolved disputes auto-close
+ 'dispute_reward_multiplier' => 3.0, // float multiplier for oracle insurance carve-out on upheld dispute
+ 'dispute_rejected_oracle_permille' => 500, // permille of dispute_fee to oracle when dispute rejected (50%)
+ 'dispute_rejected_voter_permille' => 500, // permille of dispute_fee to resolver/voters when dispute rejected (50%)
+];
+
+// ============================================================
+// HELPER FUNCTIONS
+// ============================================================
+function step($name, $data) {
+ static $step_num = 0;
+ $step_num++;
+ echo "\n=== STEP {$step_num}: {$name} ===\n";
+ echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
+}
+
+function make_user($id, $name, $balance = 0) {
+ return [
+ 'id' => $id, 'name' => $name, 'balance' => $balance,
+ 'oracle' => 0, 'oracle_insurance' => 0, 'oracle_fee' => 0, 'oracle_fixed_fee' => 5000,
+ 'create_market' => 0, 'add_liquidity' => 0, 'committee' => 0,
+ 'bets_balance' => 0, 'liquidity_balance' => 0,
+ 'oracle_banned' => 0, 'oracle_ban_until' => 0,
+ 'creator_banned' => 0, 'creator_ban_until' => 0,
+ ];
+}
+
+function register_oracle(&$user, $fee, $oracle_fee, $settings, $oracle_fixed_fee = 5000) {
+ if (1 == $user['oracle']) {
+ // Already registered: update profile without fee
+ $user['oracle_fee'] = $oracle_fee;
+ $user['oracle_fixed_fee'] = $oracle_fixed_fee;
+ return ['status' => true, 'updated' => true];
+ }
+ $reg_fee = $settings['oracle_registration_fee'];
+ if ($user['balance'] < $reg_fee) return ['error' => 'Insufficient balance'];
+ $user['balance'] -= $reg_fee;
+ $user['oracle'] = 1;
+ $user['oracle_fee'] = $oracle_fee; // permille: 5 = 0.5%
+ $user['oracle_fixed_fee'] = $oracle_fixed_fee;
+ return ['status' => true, 'paid' => $reg_fee];
+}
+
+function oracle_deposit_insurance(&$user, $amount) {
+ if ($user['balance'] < $amount) return ['error' => 'Insufficient balance'];
+ $user['balance'] -= $amount;
+ $user['oracle_insurance'] += $amount;
+ return ['status' => true];
+}
+
+function register_creator(&$user, $settings) {
+ $reg_fee = $settings['creator_registration_fee'];
+ if ($user['balance'] < $reg_fee) return ['error' => 'Insufficient balance'];
+ $user['balance'] -= $reg_fee;
+ $user['create_market'] = 1;
+ $user['add_liquidity'] = 1;
+ return ['status' => true, 'paid' => $reg_fee];
+}
+
+function create_market(&$creator, $oracle, $liquidity, $oracle_fee_permille, $liquidity_fee_permille,
+ $market_time, $betting_exp, $result_exp, $penalty_type, $penalty_value,
+ $committee_id, $settings, $penalty_curve_type = -1, &$committee_user = null) {
+ $market_creation_fee = $settings['market_creation_fee'];
+ $total_required = $liquidity + $market_creation_fee;
+ if ($creator['balance'] < $total_required) return ['error' => 'Insufficient balance (need '.($total_required/1000).' VIZ)'];
+ if ($liquidity < 100000) return ['error' => 'Min liquidity 100 VIZ'];
+ if ($oracle_fee_permille < $oracle['oracle_fee']) return ['error' => 'Oracle fee too low'];
+
+ // BUG-1 is here in real code: min(0,$liq_fee) — we simulate correctly with max
+ $liquidity_fee_permille = max(0, $liquidity_fee_permille);
+
+ // Oracle fixed fee from oracle's profile
+ $oracle_fixed_fee = $oracle['oracle_fixed_fee'];
+
+ // Penalty curve type: default from system settings if not specified
+ if ($penalty_curve_type !== 0 && $penalty_curve_type !== 1) {
+ $penalty_curve_type = $settings['penalty_curve_type'];
+ }
+
+ $part_a = intval(floor($liquidity / 2));
+ $part_b = $liquidity - $part_a;
+ $k = $part_a * $part_b;
+ $status = ($creator['id'] == $oracle['id']) ? 1 : 0;
+
+ $creator['balance'] -= $liquidity;
+ $creator['liquidity_balance'] += $liquidity;
+
+ // Deduct market creation fee and send to committee/DAO
+ if ($market_creation_fee > 0) {
+ $creator['balance'] -= $market_creation_fee;
+ if ($committee_user !== null) {
+ $committee_user['balance'] += $market_creation_fee;
+ }
+ }
+
+ $market = [
+ 'id' => 1, 'user' => $creator['id'], 'oracle' => $oracle['id'], 'status' => $status,
+ 'reserve_a' => $part_a, 'reserve_b' => $part_b, 'k' => $k,
+ 'liquidity_sum' => $liquidity,
+ 'oracle_fee' => $oracle_fee_permille, 'liquidity_fee' => $liquidity_fee_permille,
+ 'oracle_fee_earned' => 0, 'liquidity_fee_earned' => 0,
+ 'oracle_fixed_fee' => $oracle_fixed_fee,
+ 'bets_sum' => 0, 'a_bets_sum' => 0, 'b_bets_sum' => 0,
+ 'time' => $market_time, 'betting_expiration' => $betting_exp, 'result_expiration' => $result_exp,
+ 'time_penalty_type' => $penalty_type, 'time_penalty_value' => $penalty_value,
+ 'penalty_curve_type' => $penalty_curve_type,
+ 'committee' => $committee_id,
+ 'allow_early_resolution' => 0, 'allow_cancellation' => 1,
+ 'resolved_outcome' => null, 'payout_status' => 0, 'decision' => '',
+ ];
+
+ $sec_to_exp = $betting_exp - $market_time;
+ $lp = [
+ 'id' => 1, 'market' => 1, 'user' => $creator['id'],
+ 'amount' => $liquidity, 'weight_a' => $part_a, 'weight_b' => $part_b,
+ 'sec_to_expiration' => $sec_to_exp,
+ 'status' => 0, 'earned_fee' => 0,
+ ];
+
+ return ['status' => true, 'market' => $market, 'lp' => $lp];
+}
+
+function oracle_accept(&$market, &$oracle, $settings) {
+ if ($market['oracle'] != $oracle['id']) return ['error' => 'Not oracle'];
+ if ($market['status'] != 0) return ['error' => 'Not pending'];
+ if ($oracle['oracle_insurance'] < $settings['min_oracle_insurance'])
+ return ['error' => 'Insufficient insurance'];
+ $market['status'] = 1;
+ return ['status' => true];
+}
+
+function oracle_reject(&$market, &$creator, $lps) {
+ if ($market['status'] != 0) return ['error' => 'Not pending'];
+ // Return all liquidity
+ $returned = 0;
+ foreach ($lps as &$lp) {
+ if ($lp['market'] == $market['id'] && $lp['status'] == 0) {
+ $creator['balance'] += $lp['amount'];
+ $creator['liquidity_balance'] -= $lp['amount'];
+ $lp['status'] = 2;
+ $returned += $lp['amount'];
+ }
+ }
+ $market['status'] = -1; // deleted
+ return ['status' => true, 'returned' => $returned];
+}
+
+function place_bet(&$market, &$user, $side, $amount, $current_time) {
+ if ($market['status'] != 1) return ['error' => 'Market not active'];
+ if ($current_time >= $market['betting_expiration']) return ['error' => 'Betting expired'];
+ if ($user['balance'] < $amount) return ['error' => 'Insufficient balance'];
+
+ // Time penalty
+ $time_penalty = 0;
+ $time_to_exp = $market['betting_expiration'] - $current_time;
+ $penalty_window = 0;
+ if ($market['time_penalty_type'] == 0) {
+ $penalty_window = $market['time_penalty_value'];
+ } else {
+ $market_duration = $market['betting_expiration'] - $market['time'];
+ $penalty_window = intval($market_duration * $market['time_penalty_value'] / 100);
+ }
+ if ($penalty_window > 0 && $time_to_exp < $penalty_window) {
+ $penalty_ratio = 1 - ($time_to_exp / $penalty_window);
+ // Apply curve type: 0=linear, 1=quadratic
+ $curve_type = isset($market['penalty_curve_type']) ? $market['penalty_curve_type'] : 1;
+ if (1 == $curve_type) {
+ $penalty_ratio = $penalty_ratio * $penalty_ratio; // quadratic: ratio^2
+ }
+ $time_penalty = intval($penalty_ratio * 1000000); // precision=6, max 100%
+ }
+
+ // CPMM
+ $ra = $market['reserve_a'];
+ $rb = $market['reserve_b'];
+ $k = $market['k'];
+ if ($side == 0) {
+ $new_rb = $rb + $amount;
+ $new_ra = intval($k / $new_rb);
+ $tokens = $ra - $new_ra;
+ } else {
+ $new_ra = $ra + $amount;
+ $new_rb = intval($k / $new_ra);
+ $tokens = $rb - $new_rb;
+ }
+ if ($tokens <= 0) return ['error' => 'Trade too small'];
+ $price = intval($amount * 1000000 / $tokens);
+
+ // Fees
+ $bet_oracle_fee = intval($amount * $market['oracle_fee'] / 1000);
+ $bet_liq_fee = intval($amount * $market['liquidity_fee'] / 1000);
+
+ // Update state
+ $user['balance'] -= $amount;
+ $user['bets_balance'] += $amount;
+ $market['reserve_a'] = ($side == 0) ? $new_ra : $new_ra;
+ $market['reserve_b'] = ($side == 0) ? $new_rb : $new_rb;
+ // Fees no longer accumulated on market — computed from losing side at resolution
+ $market['bets_sum'] += $amount;
+ if ($side == 0) $market['a_bets_sum'] += $amount;
+ else $market['b_bets_sum'] += $amount;
+
+ static $bet_counter = 0;
+ $bet_counter++;
+
+ $bet = [
+ 'id' => $bet_counter, 'market' => $market['id'], 'user' => $user['id'],
+ 'side' => $side, 'amount' => $amount, 'weight' => $tokens, 'price' => $price,
+ 'oracle_fee' => $bet_oracle_fee, 'liquidity_fee' => $bet_liq_fee,
+ 'time_penalty' => $time_penalty, 'status' => 0, 'resolved_amount' => 0,
+ ];
+
+ return [
+ 'status' => true, 'bet' => $bet,
+ 'tokens' => $tokens, 'price' => $price, 'time_penalty' => $time_penalty,
+ 'reserves' => ['a' => $market['reserve_a'], 'b' => $market['reserve_b'], 'k' => $k],
+ 'fees' => ['oracle' => $bet_oracle_fee, 'liquidity' => $bet_liq_fee],
+ ];
+}
+
+function cancel_bet(&$market, &$user, &$bet) {
+ if ($bet['status'] != 0) return ['error' => 'Bet not active'];
+ $ra = $market['reserve_a'];
+ $rb = $market['reserve_b'];
+ $k = $market['k'];
+ $tokens = $bet['weight'];
+ if ($bet['side'] == 0) {
+ $new_ra = $ra + $tokens;
+ $new_rb = intval($k / $new_ra);
+ $returned = $rb - $new_rb;
+ } else {
+ $new_rb = $rb + $tokens;
+ $new_ra = intval($k / $new_rb);
+ $returned = $ra - $new_ra;
+ }
+ if ($returned <= 0) $returned = 0;
+
+ $market['reserve_a'] = $new_ra;
+ $market['reserve_b'] = $new_rb;
+ // Fees no longer tracked on market — no reversal needed
+ $market['bets_sum'] -= $bet['amount'];
+ if ($bet['side'] == 0) $market['a_bets_sum'] -= $bet['amount'];
+ else $market['b_bets_sum'] -= $bet['amount'];
+
+ $user['balance'] += $returned;
+ $user['bets_balance'] -= $bet['amount'];
+ $bet['status'] = 1;
+ $bet['resolved_amount'] = $returned;
+
+ return ['status' => true, 'returned' => $returned, 'original' => $bet['amount'],
+ 'slippage' => $returned - $bet['amount']];
+}
+
+function add_liquidity(&$market, &$user, $amount, &$lps, $current_time = null) {
+ if ($user['balance'] < $amount) return ['error' => 'Insufficient balance'];
+ $ra = $market['reserve_a'];
+ $rb = $market['reserve_b'];
+ $total = $ra + $rb;
+ $add_a = intval($amount * $ra / $total);
+ $add_b = $amount - $add_a;
+
+ $market['reserve_a'] += $add_a;
+ $market['reserve_b'] += $add_b;
+ $market['k'] = $market['reserve_a'] * $market['reserve_b'];
+ $market['liquidity_sum'] += $amount;
+
+ $user['balance'] -= $amount;
+ $user['liquidity_balance'] += $amount;
+
+ $deposit_time = ($current_time !== null) ? $current_time : $market['time'];
+ $sec_to_exp = max(1, $market['betting_expiration'] - $deposit_time);
+ $lp = [
+ 'id' => count($lps) + 1, 'market' => $market['id'], 'user' => $user['id'],
+ 'amount' => $amount, 'weight_a' => $add_a, 'weight_b' => $add_b,
+ 'sec_to_expiration' => $sec_to_exp,
+ 'status' => 0, 'earned_fee' => 0, 'time_deposited' => $deposit_time,
+ ];
+ $lps[] = $lp;
+
+ return ['status' => true, 'lp' => $lp,
+ 'reserves' => ['a' => $market['reserve_a'], 'b' => $market['reserve_b'], 'k' => $market['k']]];
+}
+
+function withdraw_liquidity(&$market, &$user, &$lp, $withdraw_amount, &$lps, $current_time, $settings) {
+ $lp_amount = $lp['amount'];
+ $weight_a = $lp['weight_a'];
+ $weight_b = $lp['weight_b'];
+
+ // Full or partial?
+ if ($withdraw_amount <= 0 || $withdraw_amount >= $lp_amount) {
+ $withdraw_amount = $lp_amount;
+ $w_a = $weight_a;
+ $w_b = $weight_b;
+ $is_full = true;
+ } else {
+ $fraction = $withdraw_amount / $lp_amount;
+ $w_a = intval(floor($weight_a * $fraction));
+ $w_b = intval(floor($weight_b * $fraction));
+ if ($w_a <= 0 || $w_b <= 0) return ['error' => 'Withdrawal amount too small'];
+ $is_full = false;
+ }
+
+ $new_ra = $market['reserve_a'] - $w_a;
+ $new_rb = $market['reserve_b'] - $w_b;
+ if ($new_ra <= 0 || $new_rb <= 0) return ['error' => 'Would deplete reserves'];
+ $new_liq_sum = $market['liquidity_sum'] - $withdraw_amount;
+ if ($new_liq_sum < 100000) return ['error' => 'Below minimum liquidity'];
+
+ // Time ratio
+ $market_duration = $market['betting_expiration'] - $market['time'];
+ $time_served = $current_time - $lp['time_deposited'];
+ $time_ratio = ($market_duration > 0) ? min(1, $time_served / $market_duration) : 0;
+
+ // Fee share: conservative estimate from smaller side (guaranteed loser lower bound)
+ $fee_from_a = intval($market['a_bets_sum'] * $market['liquidity_fee'] / 1000);
+ $fee_from_b = intval($market['b_bets_sum'] * $market['liquidity_fee'] / 1000);
+ $estimated_pool = min($fee_from_a, $fee_from_b);
+ $already_paid = $market['liquidity_fee_earned']; // tracks cumulative fees paid to early-withdrawn LPs
+ $fee_pool = max(0, $estimated_pool - $already_paid);
+ $fee_share = 0;
+ if ($fee_pool > 0) {
+ $total_tw = 0;
+ foreach ($lps as $l) {
+ if ($l['status'] == 0) $total_tw += $l['amount'] * max(1, $l['sec_to_expiration']);
+ }
+ if ($total_tw > 0) {
+ $lp_tw = $withdraw_amount * max(1, $lp['sec_to_expiration']);
+ $raw = intval($fee_pool * $lp_tw / $total_tw);
+ $fee_share = intval($raw * $time_ratio);
+ }
+ }
+
+ $returned = $withdraw_amount + $fee_share;
+
+ // Apply
+ $market['reserve_a'] = $new_ra;
+ $market['reserve_b'] = $new_rb;
+ $market['k'] = $new_ra * $new_rb;
+ $market['liquidity_sum'] = $new_liq_sum;
+ $market['liquidity_fee_earned'] += $fee_share; // tracks cumulative fees paid to early-withdrawn LPs
+
+ $user['balance'] += $returned;
+ $user['liquidity_balance'] -= $withdraw_amount;
+
+ if ($is_full) {
+ $lp['status'] = 2;
+ $lp['earned_fee'] = $fee_share;
+ } else {
+ $lp['amount'] -= $withdraw_amount;
+ $lp['weight_a'] -= $w_a;
+ $lp['weight_b'] -= $w_b;
+ $lp['earned_fee'] += $fee_share;
+ }
+ // Update lps array
+ foreach ($lps as &$ref) {
+ if ($ref['id'] === $lp['id']) { $ref = $lp; break; }
+ }
+ unset($ref);
+
+ return [
+ 'status' => true, 'returned' => $returned, 'fee_share' => $fee_share,
+ 'is_full' => $is_full, 'time_ratio' => round($time_ratio, 6),
+ 'remaining_amount' => $is_full ? 0 : $lp['amount'],
+ 'reserves' => ['a' => $market['reserve_a'], 'b' => $market['reserve_b'], 'k' => $market['k']],
+ ];
+}
+
+function resolve_market(&$market, $outcome, $bets, &$lps, &$payouts) {
+ $market['status'] = 3;
+ $market['resolved_outcome'] = $outcome;
+ $market['payout_status'] = 1;
+
+ $total_penalty_pool = 0;
+ $winner_payouts = [];
+
+ foreach ($bets as &$b) {
+ if ($b['status'] != 0) continue;
+ if ($b['side'] == $outcome) {
+ $raw = $b['weight'];
+ $profit = max(0, $raw - $b['amount']);
+ $penalty_ded = intval($profit * $b['time_penalty'] / 1000000);
+ $net = $raw - $penalty_ded;
+ $total_penalty_pool += $penalty_ded;
+ $b['resolved_amount'] = $net;
+ $b['status'] = 3;
+ if ($net > 0) {
+ $payouts[] = ['user' => $b['user'], 'type' => 0, 'amount' => $net, 'label' => 'winner_bet'];
+ }
+ $winner_payouts[] = ['bet_id' => $b['id'], 'user' => $b['user'],
+ 'raw' => $raw, 'penalty' => $penalty_ded, 'net' => $net];
+ } else {
+ $b['resolved_amount'] = 0;
+ $b['status'] = 3;
+ }
+ }
+ unset($b);
+
+ // Oracle fee payout: computed from losing side's bet volume
+ $losing_sum = ($outcome == 0) ? $market['b_bets_sum'] : $market['a_bets_sum'];
+ $oracle_fee_total = intval($losing_sum * $market['oracle_fee'] / 1000);
+ if ($oracle_fee_total > 0) {
+ $payouts[] = ['user' => $market['oracle'], 'type' => 2, 'amount' => $oracle_fee_total, 'label' => 'oracle_fee'];
+ }
+
+ // LP payouts: principal + time-weighted share of (loser fees + penalty_pool)
+ // weight = amount × sec_to_expiration (early LP earns more per VIZ)
+ $lp_fee_from_losers = intval($losing_sum * $market['liquidity_fee'] / 1000);
+ $already_paid_lp = $market['liquidity_fee_earned']; // fees paid to early-withdrawn LPs
+ $fee_pool = max(0, $lp_fee_from_losers - $already_paid_lp) + $total_penalty_pool;
+ $total_liq = $market['liquidity_sum'];
+ $lp_payouts = [];
+ if ($total_liq > 0) {
+ // First pass: calculate total time-weighted liquidity
+ $total_tw = 0;
+ foreach ($lps as $l) {
+ if ($l['market'] != $market['id'] || $l['status'] != 0) continue;
+ $total_tw += $l['amount'] * max(1, $l['sec_to_expiration']);
+ }
+ // Second pass: distribute
+ foreach ($lps as &$l) {
+ if ($l['market'] != $market['id'] || $l['status'] != 0) continue;
+ $tw = $l['amount'] * max(1, $l['sec_to_expiration']);
+ $share = ($fee_pool > 0 && $total_tw > 0) ? intval($fee_pool * $tw / $total_tw) : 0;
+ $lp_return = $l['amount'] + $share;
+ $l['earned_fee'] = $share;
+ $l['status'] = 3;
+ $payouts[] = ['user' => $l['user'], 'type' => 1, 'amount' => $lp_return, 'label' => 'lp_return'];
+ $lp_payouts[] = ['lp_id' => $l['id'], 'principal' => $l['amount'], 'fee_share' => $share, 'total' => $lp_return, 'time_weight' => $tw, 'sec_to_exp' => $l['sec_to_expiration']];
+ }
+ unset($l);
+ }
+
+ return [
+ 'outcome' => $outcome, 'total_penalty_pool' => $total_penalty_pool,
+ 'oracle_fee' => $oracle_fee_total, 'liq_fee_pool' => $fee_pool,
+ 'winner_payouts' => $winner_payouts, 'lp_payouts' => $lp_payouts,
+ ];
+}
+
+function auto_payout(&$payouts, &$users_map) {
+ $results = [];
+ foreach ($payouts as &$p) {
+ if (!isset($p['paid'])) $p['paid'] = false;
+ if ($p['paid']) continue;
+ $uid = $p['user'];
+ if (isset($users_map[$uid])) {
+ $users_map[$uid]['balance'] += $p['amount'];
+ $p['paid'] = true;
+ $results[] = ['user' => $uid, 'type' => $p['label'], 'amount' => $p['amount']];
+ }
+ }
+ unset($p);
+ return $results;
+}
+
+function oracle_penalty(&$market, &$oracle, $bets, &$lps, $settings, &$users_map) {
+ $penalty_pct = $settings['oracle_penalty_percent'];
+ $penalty_amount = intval($oracle['oracle_insurance'] * $penalty_pct / 100);
+
+ // Collect stakes
+ $stakes = [];
+ $total_stakes = 0;
+ foreach ($bets as $b) {
+ if ($b['status'] != 0) continue;
+ $uid = $b['user'];
+ if (!isset($stakes[$uid])) $stakes[$uid] = 0;
+ $stakes[$uid] += $b['amount'];
+ $total_stakes += $b['amount'];
+ }
+ foreach ($lps as $l) {
+ if ($l['market'] != $market['id'] || $l['status'] != 0) continue;
+ $uid = $l['user'];
+ if (!isset($stakes[$uid])) $stakes[$uid] = 0;
+ $stakes[$uid] += $l['amount'];
+ $total_stakes += $l['amount'];
+ }
+
+ // Refund bets
+ $refunds = [];
+ foreach ($bets as &$b) {
+ if ($b['status'] != 0) continue;
+ $uid = $b['user'];
+ $users_map[$uid]['balance'] += $b['amount'];
+ $users_map[$uid]['bets_balance'] -= $b['amount'];
+ $b['status'] = 2;
+ $refunds[] = ['user' => $uid, 'type' => 'bet_refund', 'amount' => $b['amount']];
+ }
+ unset($b);
+
+ // Refund liquidity
+ foreach ($lps as &$l) {
+ if ($l['market'] != $market['id'] || $l['status'] != 0) continue;
+ $uid = $l['user'];
+ $users_map[$uid]['balance'] += $l['amount'];
+ $users_map[$uid]['liquidity_balance'] -= $l['amount'];
+ $l['status'] = 2;
+ $refunds[] = ['user' => $uid, 'type' => 'liq_refund', 'amount' => $l['amount']];
+ }
+ unset($l);
+
+ // Deduct oracle insurance
+ $oracle['oracle_insurance'] -= $penalty_amount;
+ if ($oracle['oracle_insurance'] < 0) $oracle['oracle_insurance'] = 0;
+
+ // Distribute penalty
+ $distributions = [];
+ if ($penalty_amount > 0 && $total_stakes > 0) {
+ foreach ($stakes as $uid => $stake) {
+ $bonus = intval($penalty_amount * $stake / $total_stakes);
+ if ($bonus > 0) {
+ $users_map[$uid]['balance'] += $bonus;
+ $distributions[] = ['user' => $uid, 'stake' => $stake, 'bonus' => $bonus];
+ }
+ }
+ }
+
+ $market['status'] = 3;
+ $market['payout_status'] = 2;
+
+ return [
+ 'penalty_amount' => $penalty_amount, 'total_stakes' => $total_stakes,
+ 'refunds' => $refunds, 'distributions' => $distributions,
+ 'oracle_insurance_after' => $oracle['oracle_insurance'],
+ ];
+}
+
+function create_dispute(&$market, &$plaintiff, $settings) {
+ $fee = $settings['dispute_fee'];
+ if ($plaintiff['balance'] < $fee) return ['error' => 'Insufficient balance'];
+ $plaintiff['balance'] -= $fee;
+ $market['payout_status'] = 3;
+ return ['status' => true, 'dispute_fee' => $fee, 'dispute_id' => 1];
+}
+
+function resolve_dispute_oracle_wrong(&$market, &$plaintiff, &$oracle, $correct_outcome,
+ $bets, &$lps, &$payouts, $dispute_fee,
+ $extra_penalty=0, $ban_oracle=0, $ban_oracle_until=0,
+ $ban_creator=0, $ban_creator_until=0, &$creator=null, &$committee_user=null, $settings=null) {
+ // reward_pool = min(dispute_fee * multiplier, oracle_insurance)
+ $multiplier = ($settings !== null && isset($settings['dispute_reward_multiplier'])) ? floatval($settings['dispute_reward_multiplier']) : 3.0;
+ $reward_pool = min(intval(floor($dispute_fee * $multiplier)), $oracle['oracle_insurance']);
+ $disputer_reward = intval(floor($reward_pool / $multiplier)); // 1/3 at 3.0
+ $voter_reward = $reward_pool - $disputer_reward; // 2/3 at 3.0
+
+ // Plaintiff gets refund + disputer_reward
+ $plaintiff['balance'] += $dispute_fee + $disputer_reward;
+ // Oracle insurance reduced by reward_pool
+ $oracle['oracle_insurance'] -= $reward_pool;
+ if ($oracle['oracle_insurance'] < 0) $oracle['oracle_insurance'] = 0;
+
+ // Resolver/voter gets voter_reward
+ if ($committee_user !== null && $voter_reward > 0) {
+ $committee_user['balance'] += $voter_reward;
+ }
+
+ // Additional penalty: slash from oracle insurance, transfer to committee/DAO fund
+ $actual_extra_penalty = min($extra_penalty, $oracle['oracle_insurance']);
+ if ($actual_extra_penalty > 0) {
+ $oracle['oracle_insurance'] -= $actual_extra_penalty;
+ if ($oracle['oracle_insurance'] < 0) $oracle['oracle_insurance'] = 0;
+ if ($committee_user !== null) {
+ $committee_user['balance'] += $actual_extra_penalty;
+ }
+ }
+
+ // Apply oracle ban
+ if (1 == $ban_oracle) {
+ $oracle['oracle_banned'] = 1;
+ $oracle['oracle_ban_until'] = $ban_oracle_until;
+ }
+
+ // Apply creator ban
+ if (1 == $ban_creator && $creator !== null) {
+ $creator['creator_banned'] = 1;
+ $creator['creator_ban_until'] = $ban_creator_until;
+ }
+
+ // Delete old unpaid payouts
+ $payouts = array_filter($payouts, function($p) { return isset($p['paid']) && $p['paid']; });
+ $payouts = array_values($payouts);
+
+ // Recalculate with correct outcome
+ $new_payouts = [];
+ $result = resolve_market($market, $correct_outcome, $bets, $lps, $new_payouts);
+ $market['resolved_outcome'] = $correct_outcome;
+ $market['payout_status'] = 1;
+
+ // Merge new payouts
+ foreach ($new_payouts as $p) $payouts[] = $p;
+
+ return ['decision' => 'oracle_wrong', 'correct_outcome' => $correct_outcome,
+ 'plaintiff_refund' => $dispute_fee, 'disputer_reward' => $disputer_reward,
+ 'voter_reward' => $voter_reward, 'reward_pool' => $reward_pool,
+ 'oracle_insurance_loss' => $reward_pool,
+ 'extra_penalty' => $actual_extra_penalty,
+ 'ban_oracle' => $ban_oracle, 'ban_creator' => $ban_creator,
+ 'recalculated' => $result];
+}
+
+function resolve_dispute_oracle_right(&$market, &$committee, &$oracle, $dispute_fee, &$payouts, $settings=null) {
+ $rejected_voter_permille = ($settings !== null && isset($settings['dispute_rejected_voter_permille'])) ? intval($settings['dispute_rejected_voter_permille']) : 500;
+ $voter_share = intval(floor($dispute_fee * $rejected_voter_permille / 1000));
+ $oracle_share = $dispute_fee - $voter_share;
+
+ // Resolver gets voter_share
+ $committee['balance'] += $voter_share;
+ // Oracle gets compensation
+ $oracle['balance'] += $oracle_share;
+
+ $market['payout_status'] = 1;
+ $payouts[] = ['user' => $committee['id'], 'type' => 8, 'amount' => $voter_share,
+ 'label' => 'dispute_voter_reward', 'paid' => true];
+ $payouts[] = ['user' => $oracle['id'], 'type' => 9, 'amount' => $oracle_share,
+ 'label' => 'dispute_oracle_compensation', 'paid' => true];
+ return ['decision' => 'oracle_right', 'voter_share' => $voter_share, 'oracle_share' => $oracle_share];
+}
+
+function fmt($amount) { return round($amount / 1000, 3) . ' VIZ'; }
+
+// ============================================================
+// COMMIT-REVEAL HELPERS (pm_commit_bet, pm_reveal_bet, pm_commit_forfeit, batch_settle)
+// ============================================================
+function commit_bet(&$market, &$user, $escrow_amount, $side, $amount, $min_tokens, $salt, $settings, $current_time) {
+ // Requires allow_batch && commit_reveal_enabled
+ if (empty($market['allow_batch'])) return ['error' => 'Batch not allowed'];
+ if (empty($settings['commit_reveal_enabled'])) return ['error' => 'Commit-reveal disabled'];
+ if ($escrow_amount < ($settings['min_batch_bet'] ?? 1000)) return ['error' => 'Below min batch bet'];
+ if ($user['balance'] < $escrow_amount) return ['error' => 'Insufficient balance'];
+ if ($market['status'] != 1) return ['error' => 'Market not active'];
+ if ($current_time >= $market['betting_expiration']) return ['error' => 'Betting expired'];
+
+ $no_reveal_fee_permille = $settings['commit_no_reveal_penalty_permille'];
+ $commitment = hash('sha256', $market['id'] . '|' . $user['id'] . '|' . $side . '|' . $amount . '|' . $min_tokens . '|' . $salt);
+ $reveal_window = ($settings['reveal_window_blocks'] ?? 200) * 3; // 3s per block
+ $reveal_deadline = $current_time + $reveal_window;
+
+ $user['balance'] -= $escrow_amount;
+
+ static $commit_counter = 0;
+ $commit_counter++;
+
+ $commit = [
+ 'id' => $commit_counter, 'market' => $market['id'], 'account' => $user['id'],
+ 'commitment' => $commitment, 'escrow_amount' => $escrow_amount,
+ 'no_reveal_fee_permille' => $no_reveal_fee_permille,
+ 'side' => $side, 'amount' => $amount, 'min_tokens' => $min_tokens, 'salt' => $salt,
+ 'commit_time' => $current_time, 'reveal_deadline' => $reveal_deadline,
+ 'status' => 0, // 0=committed, 1=revealed, 2=forfeited
+ ];
+
+ return ['status' => true, 'commit' => $commit];
+}
+
+function reveal_bet(&$commit, &$market, &$user, $current_time, $epoch_blocks = 20) {
+ if ($commit['status'] != 0) return ['error' => 'Not committed'];
+ if ($current_time > $commit['reveal_deadline']) return ['error' => 'Reveal deadline passed'];
+
+ // Verify hash
+ $check = hash('sha256', $market['id'] . '|' . $user['id'] . '|' . $commit['side'] . '|' . $commit['amount'] . '|' . $commit['min_tokens'] . '|' . $commit['salt']);
+ if ($check !== $commit['commitment']) return ['error' => 'Hash mismatch'];
+
+ // Refund surplus
+ $surplus = $commit['escrow_amount'] - $commit['amount'];
+ $user['balance'] += $surplus;
+
+ // Determine epoch: first boundary at/after reveal
+ $epoch = intval(ceil($current_time / ($epoch_blocks * 3)));
+
+ $commit['status'] = 1; // revealed
+
+ // Create queued bet
+ static $reveal_bet_counter = 5000;
+ $reveal_bet_counter++;
+
+ $bet = [
+ 'id' => $reveal_bet_counter, 'market' => $market['id'], 'user' => $user['id'],
+ 'side' => $commit['side'], 'amount' => $commit['amount'], 'weight' => 0,
+ 'price' => 0, 'time_penalty' => 0, 'mode' => 2, 'epoch' => $epoch,
+ 'status' => 5, // queued
+ 'min_tokens' => $commit['min_tokens'], 'submit_time' => $commit['commit_time'],
+ 'resolved_amount' => 0,
+ ];
+
+ return ['status' => true, 'bet' => $bet, 'surplus_refund' => $surplus, 'epoch' => $epoch];
+}
+
+function commit_forfeit(&$commit, &$market, &$user) {
+ if ($commit['status'] != 0) return ['error' => 'Not committed (already revealed/forfeited)'];
+
+ $penalty = intval(floor($commit['escrow_amount'] * $commit['no_reveal_fee_permille'] / 1000));
+ $refund = $commit['escrow_amount'] - $penalty;
+
+ // Penalty goes to market forfeit_pool (boosts winners' pool at resolution)
+ if (!isset($market['forfeit_pool'])) $market['forfeit_pool'] = 0;
+ $market['forfeit_pool'] += $penalty;
+
+ // Refund remainder
+ $user['balance'] += $refund;
+ $commit['status'] = 2; // forfeited
+
+ return ['status' => true, 'penalty' => $penalty, 'refund' => $refund,
+ 'forfeit_pool' => $market['forfeit_pool']];
+}
+
+function batch_settle(&$market, &$queued_bets) {
+ // Aggregate each side into ONE CPMM op, canonical order (larger first)
+ $ra = $market['reserve_a'];
+ $rb = $market['reserve_b'];
+ $k = $market['k'];
+
+ $a_total = 0;
+ $b_total = 0;
+ foreach ($queued_bets as $b) {
+ if ($b['status'] != 5) continue; // only queued
+ if ($b['side'] == 0) $a_total += $b['amount'];
+ else $b_total += $b['amount'];
+ }
+
+ // Canonical order: larger aggregate first, tie → A
+ $order = ($a_total >= $b_total) ? [0, 1] : [1, 0];
+
+ $side_totals = [0 => $a_total, 1 => $b_total];
+ $side_tokens = [0 => 0, 1 => 0];
+
+ foreach ($order as $side) {
+ $total = $side_totals[$side];
+ if ($total <= 0) continue;
+
+ if ($side == 0) {
+ $new_rb = $rb + $total;
+ $new_ra = intval($k / $new_rb);
+ $tokens = $ra - $new_ra;
+ $ra = $new_ra;
+ $rb = $new_rb;
+ } else {
+ $new_ra = $ra + $total;
+ $new_rb = intval($k / $new_ra);
+ $tokens = $rb - $new_rb;
+ $ra = $new_ra;
+ $rb = $new_rb;
+ }
+ $side_tokens[$side] = $tokens;
+
+ // Distribute tokens pro-rata within the side, check min_tokens
+ foreach ($queued_bets as &$b) {
+ if ($b['status'] != 5 || $b['side'] != $side) continue;
+ $b_tokens = intval(floor($tokens * $b['amount'] / $total));
+ if (isset($b['min_tokens']) && $b['min_tokens'] > 0 && $b_tokens < $b['min_tokens']) {
+ $b['status'] = 2; // refunded (slippage too high)
+ } else {
+ $b['weight'] = $b_tokens;
+ $b['price'] = intval($b['amount'] * 1000000 / max(1, $b_tokens));
+ $b['status'] = 0; // active
+ }
+ }
+ unset($b);
+ }
+
+ // Update market reserves
+ $market['reserve_a'] = $ra;
+ $market['reserve_b'] = $rb;
+ // k unchanged (betting, not liquidity event)
+
+ // Dust: sum remaining unassigned tokens → would go to DAO fund
+ $dust = 0;
+ foreach ([0, 1] as $side) {
+ $assigned = 0;
+ foreach ($queued_bets as $b) {
+ if ($b['side'] == $side && $b['status'] == 0) $assigned += $b['weight'];
+ }
+ $dust += max(0, $side_tokens[$side] - $assigned);
+ }
+
+ // Update bets_sum
+ foreach ($queued_bets as &$b) {
+ if ($b['status'] == 0) {
+ $market['bets_sum'] += $b['amount'];
+ if ($b['side'] == 0) $market['a_bets_sum'] += $b['amount'];
+ else $market['b_bets_sum'] += $b['amount'];
+ }
+ }
+ unset($b);
+
+ return ['side_totals' => $side_totals, 'side_tokens' => $side_tokens,
+ 'dust' => $dust, 'reserves' => ['a' => $ra, 'b' => $rb, 'k' => $k]];
+}
+
+function place_bet_batch(&$market, &$user, $side, $amount, $current_time, $epoch_blocks = 20) {
+ // Batch mode=1: queue bet for next epoch
+ if (empty($market['allow_batch'])) return ['error' => 'Batch not allowed'];
+ if ($market['status'] != 1) return ['error' => 'Market not active'];
+ if ($current_time >= $market['betting_expiration']) return ['error' => 'Betting expired'];
+ if ($user['balance'] < $amount) return ['error' => 'Insufficient balance'];
+
+ $user['balance'] -= $amount;
+ $epoch = intval(ceil($current_time / ($epoch_blocks * 3)));
+
+ static $batch_bet_counter = 4000;
+ $batch_bet_counter++;
+
+ $bet = [
+ 'id' => $batch_bet_counter, 'market' => $market['id'], 'user' => $user['id'],
+ 'side' => $side, 'amount' => $amount, 'weight' => 0,
+ 'price' => 0, 'time_penalty' => 0, 'mode' => 1, 'epoch' => $epoch,
+ 'status' => 5, // queued
+ 'min_tokens' => 0, 'submit_time' => $current_time,
+ 'resolved_amount' => 0,
+ ];
+
+ return ['status' => true, 'bet' => $bet, 'epoch' => $epoch];
+}
+
+// ============================================================
+// ACCOUNT-MODE DISPUTE RESOLUTION (dispute_mode=1)
+// ============================================================
+function resolve_dispute_account_mode(&$market, &$resolver, &$plaintiff, &$oracle, $correct_outcome,
+ $bets, &$lps, &$payouts, $dispute_fee,
+ $penalty_amount = 0, $ban_oracle = 0, $ban_oracle_until = 0,
+ $ban_creator = 0, $ban_creator_until = 0,
+ &$creator = null, &$dao_fund = null, $settings = null) {
+ // Only the designated dispute_resolver account may call
+ if (empty($market['dispute_resolver']) || $market['dispute_resolver'] != $resolver['id']) {
+ return ['error' => 'Not the designated resolver'];
+ }
+ if ($market['dispute_mode'] != 1) {
+ return ['error' => 'Market is not in account dispute mode'];
+ }
+
+ $multiplier = ($settings !== null && isset($settings['dispute_reward_multiplier'])) ? floatval($settings['dispute_reward_multiplier']) : 3.0;
+ $reward_pool = min(intval(floor($dispute_fee * $multiplier)), $oracle['oracle_insurance']);
+ $disputer_reward = intval(floor($reward_pool / $multiplier));
+
+ // Plaintiff gets refund + disputer_reward
+ $plaintiff['balance'] += $dispute_fee + $disputer_reward;
+ $oracle['oracle_insurance'] -= $reward_pool;
+ if ($oracle['oracle_insurance'] < 0) $oracle['oracle_insurance'] = 0;
+
+ // Resolver gets a fee for service (from penalty, not from reward_pool)
+ $resolver_fee = 0;
+
+ // Additional penalty from oracle insurance
+ $actual_penalty = min($penalty_amount, $oracle['oracle_insurance']);
+ if ($actual_penalty > 0) {
+ $oracle['oracle_insurance'] -= $actual_penalty;
+ if ($oracle['oracle_insurance'] < 0) $oracle['oracle_insurance'] = 0;
+ // Split: resolver gets a portion, DAO gets the rest
+ $resolver_fee = intval($actual_penalty / 2);
+ $dao_portion = $actual_penalty - $resolver_fee;
+ $resolver['balance'] += $resolver_fee;
+ if ($dao_fund !== null) $dao_fund['balance'] += $dao_portion;
+ }
+
+ // Apply bans
+ if ($ban_oracle) {
+ $oracle['oracle_banned'] = 1;
+ $oracle['oracle_ban_until'] = $ban_oracle_until;
+ }
+ if ($ban_creator && $creator !== null) {
+ $creator['creator_banned'] = 1;
+ $creator['creator_ban_until'] = $ban_creator_until;
+ }
+
+ // Delete old unpaid payouts and recalculate
+ $payouts = array_values(array_filter($payouts, function($p) { return isset($p['paid']) && $p['paid']; }));
+ $new_payouts = [];
+ $result = resolve_market($market, $correct_outcome, $bets, $lps, $new_payouts);
+ $market['resolved_outcome'] = $correct_outcome;
+ $market['payout_status'] = 1;
+ foreach ($new_payouts as $p) $payouts[] = $p;
+
+ return ['decision' => 'account_mode', 'correct_outcome' => $correct_outcome,
+ 'plaintiff_refund' => $dispute_fee, 'disputer_reward' => $disputer_reward,
+ 'reward_pool' => $reward_pool, 'penalty' => $actual_penalty,
+ 'resolver_fee' => $resolver_fee, 'ban_oracle' => $ban_oracle, 'ban_creator' => $ban_creator,
+ 'recalculated' => $result];
+}
+
+// ============================================================
+// ORACLE ACCEPT WITH FIXED FEE TRANSFER
+// ============================================================
+function oracle_accept_with_fixed_fee(&$market, &$oracle, &$creator, $settings) {
+ if ($market['oracle'] != $oracle['id']) return ['error' => 'Not oracle'];
+ if ($market['status'] != 0) return ['error' => 'Not pending'];
+ if ($oracle['oracle_insurance'] < $settings['min_oracle_insurance'])
+ return ['error' => 'Insufficient insurance'];
+
+ $fixed_fee = $market['oracle_fixed_fee'] ?? 0;
+ // Use market fields to detect self-oracle (avoids PHP double-reference aliasing)
+ $is_self_oracle = ($market['user'] == $market['oracle']);
+ $transferred = 0;
+
+ if ($fixed_fee > 0 && !$is_self_oracle) {
+ // Transfer fixed fee from creator to oracle
+ $actual_fee = min($fixed_fee, $creator['balance']);
+ $creator['balance'] -= $actual_fee;
+ $oracle['balance'] += $actual_fee;
+ $transferred = $actual_fee;
+ }
+
+ $market['status'] = 1; // active
+
+ return ['status' => true, 'fixed_fee_transferred' => $transferred,
+ 'is_self_oracle' => $is_self_oracle,
+ 'creator_balance_after' => $creator['balance'],
+ 'oracle_balance_after' => $oracle['balance']];
+}
+
+function balances($users_map) {
+ $out = [];
+ foreach ($users_map as $u) {
+ $out[$u['name']] = [
+ 'balance' => fmt($u['balance']),
+ 'bets_balance' => fmt($u['bets_balance']),
+ 'liquidity_balance' => fmt($u['liquidity_balance']),
+ 'oracle_insurance' => fmt($u['oracle_insurance']),
+ ];
+ }
+ return $out;
+}
+
+// ============================================================
+// SCENARIO 1: HAPPY PATH — Oracle resolves correctly, no dispute
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 1: HAPPY PATH (oracle resolves X, no dispute)\n";
+echo str_repeat('=', 70) . "\n";
+
+$oracle_u = make_user(1, 'Oracle', 100000000); // 100000 VIZ
+$committee_u = make_user(2, 'Committee', 0);
+$committee_u['committee'] = 1;
+$maker_u = make_user(3, 'MarketMaker', 1000000); // 1000 VIZ
+$user_c = make_user(4, 'User_C', 500000); // 500 VIZ
+$user_d = make_user(5, 'User_D', 500000); // 500 VIZ
+
+$T0 = 1000000; // base time
+
+// Register oracle
+$r = register_oracle($oracle_u, 0, 5, $SETTINGS); // fee=5 permille=0.5%
+step('Oracle registers', ['result' => $r, 'oracle' => $oracle_u]);
+
+// Deposit insurance
+$r = oracle_deposit_insurance($oracle_u, 5000000);
+step('Oracle deposits 5000 VIZ insurance', ['result' => $r, 'oracle_insurance' => fmt($oracle_u['oracle_insurance'])]);
+
+// Register creator
+$r = register_creator($maker_u, $SETTINGS);
+step('MarketMaker registers as creator', ['result' => $r, 'maker' => $maker_u]);
+
+// Create market: 200 VIZ liquidity, oracle_fee=5‰, liq_fee=10‰, penalty_type=1(pct), penalty_value=10
+$r = create_market($maker_u, $oracle_u, 200000, 5, 10, $T0,
+ $T0 + 172800, $T0 + 259200, 1, 10, $committee_u['id'], $SETTINGS);
+$market1 = $r['market'];
+$lps = [$r['lp']];
+step('MarketMaker creates market_1 (200 VIZ liq, oracle_fee=0.5%, liq_fee=1%)', [
+ 'market' => $market1, 'lp' => $r['lp'], 'maker_balance' => fmt($maker_u['balance'])
+]);
+
+// Oracle accepts
+$r = oracle_accept($market1, $oracle_u, $SETTINGS);
+step('Oracle accepts market_1', ['result' => $r, 'market_status' => $market1['status']]);
+
+// User_C bets 100 VIZ on side 0 (A) at T0+3600 (early, no penalty)
+$bet_time = $T0 + 3600;
+$r = place_bet($market1, $user_c, 0, 100000, $bet_time);
+$bet_c = $r['bet'];
+step('User_C bets 100 VIZ on side A (early, no penalty)', $r);
+
+// User_D bets 50 VIZ on side 1 (B) at T0+7200
+$bet_time2 = $T0 + 7200;
+$r = place_bet($market1, $user_d, 1, 50000, $bet_time2);
+$bet_d = $r['bet'];
+step('User_D bets 50 VIZ on side B (early, no penalty)', $r);
+
+// Market state
+step('Market state after bets', [
+ 'reserves' => ['a' => $market1['reserve_a'], 'b' => $market1['reserve_b'], 'k' => $market1['k']],
+ 'fees_earned' => ['oracle' => fmt($market1['oracle_fee_earned']), 'liquidity' => fmt($market1['liquidity_fee_earned'])],
+ 'bets_sum' => fmt($market1['bets_sum']),
+]);
+
+// Oracle resolves to outcome=0 (A wins)
+$payouts1 = [];
+$bets1 = [$bet_c, $bet_d];
+$r = resolve_market($market1, 0, $bets1, $lps, $payouts1);
+step('Oracle resolves market_1 → outcome A', $r);
+
+// Auto-payout
+$users_map = [1 => &$oracle_u, 2 => &$committee_u, 3 => &$maker_u, 4 => &$user_c, 5 => &$user_d];
+$r = auto_payout($payouts1, $users_map);
+step('Auto-payout after grace period', ['paid' => $r, 'balances' => balances($users_map)]);
+
+
+// ============================================================
+// SCENARIO 2: ORACLE REJECTS MARKET
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 2: ORACLE REJECTS MARKET\n";
+echo str_repeat('=', 70) . "\n";
+
+$maker2 = make_user(10, 'Maker2', 500000);
+register_creator($maker2, $SETTINGS);
+$oracle2 = make_user(11, 'Oracle2', 100000000);
+register_oracle($oracle2, 0, 10, $SETTINGS);
+oracle_deposit_insurance($oracle2, 5000000);
+
+$r = create_market($maker2, $oracle2, 200000, 10, 10, $T0,
+ $T0+172800, $T0+259200, 1, 10, 0, $SETTINGS);
+$m2 = $r['market'];
+$lps2 = [$r['lp']];
+step('Maker2 creates market_2', ['maker_balance' => fmt($maker2['balance'])]);
+
+$r = oracle_reject($m2, $maker2, $lps2);
+step('Oracle2 rejects market_2', ['result' => $r, 'maker_balance' => fmt($maker2['balance'])]);
+
+
+// ============================================================
+// SCENARIO 3: SELF-ORACLE (auto-approve)
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 3: SELF-ORACLE (auto-approve)\n";
+echo str_repeat('=', 70) . "\n";
+
+$self_u = make_user(20, 'SelfOracle', 10000000);
+register_oracle($self_u, 0, 5, $SETTINGS);
+oracle_deposit_insurance($self_u, 5000000);
+register_creator($self_u, $SETTINGS);
+
+$r = create_market($self_u, $self_u, 200000, 5, 10, $T0,
+ $T0+172800, $T0+259200, 1, 10, 0, $SETTINGS);
+step('SelfOracle creates market (self-oracle auto-approve)', [
+ 'market_status' => $r['market']['status'],
+ 'balance' => fmt($self_u['balance']),
+]);
+
+
+// ============================================================
+// SCENARIO 4: TIME PENALTY — bets at different times
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 4: TIME PENALTY (bets at different times)\n";
+echo str_repeat('=', 70) . "\n";
+
+$oracle4 = make_user(30, 'Oracle4', 100000000);
+register_oracle($oracle4, 0, 5, $SETTINGS);
+oracle_deposit_insurance($oracle4, 5000000);
+$maker4 = make_user(31, 'Maker4', 1000000);
+register_creator($maker4, $SETTINGS);
+
+// Market: 48h = 172800s, penalty_type=1(pct), penalty_value=5 → window = 5% of 172800 = 8640s
+// Using quadratic curve (default)
+$r4 = create_market($maker4, $oracle4, 200000, 5, 10, $T0,
+ $T0+172800, $T0+259200, 1, 5, 0, $SETTINGS);
+$m4 = $r4['market'];
+$lps4 = [$r4['lp']];
+oracle_accept($m4, $oracle4, $SETTINGS);
+
+$uc = make_user(32, 'BetEarly', 500000);
+$ud = make_user(33, 'BetMid', 500000);
+$ue = make_user(34, 'BetLate', 500000);
+$uf = make_user(35, 'BetVeryLate', 500000);
+
+// Early bet: T0+3600 (outside penalty window, 169200s to exp > 8640)
+$r = place_bet($m4, $uc, 0, 100000, $T0 + 3600);
+step('BetEarly bets 100 VIZ at T+1h (no penalty)', ['penalty' => $r['time_penalty'], 'tokens' => $r['tokens']]);
+$bet_early = $r['bet'];
+
+// Mid bet: T0+86400 (86400s to exp > 8640)
+$r = place_bet($m4, $ud, 0, 100000, $T0 + 86400);
+step('BetMid bets 100 VIZ at T+24h (no penalty)', ['penalty' => $r['time_penalty'], 'tokens' => $r['tokens']]);
+$bet_mid = $r['bet'];
+
+// Late bet: T0+168000 (4800s to exp, inside 8640 window)
+// linear_ratio = 1 - 4800/8640 = 0.444, quadratic = 0.444^2 = 0.197
+$r = place_bet($m4, $ue, 0, 100000, $T0 + 168000);
+step('BetLate bets 100 VIZ at T+46.67h (4800s to exp, QUADRATIC PENALTY)', ['penalty' => $r['time_penalty'], 'tokens' => $r['tokens']]);
+$bet_late = $r['bet'];
+
+// Very late bet: T0+172300 (500s to exp)
+// linear_ratio = 1 - 500/8640 = 0.942, quadratic = 0.942^2 = 0.888
+$r = place_bet($m4, $uf, 0, 50000, $T0 + 172300);
+step('BetVeryLate bets 50 VIZ at T+47.86h (500s to exp, HIGH QUADRATIC PENALTY)', ['penalty' => $r['time_penalty'], 'tokens' => $r['tokens']]);
+$bet_very_late = $r['bet'];
+
+// Resolve to A (all bets win but with different penalties)
+$payouts4 = [];
+$bets4 = [$bet_early, $bet_mid, $bet_late, $bet_very_late];
+$r = resolve_market($m4, 0, $bets4, $lps4, $payouts4);
+step('Resolve market to A → all win, different penalty deductions', $r);
+
+$users_map4 = [30 => &$oracle4, 31 => &$maker4, 32 => &$uc, 33 => &$ud, 34 => &$ue, 35 => &$uf];
+$r = auto_payout($payouts4, $users_map4);
+step('Auto-payout with penalties', ['paid' => $r, 'balances' => balances($users_map4)]);
+
+
+// ============================================================
+// SCENARIO 5: ORACLE MISSES RESOLUTION → penalty + refund
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 5: ORACLE MISSES RESOLUTION\n";
+echo str_repeat('=', 70) . "\n";
+
+$oracle5 = make_user(40, 'Oracle5', 100000000);
+register_oracle($oracle5, 0, 5, $SETTINGS);
+oracle_deposit_insurance($oracle5, 5000000);
+$maker5 = make_user(41, 'Maker5', 1000000);
+register_creator($maker5, $SETTINGS);
+
+$r5 = create_market($maker5, $oracle5, 200000, 5, 10, $T0,
+ $T0+172800, $T0+259200, 1, 10, 0, $SETTINGS);
+$m5 = $r5['market'];
+$lps5 = [$r5['lp']];
+oracle_accept($m5, $oracle5, $SETTINGS);
+
+$bettor5a = make_user(42, 'Bettor5A', 500000);
+$bettor5b = make_user(43, 'Bettor5B', 500000);
+$rb1 = place_bet($m5, $bettor5a, 0, 100000, $T0+3600);
+$rb2 = place_bet($m5, $bettor5b, 1, 80000, $T0+7200);
+$bets5 = [$rb1['bet'], $rb2['bet']];
+
+step('Market 5 state before penalty', [
+ 'bettor5a_balance' => fmt($bettor5a['balance']),
+ 'bettor5b_balance' => fmt($bettor5b['balance']),
+ 'maker5_balance' => fmt($maker5['balance']),
+ 'oracle5_insurance' => fmt($oracle5['oracle_insurance']),
+]);
+
+// Oracle misses deadline → cron penalty
+$users_map5 = [40 => &$oracle5, 41 => &$maker5, 42 => &$bettor5a, 43 => &$bettor5b];
+$r = oracle_penalty($m5, $oracle5, $bets5, $lps5, $SETTINGS, $users_map5);
+step('Oracle misses deadline → cron penalty', $r);
+step('Balances after penalty', balances($users_map5));
+
+
+// ============================================================
+// SCENARIO 6: DISPUTE — Committee sides with PLAINTIFF (oracle wrong)
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 6: DISPUTE — ORACLE WRONG\n";
+echo str_repeat('=', 70) . "\n";
+
+$oracle6 = make_user(50, 'Oracle6', 100000000);
+register_oracle($oracle6, 0, 5, $SETTINGS);
+oracle_deposit_insurance($oracle6, 5000000);
+$maker6 = make_user(51, 'Maker6', 1000000);
+register_creator($maker6, $SETTINGS);
+$committee6 = make_user(52, 'Committee6', 0);
+$committee6['committee'] = 1;
+
+$r6 = create_market($maker6, $oracle6, 200000, 5, 10, $T0,
+ $T0+172800, $T0+259200, 1, 10, $committee6['id'], $SETTINGS);
+$m6 = $r6['market'];
+$lps6 = [$r6['lp']];
+oracle_accept($m6, $oracle6, $SETTINGS);
+
+$bettor_x = make_user(53, 'BettorX', 2000000);
+$bettor_y = make_user(54, 'BettorY', 2000000);
+$bx = place_bet($m6, $bettor_x, 0, 100000, $T0+3600);
+$by = place_bet($m6, $bettor_y, 1, 100000, $T0+7200);
+$bets6 = [$bx['bet'], $by['bet']];
+
+step('Bets placed', ['bettor_x' => fmt($bettor_x['balance']), 'bettor_y' => fmt($bettor_y['balance'])]);
+
+// Oracle resolves to A (wrong — should be B)
+$payouts6 = [];
+$r = resolve_market($m6, 0, $bets6, $lps6, $payouts6);
+step('Oracle resolves to A (WRONG)', $r);
+
+// Bettor Y disputes
+$r = create_dispute($m6, $bettor_y, $SETTINGS);
+step('BettorY files dispute', ['result' => $r, 'bettor_y_balance' => fmt($bettor_y['balance'])]);
+
+// Committee decides oracle was wrong, correct outcome = B(1)
+$null_creator = null;
+$r = resolve_dispute_oracle_wrong($m6, $bettor_y, $oracle6, 1, $bets6, $lps6, $payouts6, $SETTINGS['dispute_fee'], 0, 0, 0, 0, 0, $null_creator, $committee6, $SETTINGS);
+step('Committee resolves: ORACLE WRONG, correct=B', $r);
+
+$users_map6 = [50 => &$oracle6, 51 => &$maker6, 52 => &$committee6, 53 => &$bettor_x, 54 => &$bettor_y];
+$r = auto_payout($payouts6, $users_map6);
+step('Auto-payout after dispute (oracle wrong)', ['paid' => $r, 'balances' => balances($users_map6)]);
+
+
+// ============================================================
+// SCENARIO 7: DISPUTE — Committee sides with ORACLE (oracle right)
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 7: DISPUTE — ORACLE RIGHT\n";
+echo str_repeat('=', 70) . "\n";
+
+$oracle7 = make_user(60, 'Oracle7', 100000000);
+register_oracle($oracle7, 0, 5, $SETTINGS);
+oracle_deposit_insurance($oracle7, 5000000);
+$maker7 = make_user(61, 'Maker7', 1000000);
+register_creator($maker7, $SETTINGS);
+$committee7 = make_user(62, 'Committee7', 0);
+$committee7['committee'] = 1;
+
+$r7 = create_market($maker7, $oracle7, 200000, 5, 10, $T0,
+ $T0+172800, $T0+259200, 1, 10, $committee7['id'], $SETTINGS);
+$m7 = $r7['market'];
+$lps7 = [$r7['lp']];
+oracle_accept($m7, $oracle7, $SETTINGS);
+
+$bx7 = make_user(63, 'BettorX7', 2000000);
+$by7 = make_user(64, 'BettorY7', 2000000);
+$b7a = place_bet($m7, $bx7, 0, 100000, $T0+3600);
+$b7b = place_bet($m7, $by7, 1, 100000, $T0+7200);
+$bets7 = [$b7a['bet'], $b7b['bet']];
+
+// Oracle resolves to A (correct)
+$payouts7 = [];
+$r = resolve_market($m7, 0, $bets7, $lps7, $payouts7);
+step('Oracle resolves to A (correct)', $r);
+
+// Bettor Y7 disputes anyway
+$r = create_dispute($m7, $by7, $SETTINGS);
+step('BettorY7 files dispute', ['result' => $r]);
+
+// Committee decides oracle was RIGHT
+$r = resolve_dispute_oracle_right($m7, $committee7, $oracle7, $SETTINGS['dispute_fee'], $payouts7, $SETTINGS);
+step('Committee resolves: ORACLE RIGHT', $r);
+
+$users_map7 = [60 => &$oracle7, 61 => &$maker7, 62 => &$committee7, 63 => &$bx7, 64 => &$by7];
+$r = auto_payout($payouts7, $users_map7);
+step('Auto-payout after dispute (oracle right)', ['paid' => $r, 'balances' => balances($users_map7)]);
+
+
+// ============================================================
+// SCENARIO 8: BET CANCELLATION + SLIPPAGE
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 8: BET CANCELLATION + SLIPPAGE\n";
+echo str_repeat('=', 70) . "\n";
+
+$oracle8 = make_user(70, 'Oracle8', 100000000);
+register_oracle($oracle8, 0, 5, $SETTINGS);
+oracle_deposit_insurance($oracle8, 5000000);
+$maker8 = make_user(71, 'Maker8', 1000000);
+register_creator($maker8, $SETTINGS);
+
+$r8 = create_market($maker8, $oracle8, 200000, 5, 10, $T0,
+ $T0+172800, $T0+259200, 1, 10, 0, $SETTINGS);
+$m8 = $r8['market'];
+$lps8 = [$r8['lp']];
+oracle_accept($m8, $oracle8, $SETTINGS);
+
+$uc8 = make_user(72, 'UserC8', 500000);
+$ud8 = make_user(73, 'UserD8', 500000);
+
+// C bets 100 VIZ on A
+$rc = place_bet($m8, $uc8, 0, 100000, $T0+3600);
+$betC8 = $rc['bet'];
+step('C bets 100 VIZ on A', ['tokens' => $rc['tokens'], 'reserves_after' => $rc['reserves']]);
+
+// D bets 50 VIZ on B (shifts reserves)
+$rd = place_bet($m8, $ud8, 1, 50000, $T0+7200);
+$betD8 = $rd['bet'];
+step('D bets 50 VIZ on B (shifts reserves)', ['tokens' => $rd['tokens'], 'reserves_after' => $rd['reserves']]);
+
+// C cancels bet (slippage because D's bet changed reserves)
+$r = cancel_bet($m8, $uc8, $betC8);
+step('C cancels bet → slippage effect', [
+ 'result' => $r,
+ 'userC_balance' => fmt($uc8['balance']),
+ 'reserves_after' => ['a' => $m8['reserve_a'], 'b' => $m8['reserve_b']],
+]);
+
+
+// ============================================================
+// SCENARIO 9: MULTIPLE LPs — TIME-WEIGHTED proportional reward
+// Early LP (at creation, 172800s left) vs Late LP (at T+86400, 86400s left)
+// Same 100 VIZ each but early LP gets 2× the fee share per VIZ
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 9: MULTIPLE LPs — TIME-WEIGHTED\n";
+echo str_repeat('=', 70) . "\n";
+
+$oracle9 = make_user(80, 'Oracle9', 100000000);
+register_oracle($oracle9, 0, 5, $SETTINGS);
+oracle_deposit_insurance($oracle9, 5000000);
+$maker9 = make_user(81, 'Maker9', 1000000);
+register_creator($maker9, $SETTINGS);
+$lp2_u = make_user(82, 'LP2_Late', 500000);
+$lp2_u['add_liquidity'] = 1;
+$lp3_u = make_user(84, 'LP3_Late2', 500000);
+$lp3_u['add_liquidity'] = 1;
+
+// Market: 48h = 172800s
+$r9 = create_market($maker9, $oracle9, 200000, 5, 20, $T0,
+ $T0+172800, $T0+259200, 1, 10, 0, $SETTINGS);
+$m9 = $r9['market'];
+$lps9 = [$r9['lp']];
+oracle_accept($m9, $oracle9, $SETTINGS);
+
+step('Maker9 initial LP: 200 VIZ at creation, sec_to_exp='.$lps9[0]['sec_to_expiration'], [
+ 'lp' => $lps9[0],
+]);
+
+// LP2 adds 100 VIZ at T+86400 (halfway, 86400s remaining)
+$r = add_liquidity($m9, $lp2_u, 100000, $lps9, $T0+86400);
+step('LP2_Late adds 100 VIZ at T+24h, sec_to_exp='.$lps9[count($lps9)-1]['sec_to_expiration'], [
+ 'result' => $r, 'total_liquidity' => fmt($m9['liquidity_sum']),
+]);
+
+// LP3 adds 100 VIZ at T+172799 (1 second before expiration!)
+$r = add_liquidity($m9, $lp3_u, 100000, $lps9, $T0+172799);
+step('LP3_Late2 adds 100 VIZ at T+172799s (1s before exp!), sec_to_exp='.$lps9[count($lps9)-1]['sec_to_expiration'], [
+ 'result' => $r, 'total_liquidity' => fmt($m9['liquidity_sum']),
+]);
+
+// Bettor bets 200 VIZ
+$bettor9 = make_user(83, 'Bettor9', 500000);
+$rb9 = place_bet($m9, $bettor9, 0, 200000, $T0+3600);
+step('Bettor9 bets 200 VIZ on A', [
+ 'tokens' => $rb9['tokens'],
+ 'fees' => $rb9['fees'],
+ 'liq_fee_earned' => fmt($m9['liquidity_fee_earned']),
+]);
+$bets9 = [$rb9['bet']];
+
+// Resolve to A
+$payouts9 = [];
+$r = resolve_market($m9, 0, $bets9, $lps9, $payouts9);
+step('Resolve to A — TIME-WEIGHTED LP rewards', $r);
+echo " NOTE: Maker9 (200 VIZ × 172800s) gets MUCH more fee share than LP3 (100 VIZ × 1s)\n";
+
+$users_map9 = [80 => &$oracle9, 81 => &$maker9, 82 => &$lp2_u, 83 => &$bettor9, 84 => &$lp3_u];
+$r = auto_payout($payouts9, $users_map9);
+step('Auto-payout multiple LPs', ['paid' => $r, 'balances' => balances($users_map9)]);
+
+
+// ============================================================
+// SCENARIO 10: PENALTY CURVE COMPARISON (linear vs quadratic)
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 10: PENALTY CURVE COMPARISON (linear vs quadratic)\n";
+echo str_repeat('=', 70) . "\n";
+
+$oracle10 = make_user(90, 'Oracle10', 100000000);
+register_oracle($oracle10, 0, 5, $SETTINGS);
+oracle_deposit_insurance($oracle10, 5000000);
+$maker10 = make_user(91, 'Maker10', 2000000);
+register_creator($maker10, $SETTINGS);
+
+// Linear market (penalty_curve_type=0)
+$r10_lin = create_market($maker10, $oracle10, 200000, 5, 10, $T0,
+ $T0+172800, $T0+259200, 1, 5, 0, $SETTINGS, 0);
+$m10_lin = $r10_lin['market'];
+$lps10_lin = [$r10_lin['lp']];
+oracle_accept($m10_lin, $oracle10, $SETTINGS);
+
+// Quadratic market (penalty_curve_type=1)
+$r10_quad = create_market($maker10, $oracle10, 200000, 5, 10, $T0,
+ $T0+172800, $T0+259200, 1, 5, 0, $SETTINGS, 1);
+$m10_quad = $r10_quad['market'];
+$lps10_quad = [$r10_quad['lp']];
+oracle_accept($m10_quad, $oracle10, $SETTINGS);
+
+$u10a = make_user(92, 'BetterLinear', 500000);
+$u10b = make_user(93, 'BetterQuad', 500000);
+
+// Same bet timing: 4800s before exp (inside 8640s window)
+// linear_ratio = 1 - 4800/8640 = 0.4444
+// quadratic_ratio = 0.4444^2 = 0.1975
+$r_lin = place_bet($m10_lin, $u10a, 0, 100000, $T0 + 168000);
+$r_quad = place_bet($m10_quad, $u10b, 0, 100000, $T0 + 168000);
+step('Penalty curve comparison (4800s to exp)', [
+ 'linear_penalty' => $r_lin['time_penalty'],
+ 'quadratic_penalty' => $r_quad['time_penalty'],
+ 'linear_curve' => $m10_lin['penalty_curve_type'],
+ 'quadratic_curve' => $m10_quad['penalty_curve_type'],
+ 'note' => 'Quadratic should be ~44% of linear penalty',
+]);
+
+
+// ============================================================
+// SCENARIO 11: LOW-VOLUME LP SOLVENCY (1 bet, one-sided)
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 11: LOW-VOLUME LP SOLVENCY (1 small bet, one side)\n";
+echo str_repeat('=', 70) . "\n";
+
+$oracle11 = make_user(100, 'Oracle11', 100000000);
+register_oracle($oracle11, 0, 5, $SETTINGS);
+oracle_deposit_insurance($oracle11, 5000000);
+$maker11 = make_user(101, 'Maker11', 1000000);
+register_creator($maker11, $SETTINGS);
+
+$r11 = create_market($maker11, $oracle11, 100000, 5, 10, $T0,
+ $T0+172800, $T0+259200, 1, 5, 0, $SETTINGS, 0); // linear, no curve distortion
+$m11 = $r11['market'];
+$lps11 = [$r11['lp']];
+oracle_accept($m11, $oracle11, $SETTINGS);
+
+$bettor11 = make_user(102, 'SmallBettor', 500000);
+// 1 VIZ bet on side A, no bets on side B
+$rb11 = place_bet($m11, $bettor11, 0, 1000, $T0+3600); // 1 VIZ = 1000 units
+$bets11 = [$rb11['bet']];
+step('SmallBettor bets 1 VIZ on side A', ['tokens' => $rb11['tokens'], 'price' => $rb11['price']]);
+
+// Resolve to A (winner)
+$payouts11 = [];
+$r = resolve_market($m11, 0, $bets11, $lps11, $payouts11);
+step('Resolve to A → small bettor wins', $r);
+
+// Solvency check
+$money_in = $m11['liquidity_sum'] + $m11['bets_sum'];
+$money_out = 0;
+foreach ($payouts11 as $p) $money_out += $p['amount'];
+$lp_principal = 0;
+foreach ($r['lp_payouts'] as $lpp) $lp_principal += $lpp['principal'];
+$winner_payout = 0;
+foreach ($r['winner_payouts'] as $wp) $winner_payout += $wp['net'];
+
+step('LP SOLVENCY CHECK', [
+ 'money_in' => fmt($money_in),
+ 'money_out' => fmt($money_out),
+ 'surplus' => fmt($money_in - $money_out),
+ 'lp_principal_returned' => fmt($lp_principal),
+ 'winner_payout' => fmt($winner_payout),
+ 'SOLVENCY' => ($money_in >= $money_out) ? 'PASS — LP principal covered' : 'FAIL — INSUFFICIENT FUNDS',
+]);
+
+
+// ============================================================
+// SCENARIO 12: ORACLE RE-REGISTRATION (profile update)
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 12: ORACLE RE-REGISTRATION (profile update without fee)\n";
+echo str_repeat('=', 70) . "\n";
+
+$oracle12 = make_user(110, 'Oracle12', 100000000);
+$balance_before = $oracle12['balance'];
+$r = register_oracle($oracle12, 0, 5, $SETTINGS, 5000);
+step('Oracle12 first registration', ['result' => $r, 'balance_spent' => fmt($balance_before - $oracle12['balance'])]);
+
+$balance_before2 = $oracle12['balance'];
+$r = register_oracle($oracle12, 0, 10, $SETTINGS, 10000); // update fee to 10 VIZ
+step('Oracle12 re-registration (update profile)', [
+ 'result' => $r,
+ 'balance_spent' => fmt($balance_before2 - $oracle12['balance']),
+ 'new_oracle_fee' => $oracle12['oracle_fee'],
+ 'new_fixed_fee' => fmt($oracle12['oracle_fixed_fee']),
+ 'note' => 'Balance should not change on re-registration',
+]);
+
+
+// ============================================================
+// SCENARIO 13: DISPUTE WITH EXTRA PENALTY + ORACLE BAN
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 13: DISPUTE WITH EXTRA PENALTY + ORACLE BAN\n";
+echo str_repeat('=', 70) . "\n";
+
+$oracle13 = make_user(120, 'Oracle13', 100000000);
+$committee13 = make_user(121, 'Committee13', 0);
+$committee13['committee'] = 1;
+$maker13 = make_user(122, 'Maker13', 1000000);
+$user13a = make_user(123, 'User13A', 500000);
+
+register_oracle($oracle13, 0, 5, $SETTINGS);
+oracle_deposit_insurance($oracle13, 5000000);
+register_creator($maker13, $SETTINGS);
+
+$r13 = create_market($maker13, $oracle13, 200000, 5, 10, $T0,
+ $T0 + 172800, $T0 + 259200, 1, 10, $committee13['id'], $SETTINGS, -1, $committee13);
+$m13 = $r13['market'];
+$lps13 = [$r13['lp']];
+oracle_accept($m13, $oracle13, $SETTINGS);
+
+$r = place_bet($m13, $user13a, 0, 100000, $T0 + 3600);
+$bet13a = $r['bet'];
+step('User13A bets 100 VIZ on A', $r);
+
+// Resolve incorrectly (oracle says B wins)
+$payouts13 = [];
+$bets13 = [$bet13a];
+resolve_market($m13, 1, $bets13, $lps13, $payouts13);
+step('Oracle13 resolves to B (WRONG, should be A)', ['market_outcome' => $m13['resolved_outcome']]);
+
+// Dispute with extra penalty (2000 VIZ) + oracle ban (permanent)
+$r = create_dispute($m13, $user13a, $SETTINGS);
+step('User13A files dispute', $r);
+
+$oracle13_insurance_before = $oracle13['oracle_insurance'];
+$r = resolve_dispute_oracle_wrong($m13, $user13a, $oracle13, 0,
+ $bets13, $lps13, $payouts13, $SETTINGS['dispute_fee'],
+ 2000000, 1, 0, 0, 0, $maker13, $committee13, $SETTINGS); // extra_penalty=2000 VIZ, ban oracle permanently
+step('Committee resolves: oracle WRONG + 2000 VIZ extra penalty + permanent oracle ban', [
+ 'result' => $r,
+ 'oracle_insurance_before' => fmt($oracle13_insurance_before),
+ 'oracle_insurance_after' => fmt($oracle13['oracle_insurance']),
+ 'total_slashed' => fmt($oracle13_insurance_before - $oracle13['oracle_insurance']),
+ 'oracle_banned' => $oracle13['oracle_banned'],
+ 'oracle_ban_until' => $oracle13['oracle_ban_until'],
+ 'committee_balance' => fmt($committee13['balance']),
+]);
+
+// Verify ban enforcement: banned oracle cannot accept markets
+$test_market_r = create_market($maker13, $oracle13, 200000, 5, 10, $T0,
+ $T0 + 172800, $T0 + 259200, 1, 10, 0, $SETTINGS);
+$test_m = $test_market_r['market'];
+// Simulate ban check (oracle_accept would fail in real code)
+$ban_check = (1 == $oracle13['oracle_banned'] && (0 == $oracle13['oracle_ban_until'] || $oracle13['oracle_ban_until'] > time()));
+step('Ban enforcement: oracle tries to accept market', [
+ 'oracle_banned' => $oracle13['oracle_banned'],
+ 'would_be_rejected' => $ban_check ? 'YES - Oracle is banned' : 'NO - Oracle is not banned',
+]);
+
+
+// ============================================================
+// SCENARIO 14: DISPUTE WITH CREATOR BAN (TIME-LIMITED)
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 14: DISPUTE WITH TIME-LIMITED CREATOR BAN\n";
+echo str_repeat('=', 70) . "\n";
+
+$oracle14 = make_user(130, 'Oracle14', 100000000);
+$committee14 = make_user(131, 'Committee14', 0);
+$committee14['committee'] = 1;
+$maker14 = make_user(132, 'Maker14', 1000000);
+$user14a = make_user(133, 'User14A', 500000);
+
+register_oracle($oracle14, 0, 5, $SETTINGS);
+oracle_deposit_insurance($oracle14, 5000000);
+register_creator($maker14, $SETTINGS);
+
+$r14 = create_market($maker14, $oracle14, 200000, 5, 10, $T0,
+ $T0 + 172800, $T0 + 259200, 1, 10, $committee14['id'], $SETTINGS, -1, $committee14);
+$m14 = $r14['market'];
+$lps14 = [$r14['lp']];
+oracle_accept($m14, $oracle14, $SETTINGS);
+
+$r = place_bet($m14, $user14a, 0, 100000, $T0 + 3600);
+$bet14a = $r['bet'];
+
+$payouts14 = [];
+$bets14 = [$bet14a];
+resolve_market($m14, 1, $bets14, $lps14, $payouts14);
+
+$r = create_dispute($m14, $user14a, $SETTINGS);
+
+// Ban creator for 30 days
+$ban_until = time() + (30 * 86400);
+$r = resolve_dispute_oracle_wrong($m14, $user14a, $oracle14, 0,
+ $bets14, $lps14, $payouts14, $SETTINGS['dispute_fee'],
+ 0, 0, 0, 1, $ban_until, $maker14, $committee14, $SETTINGS); // ban creator for 30 days
+step('Committee resolves: oracle wrong + creator banned 30 days', [
+ 'creator_banned' => $maker14['creator_banned'],
+ 'creator_ban_until' => $maker14['creator_ban_until'],
+ 'ban_active' => ($maker14['creator_banned'] == 1 && $maker14['creator_ban_until'] > time()) ? 'YES' : 'NO',
+]);
+
+
+// ============================================================
+// SCENARIO 15: FRACTIONAL LP WITHDRAWAL
+// LP deposits 200 VIZ, withdraws 50% (100 VIZ), position stays active
+// Then places bet, resolves, remaining LP gets payout
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 15: FRACTIONAL LP WITHDRAWAL\n";
+echo str_repeat('=', 70) . "\n";
+
+$T0 = 1000000;
+$oracle15 = make_user(150, 'Oracle15', 100000000);
+register_oracle($oracle15, 0, 5, $SETTINGS);
+oracle_deposit_insurance($oracle15, 5000000);
+$maker15 = make_user(151, 'Maker15', 100000000);
+register_creator($maker15, $SETTINGS);
+$lp15_user = make_user(152, 'LP15', 100000000);
+$lp15_user['add_liquidity'] = 1;
+$bettor15 = make_user(153, 'Bettor15', 100000000);
+
+// Create market with 200 VIZ liquidity
+$lps15 = [];
+$m15 = create_market($maker15, $oracle15, 200000, 5, 10, $T0,
+ $T0+172800, $T0+259200, 1, 5, 0, $SETTINGS);
+$m15_market = $m15['market'];
+oracle_accept($m15_market, $oracle15, $SETTINGS);
+
+step('Market 15 created', [
+ 'liquidity_sum' => $m15_market['liquidity_sum'],
+ 'reserves' => ['a' => $m15_market['reserve_a'], 'b' => $m15_market['reserve_b']],
+]);
+
+// Get creator's LP (first LP)
+$creator_lp15 = $m15['lp'];
+$creator_lp15['time_deposited'] = $T0;
+$lps15[] = $creator_lp15;
+
+// Second LP adds 200 VIZ at T0+3600 (1 hour in)
+$lp_time = $T0 + 3600;
+$r_add = add_liquidity($m15_market, $lp15_user, 200000, $lps15, $lp_time);
+
+step('Second LP added 200 VIZ', [
+ 'lp' => $r_add['lp'],
+ 'reserves' => $r_add['reserves'],
+]);
+
+// Place a bet to generate fees
+$bets15 = [];
+$bet_time = $T0 + 7200;
+$r_bet = place_bet($m15_market, $bettor15, 0, 50000, $bet_time);
+$bets15[] = $r_bet['bet'];
+
+step('Bet placed (50 VIZ on A)', [
+ 'tokens' => $r_bet['bet']['weight'],
+ 'fee_earned' => $m15_market['liquidity_fee_earned'],
+]);
+
+// LP15 withdraws 50% (100 VIZ out of 200 VIZ) at T0+36000 (10 hours in)
+$withdraw_time = $T0 + 36000;
+$lp_ref = $lps15[1]; // second LP
+$r_partial = withdraw_liquidity($m15_market, $lp15_user, $lp_ref, 100000, $lps15, $withdraw_time, $SETTINGS);
+
+step('LP15 partial withdrawal (100 of 200 VIZ)', [
+ 'returned' => $r_partial['returned'],
+ 'fee_share' => $r_partial['fee_share'],
+ 'is_full' => $r_partial['is_full'] ? 'NO (partial)' : 'ERROR',
+ 'remaining_amount' => $r_partial['remaining_amount'],
+ 'remaining_lp_status' => $lps15[1]['status'],
+ 'remaining_weight_a' => $lps15[1]['weight_a'],
+ 'remaining_weight_b' => $lps15[1]['weight_b'],
+]);
+
+// Verify: position still active, amount reduced
+assert($r_partial['is_full'] === false);
+assert($lps15[1]['status'] === 0); // still active
+assert($lps15[1]['amount'] === 100000); // 100 VIZ remaining
+assert($r_partial['remaining_amount'] === 100000);
+
+// LP15 tries to withdraw remaining — should work as full withdrawal
+$withdraw_time2 = $T0 + 40000;
+$lp_ref2 = $lps15[1];
+$r_full = withdraw_liquidity($m15_market, $lp15_user, $lp_ref2, 0, $lps15, $withdraw_time2, $SETTINGS);
+
+step('LP15 full withdrawal of remainder', [
+ 'returned' => $r_full['returned'],
+ 'fee_share' => $r_full['fee_share'],
+ 'is_full' => $r_full['is_full'] ? 'YES (full)' : 'ERROR',
+ 'lp_status' => $lps15[1]['status'],
+]);
+
+assert($r_full['is_full'] === true);
+assert($lps15[1]['status'] === 2); // closed
+
+step('Scenario 15 complete: fractional withdrawal works', [
+ 'partial_returned' => $r_partial['returned'],
+ 'full_returned' => $r_full['returned'],
+ 'total_returned' => $r_partial['returned'] + $r_full['returned'],
+ 'original_deposit' => 200000,
+ 'principal_safe' => ($r_partial['returned'] + $r_full['returned']) >= 200000 ? 'YES' : 'NO',
+]);
+
+
+// ============================================================
+// SCENARIO 16: Risk Score — oracle insurance coverage check
+// ============================================================
+echo "\n\n### SCENARIO 16: Risk Score — oracle insurance undercollateralization ###\n";
+
+function calc_risk_score($oracle_insurance, $total_oracle_bets) {
+ if ($total_oracle_bets <= 0) return 999;
+ return $oracle_insurance / $total_oracle_bets;
+}
+
+function check_bet_risk($oracle, $total_oracle_bets, $bet_amount, $settings) {
+ $total_after = $total_oracle_bets + $bet_amount;
+ $risk_score = calc_risk_score($oracle['oracle_insurance'], $total_after);
+ $min_risk = $settings['min_risk_score_betting'];
+ return [
+ 'risk_score' => round($risk_score, 2),
+ 'blocked' => ($risk_score < $min_risk) ? 1 : 0,
+ 'min_threshold' => $min_risk,
+ ];
+}
+
+function check_listing_risk($oracle, $total_oracle_bets, $settings) {
+ $risk_score = calc_risk_score($oracle['oracle_insurance'], $total_oracle_bets);
+ $min_risk = $settings['min_risk_score_listing'];
+ return [
+ 'risk_score' => round($risk_score, 2),
+ 'hidden' => ($risk_score < $min_risk) ? 1 : 0,
+ 'min_threshold' => $min_risk,
+ ];
+}
+
+// Setup: oracle with 5000 VIZ insurance, 1 active market
+$rs_oracle = make_user(30, 'RiskOracle', 100000000);
+register_oracle($rs_oracle, 10000, 5, $SETTINGS);
+oracle_deposit_insurance($rs_oracle, 5000000); // 5000 VIZ
+$rs_creator = make_user(31, 'RiskCreator', 100000000);
+register_creator($rs_creator, $SETTINGS);
+$rs_committee = make_user(2, 'Committee', 0);
+
+$rs_market = create_market(
+ $rs_creator, $rs_oracle, 500000, 5, 10,
+ $base_time, $base_time + 86400, $base_time + 172800,
+ 1, 5, $rs_committee['id'], $SETTINGS, -1, $rs_committee
+);
+// Oracle accepts
+$rs_market['status'] = 1;
+// Track total bets across all oracle markets (global ratio)
+$total_oracle_bets = 0; // no bets yet
+
+// Step 1: No bets yet — risk score = 999 (safe)
+$rs1 = calc_risk_score($rs_oracle['oracle_insurance'], $total_oracle_bets);
+step('Risk score with no bets = safe', [
+ 'oracle_insurance' => $rs_oracle['oracle_insurance'] / 1000 . ' VIZ',
+ 'total_oracle_bets' => $total_oracle_bets,
+ 'risk_score' => $rs1,
+]);
+assert($rs1 == 999);
+
+// Step 2: Small bet — high risk score (well covered)
+// After 100 VIZ bet: total_bets=100000, risk=5000000/100000=50
+$check_small = check_bet_risk($rs_oracle, $total_oracle_bets, 100000, $SETTINGS);
+step('Risk check: 100 VIZ bet (well covered)', $check_small);
+assert($check_small['blocked'] == 0);
+
+// Place the bet to update bets_sum
+place_bet($rs_market, $rs_creator, 0, 100000, $base_time + 100);
+$total_oracle_bets = $rs_market['bets_sum']; // 100000
+step('Bet placed, total_oracle_bets', ['total_oracle_bets' => $total_oracle_bets]);
+
+// Step 3: Large bet that would bring risk_score below threshold
+// After 3900 VIZ more: total=100000+3900000=4000000, risk=5000000/4000000=1.25 < 1.5 → blocked
+$check_large = check_bet_risk($rs_oracle, $total_oracle_bets, 3900000, $SETTINGS);
+step('Risk check: 3900 VIZ bet (would undercollateralize)', $check_large);
+assert($check_large['blocked'] == 1);
+assert($check_large['risk_score'] < 1.5);
+
+// Step 4: Listing visibility — total bets 100 VIZ, risk=50 → visible
+$listing1 = check_listing_risk($rs_oracle, $total_oracle_bets, $SETTINGS);
+step('Listing check: 100 VIZ total bets (visible)', $listing1);
+assert($listing1['hidden'] == 0);
+
+// Step 5: Simulate high total bets across multiple markets
+// total_bets=2500000 (2500 VIZ across all markets), risk=5000000/2500000=2.0 < 2.5 → hidden
+$total_heavy = 2500000;
+$listing2 = check_listing_risk($rs_oracle, $total_heavy, $SETTINGS);
+step('Listing check: 2500 VIZ total oracle bets (hidden from listing)', $listing2);
+assert($listing2['hidden'] == 1);
+assert($listing2['risk_score'] < 2.5);
+
+// Step 6: Same scenario but with show_risky=1
+step('With show_risky=1, hidden markets would be included in response', [
+ 'risk_score' => $listing2['risk_score'],
+ 'hidden_flag' => $listing2['hidden'],
+ 'show_risky_override' => 'would include in API response despite hidden flag',
+]);
+
+// Step 7: Summary
+step('Scenario 16 complete: risk score system works (global ratio)', [
+ 'formula' => 'risk_score = oracle_insurance / total_bets_all_oracle_markets',
+ 'safe_market_visible' => 'YES (risk=50.0)',
+ 'undercollateralized_bet_blocked' => 'YES (risk=1.25 < 1.5)',
+ 'high_volume_hidden' => 'YES (risk=2.0 < 2.5)',
+ 'risk_confirm_override' => 'Server allows with risk_confirm=1',
+]);
+
+
+// ============================================================
+// SCENARIO 17: ORACLE VOLUNTARY NO-CONTEST
+// Oracle can't verify outcome, voluntarily cancels market with reduced penalty
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 17: ORACLE VOLUNTARY NO-CONTEST\n";
+echo str_repeat('=', 70) . "\n";
+
+function oracle_no_contest(&$market, &$oracle, &$bets, &$lps, &$payouts, $settings) {
+ $dispute_fee = $settings['dispute_fee'];
+ $no_contest_pct = $settings['oracle_no_contest_penalty_percent'];
+ $penalty = intval($dispute_fee * $no_contest_pct / 100);
+ if ($penalty > $oracle['oracle_insurance']) $penalty = $oracle['oracle_insurance'];
+
+ // Collect stakes for penalty distribution
+ $stakes = [];
+ $total_stakes = 0;
+
+ // Mark all active bets as resolved with full refund, create pending payouts
+ foreach ($bets as &$b) {
+ if ($b['status'] != 0) continue;
+ $uid = $b['user'];
+ $bet_amount = $b['amount'];
+ if (!isset($stakes[$uid])) $stakes[$uid] = 0;
+ $stakes[$uid] += $bet_amount;
+ $total_stakes += $bet_amount;
+ $b['status'] = 3;
+ $b['resolved_amount'] = $bet_amount;
+ $payouts[] = ['user' => $uid, 'type' => 0, 'amount' => $bet_amount, 'label' => 'bet_refund'];
+ }
+ unset($b);
+
+ // Mark all active LPs as resolved, create pending refund payouts (principal only)
+ foreach ($lps as &$l) {
+ if ($l['market'] != $market['id'] || $l['status'] != 0) continue;
+ $uid = $l['user'];
+ $lp_amount = $l['amount'];
+ if (!isset($stakes[$uid])) $stakes[$uid] = 0;
+ $stakes[$uid] += $lp_amount;
+ $total_stakes += $lp_amount;
+ $l['status'] = 3;
+ $l['earned_fee'] = 0;
+ $payouts[] = ['user' => $uid, 'type' => 1, 'amount' => $lp_amount, 'label' => 'lp_refund'];
+ }
+ unset($l);
+
+ // Deduct penalty from oracle insurance (immediate)
+ $oracle['oracle_insurance'] -= $penalty;
+ if ($oracle['oracle_insurance'] < 0) $oracle['oracle_insurance'] = 0;
+
+ // Distribute penalty proportionally (immediate — paid directly, not through payout queue)
+ $distributed = [];
+ if ($total_stakes > 0 && $penalty > 0) {
+ foreach ($stakes as $uid => $stake) {
+ $bonus = intval($penalty * $stake / $total_stakes);
+ $distributed[$uid] = $bonus;
+ }
+ }
+
+ // Market: resolved as no-contest, awaiting grace period
+ $market['status'] = 3;
+ $market['resolved_outcome'] = -1;
+ $market['payout_status'] = 1; // pending — grace period for disputes
+
+ return [
+ 'penalty' => $penalty,
+ 'penalty_pct' => $no_contest_pct,
+ 'total_stakes' => $total_stakes,
+ 'distributed' => $distributed,
+ ];
+}
+
+$nc_oracle = make_user(160, 'OracleNC', 100000000);
+register_oracle($nc_oracle, 0, 5, $SETTINGS);
+oracle_deposit_insurance($nc_oracle, 5000000); // 5000 VIZ
+$nc_maker = make_user(161, 'MakerNC', 100000000);
+register_creator($nc_maker, $SETTINGS);
+$nc_bettor1 = make_user(162, 'BettorNC1', 100000000);
+$nc_bettor2 = make_user(163, 'BettorNC2', 100000000);
+
+$nc_r = create_market($nc_maker, $nc_oracle, 200000, 5, 10, $base_time,
+ $base_time + 172800, $base_time + 259200, 1, 5, 0, $SETTINGS);
+$nc_market = $nc_r['market'];
+$nc_lps = [$nc_r['lp']];
+oracle_accept($nc_market, $nc_oracle, $SETTINGS);
+
+// Place bets
+$nc_b1 = place_bet($nc_market, $nc_bettor1, 0, 300000, $base_time + 3600);
+$nc_b2 = place_bet($nc_market, $nc_bettor2, 1, 200000, $base_time + 7200);
+$nc_bets = [$nc_b1['bet'], $nc_b2['bet']];
+$nc_payouts = [];
+
+$insurance_before = $nc_oracle['oracle_insurance'];
+step('Setup: oracle insurance=' . fmt($insurance_before) . ', bets=300+200 VIZ', [
+ 'insurance' => fmt($insurance_before),
+ 'bet1' => fmt($nc_b1['bet']['amount']),
+ 'bet2' => fmt($nc_b2['bet']['amount']),
+]);
+
+// Step 1: Oracle declares no-contest (creates pending refund payouts, grace period)
+$nc_result = oracle_no_contest($nc_market, $nc_oracle, $nc_bets, $nc_lps, $nc_payouts, $SETTINGS);
+
+step('Oracle declares no-contest (pending payouts, grace period)', $nc_result);
+
+// Step 2: Verify penalty is 50% of dispute_fee = 500 VIZ
+$expected_penalty = intval($SETTINGS['dispute_fee'] * $SETTINGS['oracle_no_contest_penalty_percent'] / 100);
+assert($nc_result['penalty'] == $expected_penalty);
+step('Penalty verification', [
+ 'expected' => fmt($expected_penalty) . ' (' . $SETTINGS['oracle_no_contest_penalty_percent'] . '% of dispute_fee ' . fmt($SETTINGS['dispute_fee']) . ')',
+ 'actual' => fmt($nc_result['penalty']),
+ 'oracle_insurance_after' => fmt($nc_oracle['oracle_insurance']),
+ 'insurance_reduced_by' => fmt($insurance_before - $nc_oracle['oracle_insurance']),
+]);
+assert($nc_oracle['oracle_insurance'] == $insurance_before - $expected_penalty);
+
+// Step 3: Verify market is in grace period (resolved_outcome=-1, payout_status=1)
+assert($nc_market['status'] == 3);
+assert($nc_market['resolved_outcome'] == -1);
+assert($nc_market['payout_status'] == 1); // pending — awaiting grace period
+step('Market state after no-contest', [
+ 'status' => $nc_market['status'],
+ 'resolved_outcome' => $nc_market['resolved_outcome'] . ' (no winner)',
+ 'payout_status' => $nc_market['payout_status'] . ' (pending — grace period for disputes)',
+]);
+
+// Step 4: Verify pending payouts were created (2 bet refunds + 1 LP refund)
+$bet_refund_payouts = array_filter($nc_payouts, function($p) { return $p['label'] == 'bet_refund'; });
+$lp_refund_payouts = array_filter($nc_payouts, function($p) { return $p['label'] == 'lp_refund'; });
+assert(count($bet_refund_payouts) == 2);
+assert(count($lp_refund_payouts) == 1);
+step('Pending payouts created', [
+ 'bet_refunds' => count($bet_refund_payouts),
+ 'lp_refunds' => count($lp_refund_payouts),
+ 'total_pending' => count($nc_payouts),
+]);
+
+// Step 5: Grace period passes with no dispute → auto_payout processes refunds
+$nc_users_map = [
+ $nc_bettor1['id'] => &$nc_bettor1,
+ $nc_bettor2['id'] => &$nc_bettor2,
+ $nc_maker['id'] => &$nc_maker,
+];
+$b1_before = $nc_bettor1['balance'];
+$b2_before = $nc_bettor2['balance'];
+$maker_before = $nc_maker['balance'];
+$payout_results = auto_payout($nc_payouts, $nc_users_map);
+$nc_market['payout_status'] = 2; // finalized after auto-payout
+
+step('Auto-payout after grace period (refunds processed)', [
+ 'payouts_processed' => count($payout_results),
+ 'bettor1_refund' => fmt($nc_bettor1['balance'] - $b1_before),
+ 'bettor2_refund' => fmt($nc_bettor2['balance'] - $b2_before),
+ 'maker_lp_refund' => fmt($nc_maker['balance'] - $maker_before),
+ 'market_payout_status' => $nc_market['payout_status'] . ' (finalized)',
+]);
+assert($nc_bettor1['balance'] == $b1_before + $nc_b1['bet']['amount']);
+assert($nc_bettor2['balance'] == $b2_before + $nc_b2['bet']['amount']);
+assert($nc_market['payout_status'] == 2);
+
+// Step 6: Compare with dispute penalty
+// No-contest: 500 VIZ (50% of 1000)
+// Dispute loss: 1000 VIZ (dispute_fee) + potential extra penalty + potential ban
+// Oracle-miss (cron): 5% of insurance = 250 VIZ
+$dispute_penalty = $SETTINGS['dispute_fee'];
+$miss_penalty = intval($insurance_before * $SETTINGS['oracle_penalty_percent'] / 100);
+step('Scenario 17 complete: oracle no-contest incentive comparison', [
+ 'no_contest_penalty' => fmt($nc_result['penalty']) . ' (voluntary, no ban risk)',
+ 'dispute_loss_penalty' => fmt($dispute_penalty) . ' + extra + possible ban',
+ 'oracle_miss_penalty' => fmt($miss_penalty) . ' (5% insurance, auto by cron)',
+ 'incentive' => 'No-contest is CHEAPER than dispute loss, encourages honesty',
+ 'flow' => 'no-contest → grace period → auto-payout (same as normal resolution)',
+]);
+
+
+// ============================================================
+// SCENARIO 18: DISPUTE AGAINST NO-CONTEST (3-outcome resolution)
+// Oracle abuses no-contest, resolver can choose: A wins / B wins / confirm no-contest
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 18: DISPUTE AGAINST ABUSIVE NO-CONTEST (3-OUTCOME RESOLUTION)\n";
+echo str_repeat('=', 70) . "\n";
+
+// Setup: oracle with insurance, market with bets, committee assigned
+$nc_oracle2 = make_user(170, 'OracleNC2', 100000000);
+register_oracle($nc_oracle2, 0, 5, $SETTINGS);
+oracle_deposit_insurance($nc_oracle2, 5000000); // 5000 VIZ
+$nc_maker2 = make_user(171, 'MakerNC2', 100000000);
+register_creator($nc_maker2, $SETTINGS);
+$nc_committee2 = make_user(172, 'CommitteeNC2', 0);
+$nc_committee2['committee'] = 1;
+$nc_bettor_a = make_user(173, 'BettorA_NC', 100000000);
+$nc_bettor_b = make_user(174, 'BettorB_NC', 100000000);
+
+$nc_r2 = create_market($nc_maker2, $nc_oracle2, 200000, 5, 10, $base_time,
+ $base_time + 172800, $base_time + 259200, 1, 5, $nc_committee2['id'], $SETTINGS, -1, $nc_committee2);
+$nc_market2 = $nc_r2['market'];
+$nc_lps2 = [$nc_r2['lp']];
+oracle_accept($nc_market2, $nc_oracle2, $SETTINGS);
+
+// Bets: user A bets 300 on side 0, user B bets 200 on side 1
+$nc_b2a = place_bet($nc_market2, $nc_bettor_a, 0, 300000, $base_time + 3600);
+$nc_b2b = place_bet($nc_market2, $nc_bettor_b, 1, 200000, $base_time + 7200);
+$nc_bets2 = [$nc_b2a['bet'], $nc_b2b['bet']];
+$nc_payouts2 = [];
+
+$oracle_ins_before = $nc_oracle2['oracle_insurance'];
+
+// Step 1: Oracle abuses no-contest (creates pending refund payouts, payout_status=1)
+$nc_res2 = oracle_no_contest($nc_market2, $nc_oracle2, $nc_bets2, $nc_lps2, $nc_payouts2, $SETTINGS);
+step('Oracle declares abusive no-contest (grace period)', [
+ 'penalty' => fmt($nc_res2['penalty']),
+ 'oracle_insurance_after_no_contest' => fmt($nc_oracle2['oracle_insurance']),
+ 'market_resolved_outcome' => $nc_market2['resolved_outcome'] . ' (no-contest)',
+ 'market_payout_status' => $nc_market2['payout_status'] . ' (pending — disputable)',
+ 'pending_payouts' => count($nc_payouts2),
+]);
+assert($nc_market2['resolved_outcome'] == -1);
+assert($nc_market2['payout_status'] == 1); // pending, not finalized
+
+// Step 2: BettorA disputes during grace period (outcome was clearly A)
+$nc_market2['payout_status'] = 3; // disputed
+$dispute_fee = $SETTINGS['dispute_fee'];
+$nc_bettor_a['balance'] -= $dispute_fee;
+step('BettorA disputes no-contest during grace period', [
+ 'dispute_fee_paid' => fmt($dispute_fee),
+ 'bettor_a_balance' => fmt($nc_bettor_a['balance']),
+ 'market_payout_status' => '3 (disputed)',
+]);
+
+// Step 3: Resolver chooses correct_outcome=0 (A wins) — oracle was WRONG
+// Oracle declared -1, correct is 0, so oracle_was_wrong = true
+$correct_outcome = 0; // A wins
+$oracle_was_wrong = ($correct_outcome != $nc_market2['resolved_outcome']); // 0 != -1 → true
+assert($oracle_was_wrong === true);
+
+// Delete pending no-contest refund payouts (they'll be replaced with winner payouts)
+$nc_payouts2 = []; // clear pending refunds
+
+// Plaintiff gets 2x dispute_fee (oracle was wrong)
+$nc_bettor_a['balance'] += 2 * $dispute_fee;
+
+// Oracle loses dispute_fee from insurance
+$oracle_ins_before_dispute = $nc_oracle2['oracle_insurance'];
+$nc_oracle2['oracle_insurance'] -= $dispute_fee;
+if ($nc_oracle2['oracle_insurance'] < 0) $nc_oracle2['oracle_insurance'] = 0;
+
+// Extra penalty + permanent ban
+$penalty_extra = 2000000; // 2000 VIZ extra
+$actual_extra = min($penalty_extra, $nc_oracle2['oracle_insurance']);
+$nc_oracle2['oracle_insurance'] -= $actual_extra;
+$nc_oracle2['oracle_banned'] = 1;
+$nc_oracle2['oracle_ban_until'] = 0;
+
+step('Resolver: correct_outcome=0 (A wins), oracle WRONG + sanctions', [
+ 'correct_outcome' => $correct_outcome . ' (A wins)',
+ 'original_outcome' => '-1 (no-contest)',
+ 'oracle_was_wrong' => $oracle_was_wrong ? 'YES' : 'NO',
+ 'plaintiff_reward' => fmt(2 * $dispute_fee),
+ 'oracle_dispute_fee_slash' => fmt($dispute_fee),
+ 'oracle_extra_penalty' => fmt($actual_extra),
+ 'oracle_insurance_after' => fmt($nc_oracle2['oracle_insurance']),
+ 'oracle_banned' => 'PERMANENT',
+]);
+
+// Step 4: Recalculate payouts for correct_outcome=0 (A wins)
+// Since correct_outcome=0, side-0 bettors win, side-1 lose
+// Use resolve_market logic with outcome=0
+$nc_bets2_fresh = [$nc_b2a['bet'], $nc_b2b['bet']]; // fresh copies (original amounts)
+// Reset bet statuses to active for recalculation
+foreach ($nc_bets2_fresh as &$fb) { $fb['status'] = 0; }
+unset($fb);
+$nc_lps2_fresh = [$nc_r2['lp']]; // fresh LP
+foreach ($nc_lps2_fresh as &$fl) { $fl['status'] = 0; }
+unset($fl);
+$nc_payouts2_new = [];
+resolve_market($nc_market2, $correct_outcome, $nc_bets2_fresh, $nc_lps2_fresh, $nc_payouts2_new);
+
+// Process payouts
+$nc_users_map2 = [
+ $nc_bettor_a['id'] => &$nc_bettor_a,
+ $nc_bettor_b['id'] => &$nc_bettor_b,
+ $nc_maker2['id'] => &$nc_maker2,
+];
+$ba_before = $nc_bettor_a['balance'];
+$bb_before = $nc_bettor_b['balance'];
+$maker2_before = $nc_maker2['balance'];
+$payout_results2 = auto_payout($nc_payouts2_new, $nc_users_map2);
+$nc_market2['payout_status'] = 2; // finalized
+
+step('Payouts recalculated for correct_outcome=0 (A wins)', [
+ 'payouts_processed' => count($payout_results2),
+ 'bettor_a_received' => fmt($nc_bettor_a['balance'] - $ba_before) . ' (winner)',
+ 'bettor_b_received' => fmt($nc_bettor_b['balance'] - $bb_before) . ' (loser, 0)',
+ 'maker_received' => fmt($nc_maker2['balance'] - $maker2_before) . ' (LP fee)',
+ 'market_payout_status' => '2 (finalized)',
+]);
+
+// Winner should get more than their bet (they won the pool)
+assert($nc_bettor_a['balance'] > $ba_before);
+// Market finalized
+assert($nc_market2['payout_status'] == 2);
+
+// Step 5: Verify total damage to oracle
+$total_insurance_lost = $oracle_ins_before - $nc_oracle2['oracle_insurance'];
+step('Scenario 18 complete: total oracle damage from abusive no-contest', [
+ 'no_contest_penalty' => fmt($nc_res2['penalty']) . ' (initial, from no-contest declaration)',
+ 'dispute_fee_slash' => fmt($dispute_fee),
+ 'extra_penalty' => fmt($actual_extra),
+ 'total_insurance_lost' => fmt($total_insurance_lost) . ' out of ' . fmt($oracle_ins_before),
+ 'oracle_banned' => 'PERMANENT',
+ 'correct_outcome_applied' => 'A wins (side 0) — bettors paid correctly',
+ 'conclusion' => 'Abusing no-contest costs MORE than honest resolution',
+ 'resolver_options' => 'A wins (0) / B wins (1) / confirm no-contest (-1) + sanctions',
+]);
+
+// Verify: total damage > no-contest penalty alone
+assert($total_insurance_lost > $nc_res2['penalty']);
+// Verify: oracle is banned
+assert($nc_oracle2['oracle_banned'] == 1);
+
+
+// ============================================================
+// SCENARIO 19: AUTO-CLOSE STALE DISPUTE (denial-of-resolution prevention)
+// Dispute resolver doesn't act for 14 days → auto-close, refund all, penalize oracle
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 19: AUTO-CLOSE STALE DISPUTE\n";
+echo str_repeat('=', 70) . "\n";
+
+// Setup: create a market, resolve it, file a dispute, then simulate 14-day timeout
+$ac_oracle = make_user(180, 'OracleAC', 100000000);
+register_oracle($ac_oracle, 0, 5, $SETTINGS);
+oracle_deposit_insurance($ac_oracle, 5000000); // 5000 VIZ
+$ac_maker = make_user(181, 'MakerAC', 100000000);
+register_creator($ac_maker, $SETTINGS);
+$ac_committee = make_user(182, 'CommitteeAC', 0);
+$ac_committee['committee'] = 1;
+$ac_bettor1 = make_user(183, 'BettorAC1', 100000000);
+$ac_bettor2 = make_user(184, 'BettorAC2', 100000000);
+
+$ac_r = create_market($ac_maker, $ac_oracle, 200000, 5, 10, $base_time,
+ $base_time + 172800, $base_time + 259200, 1, 5, $ac_committee['id'], $SETTINGS, -1, $ac_committee);
+$ac_market = $ac_r['market'];
+$ac_lps = [$ac_r['lp']];
+oracle_accept($ac_market, $ac_oracle, $SETTINGS);
+
+// Place bets
+$ac_b1 = place_bet($ac_market, $ac_bettor1, 0, 300000, $base_time + 3600);
+$ac_b2 = place_bet($ac_market, $ac_bettor2, 1, 200000, $base_time + 7200);
+
+// Oracle resolves to outcome 0
+$ac_bets = [$ac_b1['bet'], $ac_b2['bet']];
+$ac_payouts = [];
+resolve_market($ac_market, 0, $ac_bets, $ac_lps, $ac_payouts);
+
+$bettor1_balance_before = $ac_bettor1['balance'];
+$oracle_insurance_before = $ac_oracle['oracle_insurance'];
+
+// Bettor2 files dispute (they lost)
+$dispute_fee = $SETTINGS['dispute_fee'];
+$ac_bettor2['balance'] -= $dispute_fee;
+$ac_market['payout_status'] = 3; // disputed
+
+step('Dispute filed, resolver does nothing...', [
+ 'bettor2_paid_dispute_fee' => fmt($dispute_fee),
+ 'market_payout_status' => 3,
+ 'auto_close_days' => $SETTINGS['dispute_auto_close_days'],
+]);
+
+// Simulate auto-close after 14 days (cron job logic)
+$auto_close_days = $SETTINGS['dispute_auto_close_days'];
+
+// Refund plaintiff dispute fee
+$ac_bettor2['balance'] += $dispute_fee;
+
+// Penalize oracle: dispute_fee from insurance
+$oracle_penalty = min($dispute_fee, $ac_oracle['oracle_insurance']);
+$ac_oracle['oracle_insurance'] -= $oracle_penalty;
+
+// Refund all bets (original amounts)
+$bet1_refund = intval($ac_b1['bet']['amount']);
+$bet2_refund = intval($ac_b2['bet']['amount']);
+$ac_bettor1['balance'] += $bet1_refund;
+$ac_bettor2['balance'] += $bet2_refund;
+
+// Refund LP
+$lp_refund = intval($ac_lps[0]['amount']);
+$ac_maker['balance'] += $lp_refund;
+
+// Distribute oracle penalty proportionally
+$total_stakes = $bet1_refund + $bet2_refund + $lp_refund;
+$bonus1 = intval($oracle_penalty * $bet1_refund / $total_stakes);
+$bonus2 = intval($oracle_penalty * $bet2_refund / $total_stakes);
+$bonus_lp = intval($oracle_penalty * $lp_refund / $total_stakes);
+$ac_bettor1['balance'] += $bonus1;
+$ac_bettor2['balance'] += $bonus2;
+$ac_maker['balance'] += $bonus_lp;
+
+// Market finalized
+$ac_market['payout_status'] = 2;
+
+step('Auto-close after ' . $auto_close_days . ' days: all refunded + oracle penalized', [
+ 'dispute_fee_refunded_to_plaintiff' => fmt($dispute_fee),
+ 'oracle_penalty' => fmt($oracle_penalty),
+ 'oracle_insurance_after' => fmt($ac_oracle['oracle_insurance']),
+ 'bettor1_refund' => fmt($bet1_refund) . ' + bonus ' . fmt($bonus1),
+ 'bettor2_refund' => fmt($bet2_refund) . ' + bonus ' . fmt($bonus2),
+ 'lp_refund' => fmt($lp_refund) . ' + bonus ' . fmt($bonus_lp),
+ 'total_penalty_distributed' => fmt($bonus1 + $bonus2 + $bonus_lp),
+ 'market_payout_status' => $ac_market['payout_status'],
+]);
+
+// Verify: plaintiff got their dispute fee back
+assert($ac_bettor2['balance'] > $bettor1_balance_before - $dispute_fee); // bettor2 is whole
+// Verify: oracle was penalized
+assert($ac_oracle['oracle_insurance'] == $oracle_insurance_before - $oracle_penalty);
+// Verify: market is finalized
+assert($ac_market['payout_status'] == 2);
+
+step('Scenario 19 complete: stale dispute auto-closed', [
+ 'protection' => 'Committee inaction cannot freeze funds indefinitely',
+ 'fallback' => 'After ' . $auto_close_days . ' days: refund all + oracle penalty (dispute_fee)',
+ 'plaintiff' => 'Gets dispute fee back (not punished for committee failure)',
+ 'oracle' => 'Loses dispute_fee from insurance (accountability)',
+]);
+
+
+// ============================================================
+// LAZY POOL SETTINGS
+// ============================================================
+$SETTINGS['lazy_pool_allocation_percent'] = 2;
+$SETTINGS['lazy_pool_max_total_allocation'] = 70;
+$SETTINGS['lazy_pool_min_market_allocation'] = 100000; // 100 VIZ
+$SETTINGS['lazy_pool_lock_period_days'] = 30;
+$SETTINGS['lazy_pool_emergency_penalty'] = 50;
+
+$LAZY_POOL_PRECISION = 1000000000; // 10^9
+
+// ============================================================
+// LAZY POOL HELPER SIMULATION FUNCTIONS
+// ============================================================
+function make_lazy_pool() {
+ return [
+ 'total_shares' => 0,
+ 'free_balance' => 0,
+ 'allocated_balance' => 0,
+ 'reward_per_share' => 0,
+ ];
+}
+
+function make_lazy_user($id, $name, $balance) {
+ $u = make_user($id, $name, $balance);
+ $u['lazy_pool_balance'] = 0;
+ $u['lazy_pool_shares'] = 0;
+ $u['lazy_pool_reward_snapshot'] = 0;
+ $u['lazy_pool_pending_rewards'] = 0;
+ return $u;
+}
+
+function lp_settle_rewards(&$user, $pool) {
+ global $LAZY_POOL_PRECISION;
+ $shares = $user['lazy_pool_shares'];
+ if ($shares > 0) {
+ $rps_diff = $pool['reward_per_share'] - $user['lazy_pool_reward_snapshot'];
+ if ($rps_diff > 0) {
+ $new_rewards = intval($shares * $rps_diff / $LAZY_POOL_PRECISION);
+ $user['lazy_pool_pending_rewards'] += $new_rewards;
+ }
+ }
+ $user['lazy_pool_reward_snapshot'] = $pool['reward_per_share'];
+}
+
+function lp_deposit(&$user, &$pool, $amount, $lock_days, $time_now) {
+ global $LAZY_POOL_PRECISION;
+ if ($user['balance'] < $amount) return ['error' => 'Insufficient balance'];
+
+ // Settle existing rewards first
+ lp_settle_rewards($user, $pool);
+
+ // Calculate shares
+ if ($pool['total_shares'] == 0) {
+ $new_shares = $amount; // 1:1 first deposit
+ } else {
+ $new_shares = intval($amount * $pool['total_shares'] / $pool['free_balance']);
+ }
+
+ $unlock_time = $time_now + $lock_days * 86400;
+
+ // Update pool
+ $pool['total_shares'] += $new_shares;
+ $pool['free_balance'] += $amount;
+
+ // Update user
+ $user['balance'] -= $amount;
+ $user['lazy_pool_balance'] += $amount;
+ $user['lazy_pool_shares'] += $new_shares;
+ $user['lazy_pool_reward_snapshot'] = $pool['reward_per_share'];
+
+ return [
+ 'status' => true,
+ 'shares' => $new_shares,
+ 'unlock_time' => $unlock_time,
+ ];
+}
+
+function lp_receive_profit(&$pool, $profit) {
+ // ONLY writes to pool. ZERO user writes.
+ global $LAZY_POOL_PRECISION;
+ if ($pool['total_shares'] > 0 && $profit > 0) {
+ $pool['reward_per_share'] += intval($profit * $LAZY_POOL_PRECISION / $pool['total_shares']);
+ }
+}
+
+function lp_withdraw(&$user, &$pool, $shares_to_burn, $principal_to_return) {
+ global $LAZY_POOL_PRECISION;
+ // Settle rewards first
+ lp_settle_rewards($user, $pool);
+
+ // Calculate share value
+ $share_value = intval($shares_to_burn * $pool['free_balance'] / $pool['total_shares']);
+
+ // Calculate reward portion based on shares being burned vs total user shares
+ $reward_portion = 0;
+ if ($user['lazy_pool_shares'] > 0) {
+ $reward_portion = intval($user['lazy_pool_pending_rewards'] * $shares_to_burn / $user['lazy_pool_shares']);
+ }
+
+ $total_payout = $share_value + $reward_portion;
+
+ // Update pool
+ $pool['total_shares'] -= $shares_to_burn;
+ $pool['free_balance'] -= $total_payout;
+
+ // Update user
+ $user['balance'] += $total_payout;
+ $user['lazy_pool_shares'] -= $shares_to_burn;
+ $user['lazy_pool_balance'] -= $principal_to_return;
+ $user['lazy_pool_pending_rewards'] -= $reward_portion;
+
+ return [
+ 'status' => true,
+ 'share_value' => $share_value,
+ 'reward_portion' => $reward_portion,
+ 'total_payout' => $total_payout,
+ ];
+}
+
+function lp_emergency_withdraw(&$user, &$pool, $locked_shares, $total_user_shares, $penalty_pct) {
+ global $LAZY_POOL_PRECISION;
+ // Settle rewards first
+ lp_settle_rewards($user, $pool);
+
+ $total_share_value = intval($total_user_shares * $pool['free_balance'] / $pool['total_shares']);
+ $pending = $user['lazy_pool_pending_rewards'];
+ $profit = $total_share_value + $pending - $user['lazy_pool_balance'];
+
+ $penalty = 0;
+ if ($profit > 0 && $locked_shares > 0 && $total_user_shares > 0) {
+ $penalty = intval($profit * $locked_shares / $total_user_shares * $penalty_pct / 100);
+ }
+
+ // Penalty stays in pool via reward_per_share for remaining participants
+ $remaining_shares = $pool['total_shares'] - $total_user_shares;
+ if ($penalty > 0 && $remaining_shares > 0) {
+ $pool['reward_per_share'] += intval($penalty * $LAZY_POOL_PRECISION / $remaining_shares);
+ }
+
+ $total_payout = $total_share_value + $pending - $penalty;
+
+ // Update pool
+ $pool['total_shares'] -= $total_user_shares;
+ $pool['free_balance'] -= $total_payout;
+
+ // Update user
+ $user['balance'] += $total_payout;
+ $user['lazy_pool_shares'] = 0;
+ $user['lazy_pool_balance'] = 0;
+ $user['lazy_pool_pending_rewards'] = 0;
+ $user['lazy_pool_reward_snapshot'] = $pool['reward_per_share'];
+
+ return [
+ 'status' => true,
+ 'total_share_value' => $total_share_value,
+ 'pending_rewards' => $pending,
+ 'profit' => $profit,
+ 'penalty' => $penalty,
+ 'total_payout' => $total_payout,
+ ];
+}
+
+function lp_frontend_reward($user, $pool) {
+ global $LAZY_POOL_PRECISION;
+ $live = $user['lazy_pool_pending_rewards'];
+ if ($user['lazy_pool_shares'] > 0) {
+ $rps_diff = $pool['reward_per_share'] - $user['lazy_pool_reward_snapshot'];
+ if ($rps_diff > 0) {
+ $live += intval($user['lazy_pool_shares'] * $rps_diff / $LAZY_POOL_PRECISION);
+ }
+ }
+ return $live;
+}
+
+// ============================================================
+// SCENARIO 20: Lazy Pool — Deposit, Shares, Lock Period
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 20: Lazy Pool — Deposit, Shares, Lock Period\n";
+echo str_repeat('=', 70) . "\n";
+
+$pool = make_lazy_pool();
+$lp_user1 = make_lazy_user(100, 'LazyLP_Alice', 10000000); // 10000 VIZ
+$lp_user2 = make_lazy_user(101, 'LazyLP_Bob', 5000000); // 5000 VIZ
+$lock_days = $SETTINGS['lazy_pool_lock_period_days'];
+$time_now = 1000000;
+
+// Alice deposits 1000 VIZ (first depositor)
+$r1 = lp_deposit($lp_user1, $pool, 1000000, $lock_days, $time_now);
+assert($r1['status'] === true);
+assert($r1['shares'] == 1000000); // 1:1 first deposit
+assert($pool['total_shares'] == 1000000);
+assert($pool['free_balance'] == 1000000);
+assert($lp_user1['lazy_pool_shares'] == 1000000);
+assert($lp_user1['lazy_pool_balance'] == 1000000);
+
+step('Scenario 20: First deposit (Alice 1000 VIZ)', [
+ 'shares_received' => $r1['shares'],
+ 'pool_total_shares' => $pool['total_shares'],
+ 'pool_free_balance' => fmt($pool['free_balance']),
+ 'unlock_time' => $r1['unlock_time'],
+]);
+
+// Bob deposits 500 VIZ (proportional shares)
+$r2 = lp_deposit($lp_user2, $pool, 500000, $lock_days, $time_now + 100);
+assert($r2['status'] === true);
+assert($r2['shares'] == 500000); // 500K * 1M / 1M = 500K (proportional, pool hasn't grown yet)
+assert($pool['total_shares'] == 1500000);
+assert($pool['free_balance'] == 1500000);
+
+step('Scenario 20: Second deposit (Bob 500 VIZ)', [
+ 'shares_received' => $r2['shares'],
+ 'pool_total_shares' => $pool['total_shares'],
+ 'pool_free_balance' => fmt($pool['free_balance']),
+ 'alice_shares' => $lp_user1['lazy_pool_shares'],
+ 'bob_shares' => $lp_user2['lazy_pool_shares'],
+]);
+
+// ============================================================
+// SCENARIO 21: Share Calculation After Profit
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 21: Lazy Pool — Share Calculation After Profit\n";
+echo str_repeat('=', 70) . "\n";
+
+// Simulate market profit: pool receives 300 VIZ
+lp_receive_profit($pool, 300000);
+
+// Verify: ONLY pool.reward_per_share changed, NO user writes
+assert($pool['reward_per_share'] == intval(300000 * $LAZY_POOL_PRECISION / 1500000));
+$expected_rps = intval(300000 * $LAZY_POOL_PRECISION / 1500000); // = 200000000
+
+step('Scenario 21: Profit distributed (300 VIZ), ZERO user writes', [
+ 'profit' => fmt(300000),
+ 'reward_per_share' => $pool['reward_per_share'],
+ 'expected_rps' => $expected_rps,
+ 'alice_snapshot_unchanged' => $lp_user1['lazy_pool_reward_snapshot'],
+ 'bob_snapshot_unchanged' => $lp_user2['lazy_pool_reward_snapshot'],
+ 'alice_pending_unchanged' => $lp_user1['lazy_pool_pending_rewards'],
+]);
+
+// Frontend reward calculation (NO backend write)
+$alice_live = lp_frontend_reward($lp_user1, $pool);
+$bob_live = lp_frontend_reward($lp_user2, $pool);
+
+// Alice: 1M shares, Bob: 500K shares. Profit 300K split 2:1
+assert($alice_live == intval(1000000 * $expected_rps / $LAZY_POOL_PRECISION)); // 200 VIZ
+assert($bob_live == intval(500000 * $expected_rps / $LAZY_POOL_PRECISION)); // 100 VIZ
+
+step('Scenario 21: Frontend reward (JS formula, zero writes)', [
+ 'alice_live_reward' => fmt($alice_live),
+ 'bob_live_reward' => fmt($bob_live),
+ 'formula' => 'pending + shares * (pool.rps - user.snapshot) / PRECISION',
+]);
+
+// ============================================================
+// SCENARIO 22: Late Depositor Fairness
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 22: Lazy Pool — Late Depositor Does NOT Get Old Rewards\n";
+echo str_repeat('=', 70) . "\n";
+
+$lp_user3 = make_lazy_user(102, 'LazyLP_Charlie', 5000000); // 5000 VIZ
+
+// Charlie deposits AFTER the 300 VIZ profit was distributed
+$r3 = lp_deposit($lp_user3, $pool, 1000000, $lock_days, $time_now + 200);
+assert($r3['status'] === true);
+// Shares: 1000000 * 1500000 / 1500000 = 1000000 (pool hasn't had free_balance change from rewards)
+// Wait - free_balance didn't change from rewards (rewards are tracked via rps, not free_balance)
+// So Charlie gets 1M shares for 1M amount — same as Alice
+
+$charlie_live = lp_frontend_reward($lp_user3, $pool);
+assert($charlie_live == 0); // NO old rewards for late depositor!
+
+step('Scenario 22: Late depositor Charlie gets ZERO old rewards', [
+ 'charlie_shares' => $r3['shares'],
+ 'charlie_live_reward' => fmt($charlie_live),
+ 'charlie_snapshot' => $lp_user3['lazy_pool_reward_snapshot'],
+ 'pool_rps' => $pool['reward_per_share'],
+ 'fairness' => 'snapshot == pool.rps at deposit time, so rps_diff=0',
+]);
+
+// ============================================================
+// SCENARIO 23: Unlock Consolidation
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 23: Lazy Pool — Deposit Unlock Consolidation\n";
+echo str_repeat('=', 70) . "\n";
+
+// Simulate 3 deposits with different unlock times
+$deposits = [
+ ['id' => 1, 'user' => 200, 'amount' => 100000, 'shares' => 100000, 'unlock_time' => 1000, 'status' => 0],
+ ['id' => 2, 'user' => 200, 'amount' => 200000, 'shares' => 200000, 'unlock_time' => 2000, 'status' => 0],
+ ['id' => 3, 'user' => 200, 'amount' => 300000, 'shares' => 300000, 'unlock_time' => 5000, 'status' => 0],
+];
+
+// At time=2500, deposits 1 and 2 are expired, deposit 3 still locked
+$check_time = 2500;
+$expired = [];
+$still_locked = [];
+foreach ($deposits as &$dep) {
+ if ($dep['status'] == 0 && $dep['unlock_time'] <= $check_time) {
+ $expired[] = $dep;
+ $dep['status'] = 2; // merged
+ } else {
+ $still_locked[] = $dep;
+ }
+}
+unset($dep);
+
+$sum_shares = 0; $sum_amount = 0;
+foreach ($expired as $e) {
+ $sum_shares += $e['shares'];
+ $sum_amount += $e['amount'];
+}
+
+// Create consolidated unlocked record
+$unlocked_record = ['id' => 4, 'user' => 200, 'amount' => $sum_amount, 'shares' => $sum_shares, 'unlock_time' => 0, 'status' => 1];
+
+assert($unlocked_record['amount'] == 300000); // 100K + 200K
+assert($unlocked_record['shares'] == 300000); // 100K + 200K
+assert(count($expired) == 2);
+assert($deposits[2]['status'] == 0); // deposit 3 still locked
+
+step('Scenario 23: Consolidation at time=2500', [
+ 'expired_count' => count($expired),
+ 'merged_amount' => fmt($sum_amount),
+ 'merged_shares' => $sum_shares,
+ 'unlocked_record' => $unlocked_record,
+ 'still_locked' => $still_locked,
+ 'rule' => 'Max 1 unlocked (status=1) + N locked (status=0) per user',
+]);
+
+// Second consolidation: deposit 3 expires at time=5500
+$check_time2 = 5500;
+$expired2 = [];
+foreach ($deposits as &$dep) {
+ if ($dep['status'] == 0 && $dep['unlock_time'] <= $check_time2) {
+ $expired2[] = $dep;
+ $dep['status'] = 2;
+ }
+}
+unset($dep);
+assert(count($expired2) == 1);
+
+// Merge into existing unlocked record
+$unlocked_record['amount'] += $expired2[0]['amount'];
+$unlocked_record['shares'] += $expired2[0]['shares'];
+assert($unlocked_record['amount'] == 600000); // 300K + 300K
+assert($unlocked_record['shares'] == 600000);
+
+step('Scenario 23: Second consolidation — all merged into one record', [
+ 'final_unlocked_amount' => fmt($unlocked_record['amount']),
+ 'final_unlocked_shares' => $unlocked_record['shares'],
+ 'total_deposits' => 3,
+ 'merged_deposits' => 3,
+ 'result' => '1 unlocked record with all shares',
+]);
+
+// ============================================================
+// SCENARIO 24: Planned Withdrawal
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 24: Lazy Pool — Planned Withdrawal\n";
+echo str_repeat('=', 70) . "\n";
+
+// Use Alice from scenarios 20-21 (has 1M shares, pool has profit)
+// First, more profit to make things interesting
+lp_receive_profit($pool, 150000); // another 150 VIZ profit
+
+$alice_shares_before = $lp_user1['lazy_pool_shares'];
+$alice_balance_before = $lp_user1['balance'];
+
+// Alice withdraws all her unlocked shares (simulate they're all unlocked now)
+$r_w = lp_withdraw($lp_user1, $pool, $alice_shares_before, $lp_user1['lazy_pool_balance']);
+
+assert($r_w['status'] === true);
+assert($lp_user1['lazy_pool_shares'] == 0);
+assert($r_w['total_payout'] > 0);
+
+step('Scenario 24: Alice full withdrawal', [
+ 'shares_burned' => $alice_shares_before,
+ 'share_value' => fmt($r_w['share_value']),
+ 'reward_portion' => fmt($r_w['reward_portion']),
+ 'total_payout' => fmt($r_w['total_payout']),
+ 'alice_balance_before' => fmt($alice_balance_before),
+ 'alice_balance_after' => fmt($lp_user1['balance']),
+ 'pool_total_shares_after' => $pool['total_shares'],
+ 'pool_free_balance_after' => fmt($pool['free_balance']),
+]);
+
+// ============================================================
+// SCENARIO 25: Emergency Withdrawal with Penalty
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 25: Lazy Pool — Emergency Withdrawal (Locked + Penalty)\n";
+echo str_repeat('=', 70) . "\n";
+
+// Reset pool for clean scenario
+$epool = make_lazy_pool();
+$e_user1 = make_lazy_user(300, 'Emergency_Alice', 10000000);
+$e_user2 = make_lazy_user(301, 'Emergency_Bob', 10000000);
+
+// Both deposit 1000 VIZ
+lp_deposit($e_user1, $epool, 1000000, $lock_days, $time_now);
+lp_deposit($e_user2, $epool, 1000000, $lock_days, $time_now);
+
+// Pool receives 200 VIZ profit
+lp_receive_profit($epool, 200000);
+
+$bob_balance_before = $e_user2['balance'];
+
+// Alice does emergency withdrawal — all her shares are locked
+$r_e = lp_emergency_withdraw(
+ $e_user1, $epool,
+ 1000000, // locked_shares = all
+ 1000000, // total_user_shares
+ $SETTINGS['lazy_pool_emergency_penalty']
+);
+
+assert($r_e['status'] === true);
+assert($r_e['profit'] > 0);
+assert($r_e['penalty'] > 0);
+assert($e_user1['lazy_pool_shares'] == 0);
+
+// Verify penalty stayed in pool for Bob
+$bob_live = lp_frontend_reward($e_user2, $epool);
+
+step('Scenario 25: Emergency withdrawal — penalty on locked profit', [
+ 'total_share_value' => fmt($r_e['total_share_value']),
+ 'pending_rewards' => fmt($r_e['pending_rewards']),
+ 'profit' => fmt($r_e['profit']),
+ 'penalty' => fmt($r_e['penalty']),
+ 'total_payout' => fmt($r_e['total_payout']),
+ 'alice_balance_after' => fmt($e_user1['balance']),
+ 'bob_live_reward_after' => fmt($bob_live),
+ 'penalty_redistributed' => 'Yes, via reward_per_share to remaining participants',
+]);
+
+// Verify Bob got the penalty via increased rps
+assert($bob_live > intval(200000 * 1000000 / 2000000)); // Bob gets more than half of 200K profit
+
+step('Scenario 25: Bob benefits from Alice penalty', [
+ 'bob_fair_share_of_profit' => fmt(intval(200000 / 2)),
+ 'bob_actual_reward' => fmt($bob_live),
+ 'extra_from_penalty' => fmt($bob_live - intval(200000 / 2)),
+]);
+
+// ============================================================
+// SCENARIO 26: Emergency Withdrawal — No Profit, No Penalty
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 26: Lazy Pool — Emergency No Profit = No Penalty\n";
+echo str_repeat('=', 70) . "\n";
+
+$np_pool = make_lazy_pool();
+$np_user = make_lazy_user(400, 'NoProfitUser', 5000000);
+lp_deposit($np_user, $np_pool, 1000000, $lock_days, $time_now);
+
+// No profit received — emergency withdraw
+$r_np = lp_emergency_withdraw(
+ $np_user, $np_pool,
+ 1000000, // all locked
+ 1000000,
+ $SETTINGS['lazy_pool_emergency_penalty']
+);
+
+assert($r_np['penalty'] == 0);
+assert($r_np['total_payout'] == 1000000); // Full principal returned
+assert($np_user['balance'] == 5000000); // Back to original
+
+step('Scenario 26: No profit = no penalty', [
+ 'profit' => fmt($r_np['profit']),
+ 'penalty' => fmt($r_np['penalty']),
+ 'total_payout' => fmt($r_np['total_payout']),
+ 'user_balance_restored' => fmt($np_user['balance']),
+]);
+
+// ============================================================
+// SCENARIO 27: Market Auto-Allocation
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 27: Lazy Pool — Market Auto-Allocation\n";
+echo str_repeat('=', 70) . "\n";
+
+$alloc_pool = make_lazy_pool();
+$alloc_user = make_lazy_user(500, 'AllocUser', 50000000); // 50000 VIZ
+lp_deposit($alloc_user, $alloc_pool, 20000000, $lock_days, $time_now); // 20000 VIZ
+
+$alloc_pct = $SETTINGS['lazy_pool_allocation_percent'];
+$max_alloc_pct = $SETTINGS['lazy_pool_max_total_allocation'];
+$min_alloc = $SETTINGS['lazy_pool_min_market_allocation'];
+
+$alloc_amount = intval($alloc_pool['free_balance'] * $alloc_pct / 100);
+$passes_min = ($alloc_amount >= $min_alloc);
+$total_pool_value = $alloc_pool['free_balance'] + $alloc_pool['allocated_balance'];
+$passes_max = ($alloc_pool['allocated_balance'] + $alloc_amount <= $total_pool_value * $max_alloc_pct / 100);
+
+assert($alloc_amount == 400000); // 2% of 20000000 = 400000 (400 VIZ)
+assert($passes_min === true);
+assert($passes_max === true);
+
+// Simulate allocation
+$alloc_pool['free_balance'] -= $alloc_amount;
+$alloc_pool['allocated_balance'] += $alloc_amount;
+
+step('Scenario 27: Auto-allocation on market activation', [
+ 'pool_free_balance' => fmt(20000000),
+ 'allocation_percent' => $alloc_pct . '%',
+ 'allocation_amount' => fmt($alloc_amount),
+ 'passes_min_check' => $passes_min ? 'YES' : 'NO',
+ 'passes_max_check' => $passes_max ? 'YES' : 'NO',
+ 'pool_free_after' => fmt($alloc_pool['free_balance']),
+ 'pool_allocated_after' => fmt($alloc_pool['allocated_balance']),
+]);
+
+// Simulate market resolution with profit
+$market_return = $alloc_amount + 50000; // principal + 50 VIZ profit
+$profit = $market_return - $alloc_amount;
+$alloc_pool['free_balance'] += $market_return;
+$alloc_pool['allocated_balance'] -= $alloc_amount;
+lp_receive_profit($alloc_pool, $profit);
+
+$user_reward = lp_frontend_reward($alloc_user, $alloc_pool);
+
+step('Scenario 27: Market resolved — profit distributed via rps', [
+ 'market_return' => fmt($market_return),
+ 'profit' => fmt($profit),
+ 'pool_rps_after' => $alloc_pool['reward_per_share'],
+ 'user_live_reward' => fmt($user_reward),
+ 'pool_free_after' => fmt($alloc_pool['free_balance']),
+ 'pool_allocated_after' => fmt($alloc_pool['allocated_balance']),
+]);
+
+assert($user_reward == $profit); // Solo depositor gets all profit
+
+// ============================================================
+// SCENARIO 28: Edge Cases
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 28: Lazy Pool — Edge Cases\n";
+echo str_repeat('=', 70) . "\n";
+
+// 28a: First depositor gets 1:1 shares
+$edge_pool = make_lazy_pool();
+$edge_user = make_lazy_user(600, 'EdgeUser', 5000000);
+$r_edge = lp_deposit($edge_user, $edge_pool, 777000, $lock_days, $time_now);
+assert($r_edge['shares'] == 777000);
+
+step('Scenario 28a: First depositor 1:1 shares', [
+ 'amount' => fmt(777000),
+ 'shares' => $r_edge['shares'],
+ 'rule' => 'When total_shares==0, shares=amount',
+]);
+
+// 28b: Zero reward when rps hasn't changed
+$edge_user2 = make_lazy_user(601, 'EdgeUser2', 5000000);
+lp_deposit($edge_user2, $edge_pool, 500000, $lock_days, $time_now);
+$r_zero = lp_frontend_reward($edge_user2, $edge_pool);
+assert($r_zero == 0);
+
+step('Scenario 28b: Zero reward when no profit has occurred', [
+ 'live_reward' => fmt($r_zero),
+ 'pool_rps' => $edge_pool['reward_per_share'],
+ 'user_snapshot' => $edge_user2['lazy_pool_reward_snapshot'],
+]);
+
+// 28c: Partial withdrawal (50%)
+$partial_pool = make_lazy_pool();
+$partial_user = make_lazy_user(700, 'PartialUser', 10000000);
+lp_deposit($partial_user, $partial_pool, 2000000, $lock_days, $time_now);
+lp_receive_profit($partial_pool, 100000); // 100 VIZ profit
+
+// Withdraw 50% of shares
+$half_shares = intval($partial_user['lazy_pool_shares'] / 2);
+$half_principal = intval($partial_user['lazy_pool_balance'] / 2);
+$r_partial = lp_withdraw($partial_user, $partial_pool, $half_shares, $half_principal);
+
+assert($partial_user['lazy_pool_shares'] == $half_shares); // Half remains
+assert($r_partial['total_payout'] > 0);
+
+step('Scenario 28c: Partial withdrawal (50%)', [
+ 'shares_burned' => $half_shares,
+ 'shares_remaining' => $partial_user['lazy_pool_shares'],
+ 'payout' => fmt($r_partial['total_payout']),
+ 'share_value' => fmt($r_partial['share_value']),
+ 'reward_portion' => fmt($r_partial['reward_portion']),
+]);
+
+// ============================================================
+// SCENARIO 29: LMSR MATH UNIT TESTS
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 29: LMSR Math Unit Tests\n";
+echo str_repeat('=', 70) . "\n";
+
+require_once __DIR__ . '/../module/lmsr_fixed.php';
+
+// 29a: Price calculation — equal q → equal prices
+$q3 = [0, 0, 0];
+$b3 = 100000; // b = 100
+$prices3 = lmsr_prices($q3, $b3);
+$expected_price = intval(round(1000000 / 3)); // ~333333
+assert(abs($prices3[0] - $expected_price) <= 1, 'Equal q should give equal prices');
+assert(abs($prices3[1] - $expected_price) <= 1);
+assert(abs($prices3[2] - $expected_price) <= 1);
+$price_sum = array_sum($prices3);
+assert(abs($price_sum - 1000000) <= count($q3), 'Prices must sum to ~1.0');
+
+step('Scenario 29a: Equal q → equal prices (3 outcomes)', [
+ 'q' => $q3, 'b' => $b3,
+ 'prices' => $prices3, 'sum' => $price_sum,
+]);
+
+// 29b: Cost function monotonicity
+$cost0 = lmsr_cost([0, 0, 0], $b3);
+$cost1 = lmsr_cost([10000, 0, 0], $b3);
+assert($cost1 > $cost0, 'Cost must increase when q increases');
+
+step('Scenario 29b: Cost monotonicity', [
+ 'cost_at_0' => $cost0, 'cost_at_10k' => $cost1,
+]);
+
+// 29c: Buy cost > 0 and sell return > 0
+$buy = lmsr_buy_cost([0, 0, 0], $b3, 0, 5000);
+$sell = lmsr_sell_return([5000, 0, 0], $b3, 0, 5000);
+assert($buy > 0, 'Buy cost must be positive');
+assert($sell > 0, 'Sell return must be positive');
+assert(abs($buy - $sell) <= 2, 'Buy/sell roundtrip should be nearly equal');
+
+step('Scenario 29c: Buy/sell roundtrip', [
+ 'buy_cost_5000' => $buy, 'sell_return_5000' => $sell, 'diff' => abs($buy - $sell),
+]);
+
+// 29d: tokens_for_amount consistency
+$tokens = lmsr_tokens_for_amount([0, 0, 0], $b3, 0, 50000); // spend 50 VIZ
+assert($tokens > 0, 'Must get some tokens');
+$actual_cost = lmsr_buy_cost([0, 0, 0], $b3, 0, $tokens);
+assert($actual_cost <= 50000, 'Actual cost must not exceed amount');
+
+step('Scenario 29d: tokens_for_amount', [
+ 'amount' => 50000, 'tokens' => $tokens, 'actual_cost' => $actual_cost,
+]);
+
+// 29e: b_from_liquidity
+$b_calc = lmsr_b_from_liquidity(100000, 3); // 100 VIZ, 3 outcomes
+assert($b_calc > 0);
+$max_loss = lmsr_max_loss($b_calc, 3);
+assert(abs($max_loss - 100000) < 500, 'max_loss should ≈ liquidity'); // b*ln(3) ≈ liquidity
+
+step('Scenario 29e: b_from_liquidity', [
+ 'liquidity' => 100000, 'n' => 3, 'b' => $b_calc, 'max_loss' => $max_loss,
+]);
+
+// 29f: Price shift after buying — price of bought outcome increases
+$q_before = [0, 0, 0];
+$p_before = lmsr_prices($q_before, $b3);
+$q_after = [10000, 0, 0]; // bought 10 tokens on outcome 0
+$p_after = lmsr_prices($q_after, $b3);
+assert($p_after[0] > $p_before[0], 'Bought outcome price must increase');
+assert($p_after[1] < $p_before[1], 'Other outcome price must decrease');
+
+step('Scenario 29f: Price shift after buy', [
+ 'before' => $p_before, 'after' => $p_after,
+]);
+
+
+// ============================================================
+// SCENARIO 30: LMSR SETTLEMENT (Onix Multi)
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 30: Onix Multi Settlement\n";
+echo str_repeat('=', 70) . "\n";
+
+$bets_multi = [
+ ['id'=>1, 'user'=>10, 'amount'=>50000, 'weight'=>45000, 'outcome_index'=>0, 'time_penalty'=>0],
+ ['id'=>2, 'user'=>11, 'amount'=>30000, 'weight'=>25000, 'outcome_index'=>0, 'time_penalty'=>50000], // 5% penalty
+ ['id'=>3, 'user'=>12, 'amount'=>40000, 'weight'=>35000, 'outcome_index'=>1, 'time_penalty'=>0],
+ ['id'=>4, 'user'=>13, 'amount'=>60000, 'weight'=>50000, 'outcome_index'=>2, 'time_penalty'=>0],
+];
+
+$settlement = lmsr_settlement($bets_multi, 0, 5, 3, 10); // oracle 0.5%, creator 0.3%, LP 1%
+
+// Losers = bets on outcomes 1 + 2 = 40000 + 60000 = 100000
+assert($settlement['losers_sum'] == 100000, 'Losers sum = 100000');
+assert($settlement['oracle_fee'] == intval(100000 * 5 / 1000), 'Oracle fee');
+assert($settlement['creator_fee'] == intval(100000 * 3 / 1000), 'Creator fee');
+assert($settlement['liquidity_fee'] == intval(100000 * 10 / 1000), 'Liquidity fee');
+assert($settlement['winners_pool'] == 100000 - 500 - 300 - 1000, 'Winners pool');
+assert(count($settlement['payouts']) == 2, 'Two winners');
+
+// Winners get their amount back + profit share
+foreach ($settlement['payouts'] as $p) {
+ assert($p['payout'] >= $p['amount'], 'Winner payout >= original bet');
+}
+
+step('Scenario 30: Settlement', [
+ 'losers_sum' => $settlement['losers_sum'],
+ 'oracle_fee' => $settlement['oracle_fee'],
+ 'creator_fee' => $settlement['creator_fee'],
+ 'liquidity_fee' => $settlement['liquidity_fee'],
+ 'winners_pool' => $settlement['winners_pool'],
+ 'payouts' => $settlement['payouts'],
+ 'undistributed' => $settlement['undistributed'],
+]);
+
+
+// ============================================================
+// SCENARIO 31: CREATOR FEE IN BINARY RESOLUTION
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 31: Creator Fee in Binary Resolution\n";
+echo str_repeat('=', 70) . "\n";
+
+// Simulate binary resolution with creator fee using same settlement logic
+$binary_bets = [
+ ['id'=>10, 'user'=>20, 'amount'=>100000, 'weight'=>95000, 'outcome_index'=>0, 'time_penalty'=>0],
+ ['id'=>11, 'user'=>21, 'amount'=>80000, 'weight'=>70000, 'outcome_index'=>1, 'time_penalty'=>0],
+];
+
+$binary_settle = lmsr_settlement($binary_bets, 0, 10, 5, 20); // oracle 1%, creator 0.5%, LP 2%
+assert($binary_settle['losers_sum'] == 80000, 'Losers = side B');
+assert($binary_settle['oracle_fee'] == 800, 'Oracle fee = 80000 * 10/1000 = 800');
+assert($binary_settle['creator_fee'] == 400, 'Creator fee = 80000 * 5/1000 = 400');
+assert($binary_settle['liquidity_fee'] == 1600, 'LP fee = 80000 * 20/1000 = 1600');
+assert($binary_settle['winners_pool'] == 80000 - 800 - 400 - 1600, 'Winners pool = 77200');
+assert($binary_settle['payouts'][0]['payout'] == 100000 + 77200, 'Winner gets bet + pool');
+
+step('Scenario 31: Binary with creator fee', [
+ 'losers_sum' => $binary_settle['losers_sum'],
+ 'creator_fee' => $binary_settle['creator_fee'],
+ 'winner_payout' => $binary_settle['payouts'][0]['payout'],
+]);
+
+
+// ============================================================
+// SCENARIO 32: MULTI-MARKET LIFECYCLE
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 32: Multi-Market Lifecycle\n";
+echo str_repeat('=', 70) . "\n";
+
+// Create market with 4 outcomes, b = 200
+$lifecycle_b = 200000;
+$lifecycle_q = [0, 0, 0, 0];
+$lifecycle_subsidy = lmsr_max_loss($lifecycle_b, 4); // subsidy = b * ln(4)
+
+// Place bets on multiple outcomes
+$bet_amounts = [100000, 50000, 75000, 30000]; // on outcomes 0,1,2,3
+$lifecycle_bets = [];
+$lifecycle_total_tokens = [0, 0, 0, 0];
+
+for ($oi = 0; $oi < 4; $oi++) {
+ $tokens = lmsr_tokens_for_amount($lifecycle_q, $lifecycle_b, $oi, $bet_amounts[$oi]);
+ $cost = lmsr_buy_cost($lifecycle_q, $lifecycle_b, $oi, $tokens);
+ $lifecycle_q[$oi] += $tokens;
+ $lifecycle_total_tokens[$oi] += $tokens;
+ $lifecycle_bets[] = [
+ 'id' => $oi + 100,
+ 'user' => 30 + $oi,
+ 'amount' => $cost,
+ 'weight' => $tokens,
+ 'outcome_index' => $oi,
+ 'time_penalty' => 0,
+ ];
+}
+
+// Check prices shifted
+$final_prices = lmsr_prices($lifecycle_q, $lifecycle_b);
+assert($final_prices[0] > $final_prices[3], 'Outcome 0 (most bet) has highest price');
+assert(abs(array_sum($final_prices) - 1000000) <= 4, 'Prices sum to 1');
+
+// Resolve: outcome 0 wins
+$lifecycle_settle = lmsr_settlement($lifecycle_bets, 0, 5, 3, 15);
+assert($lifecycle_settle['losers_sum'] > 0, 'Has losers');
+assert(count($lifecycle_settle['payouts']) == 1, '1 winner');
+assert($lifecycle_settle['payouts'][0]['payout'] > $lifecycle_settle['payouts'][0]['amount'], 'Winner profits');
+
+step('Scenario 32: Multi lifecycle', [
+ 'q_final' => $lifecycle_q,
+ 'prices' => $final_prices,
+ 'subsidy' => $lifecycle_subsidy,
+ 'settlement' => [
+ 'losers_sum' => $lifecycle_settle['losers_sum'],
+ 'winners_pool' => $lifecycle_settle['winners_pool'],
+ 'winner_payout' => $lifecycle_settle['payouts'][0]['payout'],
+ ],
+]);
+
+
+// ============================================================
+// SCENARIO 33: LP GUARANTEE — subsidy always returned
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 33: LP Principal Guarantee\n";
+echo str_repeat('=', 70) . "\n";
+
+// The subsidy (b * ln(N)) is returned to LP regardless of outcome.
+// Verify that losers' pool covers all payouts and LP subsidy is separate.
+$lp_b = 500000; // b = 500
+$lp_n = 5;
+$lp_subsidy = lmsr_max_loss($lp_b, $lp_n);
+$lp_q = array_fill(0, $lp_n, 0);
+
+// Heavy one-sided betting
+$heavy_tokens = lmsr_tokens_for_amount($lp_q, $lp_b, 0, 500000);
+$heavy_cost = lmsr_buy_cost($lp_q, $lp_b, 0, $heavy_tokens);
+$lp_q[0] += $heavy_tokens;
+
+$small_tokens = lmsr_tokens_for_amount($lp_q, $lp_b, 1, 20000);
+$small_cost = lmsr_buy_cost($lp_q, $lp_b, 1, $small_tokens);
+$lp_q[1] += $small_tokens;
+
+$lp_bets = [
+ ['id'=>200, 'user'=>50, 'amount'=>$heavy_cost, 'weight'=>$heavy_tokens, 'outcome_index'=>0, 'time_penalty'=>0],
+ ['id'=>201, 'user'=>51, 'amount'=>$small_cost, 'weight'=>$small_tokens, 'outcome_index'=>1, 'time_penalty'=>0],
+];
+
+// Case A: outcome 0 wins (heavy side) — losers pool is small
+$settle_a = lmsr_settlement($lp_bets, 0, 5, 3, 10);
+assert($settle_a['losers_sum'] == $small_cost, 'Losers = small bet');
+// LP subsidy is separate from losers pool — always returned
+assert($lp_subsidy > 0, 'LP subsidy is positive');
+
+// Case B: outcome 1 wins (small side) — losers pool is large
+$settle_b = lmsr_settlement($lp_bets, 1, 5, 3, 10);
+assert($settle_b['losers_sum'] == $heavy_cost, 'Losers = heavy bet');
+assert($settle_b['payouts'][0]['payout'] > $settle_b['payouts'][0]['amount'], 'Small side wins big');
+
+step('Scenario 33: LP guarantee', [
+ 'lp_subsidy' => $lp_subsidy,
+ 'case_A_losers' => $settle_a['losers_sum'],
+ 'case_B_losers' => $settle_b['losers_sum'],
+ 'case_B_winner_payout' => $settle_b['payouts'][0]['payout'],
+ 'note' => 'LP subsidy is architecturally separate, always returned',
+]);
+
+
+// ============================================================
+// SCENARIO 34: EDGE CASES
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 34: Edge Cases\n";
+echo str_repeat('=', 70) . "\n";
+
+// 34a: All bets on winner — break even (losers_sum = 0)
+$edge_bets_a = [
+ ['id'=>300, 'user'=>60, 'amount'=>50000, 'weight'=>45000, 'outcome_index'=>0, 'time_penalty'=>0],
+ ['id'=>301, 'user'=>61, 'amount'=>30000, 'weight'=>28000, 'outcome_index'=>0, 'time_penalty'=>0],
+];
+$settle_edge_a = lmsr_settlement($edge_bets_a, 0, 5, 3, 10);
+assert($settle_edge_a['losers_sum'] == 0, 'No losers');
+assert($settle_edge_a['winners_pool'] == 0, 'No profit to distribute');
+assert($settle_edge_a['payouts'][0]['payout'] == 50000, 'Winner gets original back');
+assert($settle_edge_a['payouts'][1]['payout'] == 30000, 'Winner gets original back');
+
+step('Scenario 34a: All bets on winner', [
+ 'losers_sum' => $settle_edge_a['losers_sum'],
+ 'payouts' => $settle_edge_a['payouts'],
+]);
+
+// 34b: No bets on winner — undistributed goes to LP bonus
+$edge_bets_b = [
+ ['id'=>310, 'user'=>62, 'amount'=>50000, 'weight'=>45000, 'outcome_index'=>1, 'time_penalty'=>0],
+ ['id'=>311, 'user'=>63, 'amount'=>30000, 'weight'=>28000, 'outcome_index'=>2, 'time_penalty'=>0],
+];
+$settle_edge_b = lmsr_settlement($edge_bets_b, 0, 5, 3, 10);
+assert($settle_edge_b['losers_sum'] == 80000, 'All bets are losers');
+assert(count($settle_edge_b['payouts']) == 0, 'No winners');
+assert($settle_edge_b['winners_pool'] > 0, 'Undistributed pool → LP bonus');
+
+step('Scenario 34b: No bets on winner (LP bonus)', [
+ 'losers_sum' => $settle_edge_b['losers_sum'],
+ 'winners_pool' => $settle_edge_b['winners_pool'],
+ 'note' => 'Entire winners_pool → LP bonus (undistributed)',
+]);
+
+// 34c: Zero volume market
+$settle_edge_c = lmsr_settlement([], 0, 5, 3, 10);
+assert($settle_edge_c['losers_sum'] == 0);
+assert(count($settle_edge_c['payouts']) == 0);
+
+step('Scenario 34c: Zero volume', ['result' => $settle_edge_c]);
+
+// 34d: Single bettor (wins alone)
+$edge_bets_d = [
+ ['id'=>320, 'user'=>64, 'amount'=>100000, 'weight'=>90000, 'outcome_index'=>0, 'time_penalty'=>0],
+];
+$settle_edge_d = lmsr_settlement($edge_bets_d, 0, 5, 3, 10);
+assert($settle_edge_d['losers_sum'] == 0);
+assert($settle_edge_d['payouts'][0]['payout'] == 100000, 'Sole winner gets original back');
+
+step('Scenario 34d: Single bettor', ['payout' => $settle_edge_d['payouts'][0]['payout']]);
+
+
+// ============================================================
+// SCENARIO 35: POSITION TRANSFERS
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 35: Position Transfers\n";
+echo str_repeat('=', 70) . "\n";
+
+// Simulate transfer logic
+function sim_transfer($bet, $to_user, $transfer_tokens) {
+ if ($transfer_tokens <= 0 || $transfer_tokens > $bet['weight']) {
+ return ['error' => 'Invalid amount'];
+ }
+ if ($to_user == $bet['user']) {
+ return ['error' => 'Cannot transfer to self'];
+ }
+
+ $ratio = $transfer_tokens / $bet['weight'];
+ $transfer_amount = intval($bet['amount'] * $ratio);
+
+ $original = $bet;
+ $original['weight'] -= $transfer_tokens;
+ $original['amount'] -= $transfer_amount;
+ if ($original['weight'] <= 0) $original['status'] = 4; // fully transferred
+
+ $new_bet = [
+ 'user' => $to_user,
+ 'amount' => $transfer_amount,
+ 'weight' => $transfer_tokens,
+ 'outcome_index' => $bet['outcome_index'],
+ 'time_penalty' => $bet['time_penalty'],
+ 'status' => 0,
+ ];
+ return ['original' => $original, 'new_bet' => $new_bet];
+}
+
+// 35a: Full transfer
+$bet_src = ['user'=>70, 'amount'=>100000, 'weight'=>90000, 'outcome_index'=>0, 'time_penalty'=>50000, 'status'=>0];
+$r35a = sim_transfer($bet_src, 71, 90000);
+assert(!isset($r35a['error']), 'Full transfer should succeed');
+assert($r35a['original']['weight'] == 0, 'Original has 0 tokens');
+assert($r35a['original']['status'] == 4, 'Status = fully transferred');
+assert($r35a['new_bet']['weight'] == 90000, 'New bet has all tokens');
+assert($r35a['new_bet']['user'] == 71, 'New bet user = recipient');
+assert($r35a['new_bet']['time_penalty'] == 50000, 'Inherits time penalty');
+
+step('Scenario 35a: Full transfer', $r35a);
+
+// 35b: Partial transfer
+$bet_src2 = ['user'=>70, 'amount'=>100000, 'weight'=>90000, 'outcome_index'=>1, 'time_penalty'=>0, 'status'=>0];
+$r35b = sim_transfer($bet_src2, 72, 30000);
+assert(!isset($r35b['error']));
+assert($r35b['original']['weight'] == 60000, 'Original keeps remainder');
+assert($r35b['new_bet']['weight'] == 30000);
+assert($r35b['new_bet']['amount'] == intval(100000 * 30000 / 90000));
+
+step('Scenario 35b: Partial transfer', $r35b);
+
+// 35c: Transfer to self — should fail
+$r35c = sim_transfer($bet_src2, 70, 10000);
+assert(isset($r35c['error']), 'Transfer to self must fail');
+
+step('Scenario 35c: Transfer to self', ['error' => $r35c['error']]);
+
+// 35d: Transfer more than owned — should fail
+$r35d = sim_transfer($bet_src2, 73, 100000);
+assert(isset($r35d['error']), 'Excessive transfer must fail');
+
+step('Scenario 35d: Transfer excessive amount', ['error' => $r35d['error']]);
+
+// 35e: Transfer then resolve — recipient gets payout
+$transfer_bet_a = ['id'=>400, 'user'=>70, 'amount'=>60000, 'weight'=>50000, 'outcome_index'=>0, 'time_penalty'=>0];
+$transfer_bet_b = ['id'=>401, 'user'=>71, 'amount'=>40000, 'weight'=>40000, 'outcome_index'=>0, 'time_penalty'=>50000]; // transferred portion
+$transfer_bet_c = ['id'=>402, 'user'=>80, 'amount'=>100000, 'weight'=>80000, 'outcome_index'=>1, 'time_penalty'=>0]; // loser
+
+$settle_35e = lmsr_settlement([$transfer_bet_a, $transfer_bet_b, $transfer_bet_c], 0, 5, 3, 10);
+assert($settle_35e['losers_sum'] == 100000, 'Loser bet = 100k');
+assert(count($settle_35e['payouts']) == 2, 'Both original + recipient win');
+$p_original = $settle_35e['payouts'][0];
+$p_recipient = $settle_35e['payouts'][1];
+assert($p_original['payout'] > $p_original['amount'], 'Original wins');
+assert($p_recipient['payout'] > $p_recipient['amount'], 'Recipient wins');
+assert($p_recipient['payout'] < $p_original['payout'], 'Recipient has penalty deduction');
+
+step('Scenario 35e: Transfer then resolve', [
+ 'original_payout' => $p_original['payout'],
+ 'recipient_payout' => $p_recipient['payout'],
+ 'note' => 'Recipient has time penalty, earns less profit',
+]);
+// ============================================================
+// SCENARIO 36: Graduated Early Recall (Mechanism A)
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 36: Graduated Early Recall\n";
+echo str_repeat('=', 70) . "\n";
+
+$recall_pool = make_lazy_pool();
+$recall_user = make_lazy_user(800, 'RecallUser', 50000000);
+lp_deposit($recall_user, $recall_pool, 20000000, $lock_days, $time_now);
+
+// Simulate allocation: 2% of 20000 VIZ = 400 VIZ
+$recall_alloc_pct = 2;
+$recall_vol_threshold_pct = 1;
+$recall_pct_per_step = 10;
+$recall_step_pct = 10;
+$recall_alloc_amount = intval($recall_pool['free_balance'] * $recall_alloc_pct / 100);
+assert($recall_alloc_amount == 400000, 'Allocation = 400 VIZ');
+
+// Simulate allocation
+$recall_pool['free_balance'] -= $recall_alloc_amount;
+$recall_pool['allocated_balance'] += $recall_alloc_amount;
+
+// Market: 30-day duration (2592000 seconds)
+$recall_market_duration = 30 * 86400;
+$recall_market_start = $time_now;
+$recall_market_end = $recall_market_start + $recall_market_duration;
+
+// Simulate 10 recall steps with zero volume
+$current_alloc = $recall_alloc_amount;
+$total_recalled = 0;
+$bets_sum_at_check = 0;
+
+for ($step = 1; $step <= 10; $step++) {
+ $volume_in_period = 0; // zero bets
+ $threshold = intval($current_alloc * $recall_vol_threshold_pct / 100);
+
+ if ($volume_in_period < $threshold) {
+ $recall_amount = intval($current_alloc * $recall_pct_per_step / 100);
+ $current_alloc -= $recall_amount;
+ $total_recalled += $recall_amount;
+ $recall_pool['free_balance'] += $recall_amount;
+ $recall_pool['allocated_balance'] -= $recall_amount;
+ }
+}
+
+step('Scenario 36: Idle market — all 10 steps recalled', [
+ 'original_alloc' => fmt($recall_alloc_amount),
+ 'final_alloc' => fmt($current_alloc),
+ 'total_recalled' => fmt($total_recalled),
+ 'recall_pct' => round($total_recalled / $recall_alloc_amount * 100, 1) . '%',
+]);
+
+// After 10 idle steps: 0.9^10 ≈ 0.349, so ~65% recalled
+$expected_remaining_ratio = pow(1 - $recall_pct_per_step / 100, 10);
+assert(abs($current_alloc / $recall_alloc_amount - $expected_remaining_ratio) < 0.01, 'Remaining should be ~34.9%');
+
+// 36b: Market with volume at step 3 — stops recall
+$recall_pool2 = make_lazy_pool();
+$recall_user2 = make_lazy_user(801, 'RecallUser2', 50000000);
+lp_deposit($recall_user2, $recall_pool2, 20000000, $lock_days, $time_now);
+
+$current_alloc2 = intval($recall_pool2['free_balance'] * $recall_alloc_pct / 100);
+$recall_pool2['free_balance'] -= $current_alloc2;
+$recall_pool2['allocated_balance'] += $current_alloc2;
+
+$total_recalled2 = 0;
+for ($step = 1; $step <= 10; $step++) {
+ $volume = ($step == 3) ? intval($current_alloc2 * 2 / 100) : 0; // 2% volume at step 3
+ $threshold2 = intval($current_alloc2 * $recall_vol_threshold_pct / 100);
+
+ if ($volume < $threshold2) {
+ $recall_amount2 = intval($current_alloc2 * $recall_pct_per_step / 100);
+ $current_alloc2 -= $recall_amount2;
+ $total_recalled2 += $recall_amount2;
+ }
+ // If volume >= threshold, no recall (step passes)
+}
+
+step('Scenario 36b: Volume at step 3 — only steps 1-2 recalled', [
+ 'original_alloc' => fmt(intval($recall_pool2['free_balance'] + $recall_pool2['allocated_balance']) * $recall_alloc_pct / 100),
+ 'remaining_alloc' => fmt($current_alloc2),
+ 'total_recalled' => fmt($total_recalled2),
+ 'note' => 'Steps 1-2 idle (recalled), step 3 has volume (kept), steps 4-10 idle (recalled)'
+]);
+
+// ============================================================
+// SCENARIO 37: Active Market Penalty (Mechanism B)
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 37: Active Market Penalty\n";
+echo str_repeat('=', 70) . "\n";
+
+$active_market_penalty_pct = 5;
+
+// Oracle with 0 active markets → multiplier = 1.0
+$mult_0 = pow(1 - $active_market_penalty_pct / 100, 0);
+assert($mult_0 == 1.0, '0 markets = full allocation');
+
+// Oracle with 1 active market → multiplier = 0.95
+$mult_1 = pow(1 - $active_market_penalty_pct / 100, 1);
+assert(abs($mult_1 - 0.95) < 0.0001, '1 market = 0.95');
+
+// Oracle with 3 active markets → multiplier = 0.857
+$mult_3 = pow(1 - $active_market_penalty_pct / 100, 3);
+assert(abs($mult_3 - 0.857375) < 0.0001, '3 markets = 0.857');
+
+// Oracle with 10 active markets → multiplier = 0.599
+$mult_10 = pow(1 - $active_market_penalty_pct / 100, 10);
+assert($mult_10 < 0.6, '10 markets = <0.6');
+
+$base_alloc = 2000000; // 2000 VIZ
+
+step('Scenario 37: Active market penalty scaling', [
+ 'penalty_pct' => $active_market_penalty_pct . '%',
+ '0_markets' => ['mult' => round($mult_0, 4), 'alloc' => fmt(intval($base_alloc * $mult_0))],
+ '1_market' => ['mult' => round($mult_1, 4), 'alloc' => fmt(intval($base_alloc * $mult_1))],
+ '3_markets' => ['mult' => round($mult_3, 4), 'alloc' => fmt(intval($base_alloc * $mult_3))],
+ '10_markets'=> ['mult' => round($mult_10, 4), 'alloc' => fmt(intval($base_alloc * $mult_10))],
+]);
+
+// ============================================================
+// SCENARIO 38: Fault Penalty Stamps (Mechanism C)
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 38: Fault Penalty Stamps\n";
+echo str_repeat('=', 70) . "\n";
+
+$fault_penalty_pct = 5;
+$fault_expiry_days = 10;
+
+// Simulate fault stamps
+$stamps = [];
+
+// Oracle gets 3 stamps over time
+$stamp_time = $time_now;
+$stamps[] = ['oracle' => 100, 'market' => 1, 'reason' => 'no_contest', 'time' => $stamp_time, 'expires' => $stamp_time + $fault_expiry_days * 86400, 'status' => 0];
+$stamps[] = ['oracle' => 100, 'market' => 2, 'reason' => 'missed_deadline', 'time' => $stamp_time + 100, 'expires' => $stamp_time + $fault_expiry_days * 86400 + 100, 'status' => 0];
+$stamps[] = ['oracle' => 100, 'market' => 3, 'reason' => 'zero_volume', 'time' => $stamp_time + 200, 'expires' => $stamp_time + $fault_expiry_days * 86400 + 200, 'status' => 0];
+
+// Count active stamps
+$active_count = count(array_filter($stamps, function($s) use ($stamp_time) { return $s['status'] == 0 && $s['expires'] > $stamp_time; }));
+assert($active_count == 3, '3 active stamps');
+
+// Calculate multiplier with 3 stamps
+$fault_mult_3 = pow(1 - $fault_penalty_pct / 100, 3);
+assert(abs($fault_mult_3 - 0.857375) < 0.0001, '3 stamps = 0.857');
+
+step('Scenario 38a: Fault stamps reduce allocation', [
+ 'stamps' => 3,
+ 'fault_multiplier' => round($fault_mult_3, 4),
+ 'base_2000_viz' => fmt(intval(2000000 * $fault_mult_3)),
+]);
+
+// 38b: Combined effect (B + C)
+$combined = $mult_3 * $fault_mult_3; // 3 active markets + 3 stamps
+$combined_alloc = intval($base_alloc * $combined);
+
+step('Scenario 38b: Combined B+C penalty', [
+ 'active_market_factor' => round($mult_3, 4),
+ 'fault_factor' => round($fault_mult_3, 4),
+ 'combined' => round($combined, 4),
+ 'base_2000_viz' => fmt($base_alloc),
+ 'final_alloc' => fmt($combined_alloc),
+ 'reduction' => round((1 - $combined) * 100, 1) . '%',
+]);
+
+// 38c: Stamp expiry — after 10 days, stamps expire and penalty disappears
+$expired_count = 0;
+$expiry_time = $stamp_time + $fault_expiry_days * 86400 + 86400; // 1 day after expiry
+foreach ($stamps as &$s) {
+ if ($s['status'] == 0 && $s['expires'] <= $expiry_time) {
+ $s['status'] = 1; // expired
+ $expired_count++;
+ }
+}
+unset($s);
+
+$remaining_active = count(array_filter($stamps, function($s) { return $s['status'] == 0; }));
+assert($expired_count == 3, 'All 3 stamps expired');
+assert($remaining_active == 0, 'No active stamps');
+
+$fault_mult_after = pow(1 - $fault_penalty_pct / 100, $remaining_active);
+assert($fault_mult_after == 1.0, 'After expiry: multiplier = 1.0');
+
+step('Scenario 38c: Stamp auto-healing', [
+ 'expired_stamps' => $expired_count,
+ 'remaining_active' => $remaining_active,
+ 'multiplier_after_expiry' => $fault_mult_after,
+ 'note' => 'After ' . $fault_expiry_days . ' days clean operation, penalty disappears',
+]);
+
+// ============================================================
+// SCENARIO 39: Market Metadata — category/subcategory/tags validation
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 39: Market Metadata — category/subcategory/tags validation\n";
+echo str_repeat('=', 70) . "\n";
+
+require_once __DIR__ . '/../module/market_categories.php';
+
+// 39a: Validate categories
+$cats = get_market_categories();
+assert(is_array($cats), 'get_market_categories returns array');
+assert(count($cats) === 9, 'Expected 9 categories, got ' . count($cats));
+assert(isset($cats['politics']), 'politics category exists');
+assert(isset($cats['crypto']), 'crypto category exists');
+assert(isset($cats['finance']), 'finance category exists');
+assert(isset($cats['tech']), 'tech category exists');
+assert(isset($cats['sports']), 'sports category exists');
+assert(isset($cats['entertainment']), 'entertainment category exists');
+assert(isset($cats['science']), 'science category exists');
+assert(isset($cats['society']), 'society category exists');
+assert(isset($cats['custom']), 'custom category exists');
+
+assert(validate_category('politics') === true, 'validate_category(politics)');
+assert(validate_category('crypto') === true, 'validate_category(crypto)');
+assert(validate_category('nonexistent') === false, 'validate_category(nonexistent)');
+assert(validate_category('') === false, 'validate_category(empty)');
+
+step('Scenario 39a: Category validation', [
+ 'total_categories' => count($cats),
+ 'valid_politics' => validate_category('politics'),
+ 'valid_crypto' => validate_category('crypto'),
+ 'invalid_nonexistent' => validate_category('nonexistent'),
+ 'all_slugs' => array_keys($cats),
+]);
+
+// 39b: Validate subcategories
+assert(validate_subcategory('politics', 'elections') === true, 'validate_subcategory(politics, elections)');
+assert(validate_subcategory('politics', 'geopolitics') === true, 'validate_subcategory(politics, geopolitics)');
+assert(validate_subcategory('politics', 'nba') === false, 'validate_subcategory(politics, nba) should fail');
+assert(validate_subcategory('crypto', 'price_targets') === true, 'validate_subcategory(crypto, price_targets)');
+assert(validate_subcategory('crypto', 'elections') === false, 'validate_subcategory(crypto, elections) should fail');
+assert(validate_subcategory('nonexistent', 'elections') === false, 'validate_subcategory for invalid category');
+assert(validate_subcategory('sports', '') === false, 'validate_subcategory with empty sub');
+
+step('Scenario 39b: Subcategory validation', [
+ 'politics_elections' => validate_subcategory('politics', 'elections'),
+ 'politics_nba' => validate_subcategory('politics', 'nba'),
+ 'crypto_price_targets' => validate_subcategory('crypto', 'price_targets'),
+ 'crypto_elections' => validate_subcategory('crypto', 'elections'),
+ 'nonexistent_cat' => validate_subcategory('nonexistent', 'elections'),
+]);
+
+// 39c: Validate tags
+$valid_tags = validate_tags(['bitcoin', 'ethereum', ' SOLANA ', 'defi']);
+assert(count($valid_tags) === 4, 'validate_tags returns 4 cleaned tags');
+assert($valid_tags[0] === 'bitcoin', 'tag lowercased: bitcoin');
+assert($valid_tags[2] === 'solana', 'tag trimmed+lowered: solana');
+
+$dedup_tags = validate_tags(['btc', 'btc', 'eth', 'BTC']);
+assert(count($dedup_tags) === 2, 'validate_tags deduplicates: ' . count($dedup_tags));
+
+$empty_tags = validate_tags([]);
+assert($empty_tags === [], 'validate_tags empty returns empty');
+
+$jur_tags = validate_tags(['jurisdiction:us', 'jurisdiction-ban:cn', 'bitcoin']);
+assert(count($jur_tags) === 3, 'jurisdiction tags preserved');
+assert($jur_tags[0] === 'jurisdiction:us', 'jurisdiction:us preserved');
+assert($jur_tags[1] === 'jurisdiction-ban:cn', 'jurisdiction-ban:cn preserved');
+
+// Reject invalid chars
+$bad_tags = validate_tags(['good_tag', 'bad tag!', 'hello@world', 'ok-tag']);
+assert(in_array('good_tag', $bad_tags), 'good_tag kept');
+assert(in_array('ok-tag', $bad_tags), 'ok-tag kept');
+// bad tag! -> badtag (space and ! removed)
+assert(in_array('badtag', $bad_tags), 'bad tag! sanitized to badtag');
+
+// Too long tag (>64 chars) rejected
+$long_tag = str_repeat('a', 65);
+$long_result = validate_tags([$long_tag, 'short']);
+assert(count($long_result) === 1, 'tag >64 chars rejected');
+assert($long_result[0] === 'short', 'only short tag kept');
+
+step('Scenario 39c: Tag validation', [
+ 'clean_tags' => $valid_tags,
+ 'dedup_count' => count($dedup_tags),
+ 'empty_tags' => $empty_tags,
+ 'jurisdiction_tags' => $jur_tags,
+ 'sanitized_tags' => $bad_tags,
+ 'long_tag_rejected' => count($long_result) === 1,
+]);
+
+// 39d: get_all_known_tags
+$all_tags = get_all_known_tags();
+assert(count($all_tags) > 50, 'Known tags pool is large: ' . count($all_tags));
+assert(in_array('bitcoin', $all_tags), 'bitcoin in known tags');
+assert(in_array('presidential', $all_tags), 'presidential in known tags');
+assert(in_array('oscars', $all_tags), 'oscars in known tags');
+
+step('Scenario 39d: All known tags', [
+ 'total_known_tags' => count($all_tags),
+ 'has_bitcoin' => in_array('bitcoin', $all_tags),
+ 'has_presidential' => in_array('presidential', $all_tags),
+]);
+
+// ============================================================
+// SCENARIO 40: Metadata JSON — build + i18n structure
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 40: Metadata JSON — build + localization structure\n";
+echo str_repeat('=', 70) . "\n";
+
+// 40a: Build metadata with full localized fields
+$meta_json = build_market_metadata([
+ 'title' => ['en' => 'Will BTC exceed $150k?', 'ru' => 'Пробьёт ли BTC $150k?'],
+ 'description' => ['en' => 'Bitcoin price target for 2026', 'ru' => 'Ценовая цель Bitcoin на 2026'],
+ 'resolution_rules' => ['en' => 'Resolved by CoinGecko price', 'ru' => 'Разрешается по цене CoinGecko'],
+ 'resolution_url' => 'https://coingecko.com/bitcoin',
+ 'image_url' => ['en' => 'https://example.com/btc-en.png', 'ru' => 'https://example.com/btc-ru.png'],
+ 'outcomes' => [
+ '0' => ['en' => 'Yes', 'ru' => 'Да'],
+ '1' => ['en' => 'No', 'ru' => 'Нет'],
+ ],
+ 'tags' => ['bitcoin', 'ath', 'jurisdiction:us', 'jurisdiction-ban:cn'],
+]);
+
+$meta = json_decode($meta_json, true);
+assert($meta !== null, 'Metadata JSON is valid');
+assert($meta['v'] === 1, 'Metadata version is 1');
+assert(!isset($meta['lang']), 'No lang field in metadata (hardcoded in scripts)');
+assert($meta['meta']['title']['en'] === 'Will BTC exceed $150k?', 'EN title stored');
+assert($meta['meta']['title']['ru'] === 'Пробьёт ли BTC $150k?', 'RU title stored');
+assert($meta['meta']['resolution_url'] === 'https://coingecko.com/bitcoin', 'Resolution URL stored');
+assert($meta['meta']['image_url']['en'] === 'https://example.com/btc-en.png', 'EN image_url in meta');
+assert($meta['meta']['image_url']['ru'] === 'https://example.com/btc-ru.png', 'RU image_url in meta');
+assert(!isset($meta['ui']), 'No top-level ui field (image_url moved to meta)');
+assert($meta['outcomes']['0']['en'] === 'Yes', 'Outcome 0 EN');
+assert($meta['outcomes']['1']['ru'] === 'Нет', 'Outcome 1 RU');
+assert(count($meta['tags']) === 4, 'Tags preserved in metadata');
+assert(in_array('jurisdiction:us', $meta['tags']), 'jurisdiction:us in metadata tags');
+assert(in_array('jurisdiction-ban:cn', $meta['tags']), 'jurisdiction-ban:cn in metadata tags');
+
+step('Scenario 40a: Build metadata with full i18n', [
+ 'version' => $meta['v'],
+ 'has_lang_field' => isset($meta['lang']),
+ 'title_en' => $meta['meta']['title']['en'],
+ 'title_ru' => $meta['meta']['title']['ru'],
+ 'resolution_url' => $meta['meta']['resolution_url'],
+ 'image_url_en' => $meta['meta']['image_url']['en'],
+ 'outcomes' => $meta['outcomes'],
+ 'tags_count' => count($meta['tags']),
+]);
+
+// 40b: Build metadata with only EN (no RU) — fallback scenario
+$meta_en_only_json = build_market_metadata([
+ 'title' => ['en' => 'Some question'],
+ 'description' => ['en' => 'Desc'],
+ 'resolution_rules' => ['en' => 'Rules'],
+ 'resolution_url' => '',
+ 'image_url' => [],
+ 'outcomes' => ['0' => ['en' => 'Yes'], '1' => ['en' => 'No']],
+ 'tags' => ['test'],
+]);
+$meta_en = json_decode($meta_en_only_json, true);
+assert(!isset($meta_en['meta']['title']['ru']), 'No RU title when only EN provided');
+assert($meta_en['meta']['title']['en'] === 'Some question', 'EN title fallback works');
+
+step('Scenario 40b: EN-only metadata (localization fallback)', [
+ 'has_en' => isset($meta_en['meta']['title']['en']),
+ 'has_ru' => isset($meta_en['meta']['title']['ru']),
+ 'en_title' => $meta_en['meta']['title']['en'],
+]);
+
+// 40c: Build metadata with empty fields — minimal market
+$meta_minimal_json = build_market_metadata([
+ 'title' => [],
+ 'description' => [],
+ 'resolution_rules' => [],
+ 'resolution_url' => '',
+ 'image_url' => [],
+ 'outcomes' => [],
+ 'tags' => [],
+]);
+$meta_minimal = json_decode($meta_minimal_json, true);
+assert($meta_minimal['v'] === 1, 'Minimal metadata has version');
+assert($meta_minimal['meta']['title'] === [], 'Empty title array');
+assert($meta_minimal['tags'] === [], 'Empty tags');
+
+step('Scenario 40c: Minimal metadata (empty fields)', [
+ 'version' => $meta_minimal['v'],
+ 'title_empty' => empty($meta_minimal['meta']['title']),
+ 'tags_empty' => empty($meta_minimal['tags']),
+]);
+
+// ============================================================
+// SCENARIO 41: Localization fallback resolver
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 41: Localization fallback resolver (simulated resolve_i18n)\n";
+echo str_repeat('=', 70) . "\n";
+
+// Simulate the JS resolve_i18n() function in PHP
+function resolve_i18n_php($obj, $lang, $fallback = 'en') {
+ if (!$obj) return '';
+ if (is_string($obj)) return $obj;
+ return $obj[$lang] ?? $obj[$fallback] ?? (count($obj) > 0 ? reset($obj) : '');
+}
+
+// 41a: Request RU when both EN and RU exist
+$title_both = ['en' => 'Will BTC moon?', 'ru' => 'Полетит ли BTC?'];
+assert(resolve_i18n_php($title_both, 'ru') === 'Полетит ли BTC?', 'RU resolved when available');
+assert(resolve_i18n_php($title_both, 'en') === 'Will BTC moon?', 'EN resolved when requested');
+
+// 41b: Request RU when only EN exists (fallback)
+assert(resolve_i18n_php(['en' => 'English only'], 'ru') === 'English only', 'Fallback to EN when RU missing');
+
+// 41c: Request unsupported lang (de) -> fallback to en
+assert(resolve_i18n_php($title_both, 'de') === 'Will BTC moon?', 'Unsupported lang falls back to EN');
+
+// 41d: Plain string (legacy) just returned as-is
+assert(resolve_i18n_php('Legacy text', 'ru') === 'Legacy text', 'Plain string returned as-is');
+
+// 41e: Empty/null
+assert(resolve_i18n_php(null, 'en') === '', 'Null returns empty');
+assert(resolve_i18n_php([], 'en') === '', 'Empty array returns empty');
+
+// 41f: Only RU provided, request EN -> fallback to first value
+$ru_only = ['ru' => 'Только русский'];
+assert(resolve_i18n_php($ru_only, 'en') === 'Только русский', 'Falls back to first available lang');
+
+step('Scenario 41: Localization fallback', [
+ 'both_ru' => resolve_i18n_php($title_both, 'ru'),
+ 'both_en' => resolve_i18n_php($title_both, 'en'),
+ 'en_only_req_ru' => resolve_i18n_php(['en' => 'English only'], 'ru'),
+ 'unsupported_de' => resolve_i18n_php($title_both, 'de'),
+ 'legacy_string' => resolve_i18n_php('Legacy text', 'ru'),
+ 'null_input' => resolve_i18n_php(null, 'en'),
+ 'ru_only_req_en' => resolve_i18n_php($ru_only, 'en'),
+]);
+
+// ============================================================
+// SCENARIO 42: Jurisdiction filtering (client-side simulation)
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 42: Jurisdiction filtering — banned/allowed tag logic\n";
+echo str_repeat('=', 70) . "\n";
+
+// Simulate the JS is_market_jurisdiction_banned() function
+function is_jurisdiction_banned_php($market_tags, $user_jurisdiction) {
+ if (!$user_jurisdiction || !$market_tags || count($market_tags) === 0) return false;
+ $ban_tag = 'jurisdiction-ban:' . strtoupper($user_jurisdiction);
+ foreach ($market_tags as $tag) {
+ if ($tag === $ban_tag) return true;
+ }
+ return false;
+}
+
+// Market with jurisdiction-ban:CN and jurisdiction:US
+$tags_market_a = ['bitcoin', 'ath', 'jurisdiction:US', 'jurisdiction-ban:CN'];
+
+// 42a: Chinese user -> banned
+assert(is_jurisdiction_banned_php($tags_market_a, 'CN') === true, 'CN user banned from jurisdiction-ban:CN market');
+
+// 42b: US user -> NOT banned
+assert(is_jurisdiction_banned_php($tags_market_a, 'US') === false, 'US user NOT banned (jurisdiction:US is informational)');
+
+// 42c: German user -> NOT banned
+assert(is_jurisdiction_banned_php($tags_market_a, 'DE') === false, 'DE user NOT banned (no ban tag)');
+
+// 42d: No jurisdiction set -> never banned
+assert(is_jurisdiction_banned_php($tags_market_a, '') === false, 'Empty jurisdiction -> not banned');
+assert(is_jurisdiction_banned_php($tags_market_a, null) === false, 'Null jurisdiction -> not banned');
+
+// 42e: Market with no tags -> never banned
+assert(is_jurisdiction_banned_php([], 'CN') === false, 'No tags -> not banned');
+
+// 42f: Market with multiple ban tags
+$tags_multi_ban = ['bitcoin', 'jurisdiction-ban:CN', 'jurisdiction-ban:KP', 'jurisdiction-ban:IR'];
+assert(is_jurisdiction_banned_php($tags_multi_ban, 'CN') === true, 'CN banned in multi-ban');
+assert(is_jurisdiction_banned_php($tags_multi_ban, 'KP') === true, 'KP banned in multi-ban');
+assert(is_jurisdiction_banned_php($tags_multi_ban, 'IR') === true, 'IR banned in multi-ban');
+assert(is_jurisdiction_banned_php($tags_multi_ban, 'US') === false, 'US not banned in multi-ban');
+
+// 42g: Case sensitivity — tags stored lowercase, user jurisdiction uppercase
+$tags_lowercase = ['jurisdiction-ban:cn'];
+assert(is_jurisdiction_banned_php($tags_lowercase, 'CN') === false, 'Case mismatch: lowercase tag vs uppercase jurisdiction');
+// Note: in real system, tags are stored lowercase via validate_tags(), but ban check uses uppercase.
+// The API should normalize. Let's verify with proper uppercase tags:
+$tags_uppercase = ['jurisdiction-ban:CN'];
+assert(is_jurisdiction_banned_php($tags_uppercase, 'CN') === true, 'Proper case match');
+
+step('Scenario 42: Jurisdiction filtering', [
+ 'cn_banned' => is_jurisdiction_banned_php($tags_market_a, 'CN'),
+ 'us_not_banned' => !is_jurisdiction_banned_php($tags_market_a, 'US'),
+ 'de_not_banned' => !is_jurisdiction_banned_php($tags_market_a, 'DE'),
+ 'empty_not_banned' => !is_jurisdiction_banned_php($tags_market_a, ''),
+ 'no_tags_not_banned' => !is_jurisdiction_banned_php([], 'CN'),
+ 'multi_ban_cn' => is_jurisdiction_banned_php($tags_multi_ban, 'CN'),
+ 'multi_ban_kp' => is_jurisdiction_banned_php($tags_multi_ban, 'KP'),
+ 'multi_ban_us_ok' => !is_jurisdiction_banned_php($tags_multi_ban, 'US'),
+]);
+
+// ============================================================
+// SCENARIO 43: i18n category structure integrity
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 43: Category structure — i18n completeness\n";
+echo str_repeat('=', 70) . "\n";
+
+$required_langs = ['en', 'ru'];
+$structure_errors = [];
+
+foreach ($cats as $slug => $cat) {
+ // Each category must have icon, i18n, subcategories, tags
+ if (empty($cat['icon'])) $structure_errors[] = "$slug: missing icon";
+ if (!isset($cat['i18n']) || !is_array($cat['i18n'])) $structure_errors[] = "$slug: missing i18n";
+ foreach ($required_langs as $lang) {
+ if (empty($cat['i18n'][$lang])) $structure_errors[] = "$slug: missing i18n.$lang";
+ }
+ if (!isset($cat['subcategories']) || !is_array($cat['subcategories'])) {
+ $structure_errors[] = "$slug: missing subcategories";
+ } else {
+ foreach ($cat['subcategories'] as $sub_slug => $sub_i18n) {
+ foreach ($required_langs as $lang) {
+ if (empty($sub_i18n[$lang])) $structure_errors[] = "$slug/$sub_slug: missing i18n.$lang";
+ }
+ }
+ }
+ if (!isset($cat['tags']) || !is_array($cat['tags'])) $structure_errors[] = "$slug: missing tags";
+}
+
+assert(count($structure_errors) === 0, 'Category structure errors: ' . implode('; ', $structure_errors));
+
+// Count totals
+$total_subcategories = 0;
+$total_tags = 0;
+foreach ($cats as $cat) {
+ $total_subcategories += count($cat['subcategories']);
+ $total_tags += count($cat['tags']);
+}
+
+step('Scenario 43: Category structure integrity', [
+ 'errors' => $structure_errors,
+ 'total_categories' => count($cats),
+ 'total_subcategories' => $total_subcategories,
+ 'total_tags' => $total_tags,
+ 'all_have_en_ru' => count($structure_errors) === 0,
+]);
+
+// ============================================================
+// SCENARIO 44: Market creation with metadata (simulated API flow)
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 44: Market creation with metadata — simulated API flow\n";
+echo str_repeat('=', 70) . "\n";
+
+// Simulate the full create-market flow with metadata fields
+$input_category = 'crypto';
+$input_subcategory = 'price_targets';
+$input_tags_raw = ['bitcoin', 'ath', 'jurisdiction:US', 'jurisdiction-ban:CN', ' INVALID TAG! ', ''];
+$input_default_lang = 'en';
+$input_title_en = 'Will BTC exceed $150k by end of 2026?';
+$input_title_ru = 'Пробьёт ли BTC $150k к концу 2026?';
+$input_desc_en = 'Market resolves YES if BTC price...';
+$input_rules_en = 'CoinGecko BTC/USD on Dec 31, 2026 at 23:59 UTC';
+$input_resolution_url = 'https://coingecko.com/bitcoin';
+$input_outcome_a_en = 'Yes';
+$input_outcome_a_ru = 'Да';
+$input_outcome_b_en = 'No';
+$input_outcome_b_ru = 'Нет';
+
+// Step 1: Validate category
+assert(validate_category($input_category), 'Category valid');
+assert(validate_subcategory($input_category, $input_subcategory), 'Subcategory valid');
+
+// Step 2: Validate + sanitize tags
+$clean_tags = validate_tags($input_tags_raw);
+assert(in_array('bitcoin', $clean_tags), 'bitcoin tag kept');
+assert(in_array('ath', $clean_tags), 'ath tag kept');
+assert(in_array('jurisdiction:us', $clean_tags), 'jurisdiction tag kept (lowercased)');
+assert(in_array('jurisdiction-ban:cn', $clean_tags), 'ban tag kept (lowercased)');
+assert(!in_array('', $clean_tags), 'empty tag removed');
+
+// Step 3: Build metadata
+$meta_title = ['en' => htmlspecialchars($input_title_en)];
+if ($input_title_ru) $meta_title['ru'] = htmlspecialchars($input_title_ru);
+$meta_desc = ['en' => htmlspecialchars($input_desc_en)];
+$meta_rules = ['en' => htmlspecialchars($input_rules_en)];
+$meta_outcomes = [
+ '0' => ['en' => htmlspecialchars($input_outcome_a_en), 'ru' => htmlspecialchars($input_outcome_a_ru)],
+ '1' => ['en' => htmlspecialchars($input_outcome_b_en), 'ru' => htmlspecialchars($input_outcome_b_ru)],
+];
+
+$metadata_json = build_market_metadata([
+ 'title' => $meta_title,
+ 'description' => $meta_desc,
+ 'resolution_rules' => $meta_rules,
+ 'resolution_url' => $input_resolution_url,
+ 'image_url' => [],
+ 'outcomes' => $meta_outcomes,
+ 'tags' => $clean_tags,
+]);
+
+$built_meta = json_decode($metadata_json, true);
+assert($built_meta !== null, 'Built metadata is valid JSON');
+assert($built_meta['v'] === 1, 'Version 1');
+assert(!isset($built_meta['lang']), 'No lang field in metadata');
+assert($built_meta['meta']['title']['en'] === htmlspecialchars($input_title_en), 'EN title in metadata');
+assert($built_meta['meta']['title']['ru'] === htmlspecialchars($input_title_ru), 'RU title in metadata');
+
+// Legacy field fallback: q = title_en, a = outcome_a_en, b = outcome_b_en
+$legacy_q = $meta_title[$input_default_lang] ?? ($meta_title['en'] ?? '');
+$legacy_a = $meta_outcomes['0'][$input_default_lang] ?? ($meta_outcomes['0']['en'] ?? '');
+$legacy_b = $meta_outcomes['1'][$input_default_lang] ?? ($meta_outcomes['1']['en'] ?? '');
+assert($legacy_q === htmlspecialchars($input_title_en), 'Legacy q populated from title_en');
+assert($legacy_a === htmlspecialchars($input_outcome_a_en), 'Legacy a populated from outcome_a_en');
+assert($legacy_b === htmlspecialchars($input_outcome_b_en), 'Legacy b populated from outcome_b_en');
+
+step('Scenario 44: Create market with metadata', [
+ 'category' => $input_category,
+ 'subcategory' => $input_subcategory,
+ 'clean_tags' => $clean_tags,
+ 'metadata_json_length' => strlen($metadata_json),
+ 'legacy_q' => $legacy_q,
+ 'legacy_a' => $legacy_a,
+ 'legacy_b' => $legacy_b,
+ 'metadata_version' => $built_meta['v'],
+ 'outcome_0_ru' => $built_meta['outcomes']['0']['ru'],
+]);
+
+// ============================================================
+// SCENARIO 45: i18n JSON files — key consistency check
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 45: i18n JSON files — key consistency between en.json and ru.json\n";
+echo str_repeat('=', 70) . "\n";
+
+$en_json_path = __DIR__ . '/../i18n/en.json';
+$ru_json_path = __DIR__ . '/../i18n/ru.json';
+
+$en_data = json_decode(file_get_contents($en_json_path), true);
+$ru_data = json_decode(file_get_contents($ru_json_path), true);
+
+assert($en_data !== null, 'en.json is valid JSON');
+assert($ru_data !== null, 'ru.json is valid JSON');
+
+$en_keys = array_keys($en_data);
+$ru_keys = array_keys($ru_data);
+
+$missing_in_ru = array_diff($en_keys, $ru_keys);
+$missing_in_en = array_diff($ru_keys, $en_keys);
+
+assert(count($missing_in_ru) === 0, 'Keys missing in ru.json: ' . implode(', ', $missing_in_ru));
+assert(count($missing_in_en) === 0, 'Keys missing in en.json: ' . implode(', ', $missing_in_en));
+
+// Check critical keys exist
+$critical_keys = [
+ 'market.category_label', 'market.subcategory_label', 'market.tags_label',
+ 'market.default_lang_label', 'market.add_language',
+ 'market.jurisdiction_relevant_label', 'market.jurisdiction_banned_label',
+ 'jurisdiction.warning', 'jurisdiction.proceed', 'jurisdiction.go_back',
+ 'filter.all_categories', 'filter.all_subcategories', 'filter.hot_tags',
+ 'nav.markets', 'nav.create', 'nav.balance', 'nav.profile',
+ 'profile.jurisdiction_label',
+];
+$missing_critical = [];
+foreach ($critical_keys as $key) {
+ if (!isset($en_data[$key])) $missing_critical[] = "en:$key";
+ if (!isset($ru_data[$key])) $missing_critical[] = "ru:$key";
+}
+assert(count($missing_critical) === 0, 'Missing critical i18n keys: ' . implode(', ', $missing_critical));
+
+// Check interpolation placeholders consistency
+foreach ($en_keys as $key) {
+ if (isset($ru_data[$key])) {
+ preg_match_all('/%%([A-Z_]+)%%/', $en_data[$key], $en_vars);
+ preg_match_all('/%%([A-Z_]+)%%/', $ru_data[$key], $ru_vars);
+ $en_placeholders = $en_vars[1];
+ $ru_placeholders = $ru_vars[1];
+ sort($en_placeholders);
+ sort($ru_placeholders);
+ if ($en_placeholders !== $ru_placeholders) {
+ // Only warn, don't fail — some keys may intentionally differ
+ echo " WARNING: Placeholder mismatch for key '$key': EN=" . implode(',', $en_placeholders) . " RU=" . implode(',', $ru_placeholders) . "\n";
+ }
+ }
+}
+
+step('Scenario 45: i18n key consistency', [
+ 'en_keys_count' => count($en_keys),
+ 'ru_keys_count' => count($ru_keys),
+ 'missing_in_ru' => $missing_in_ru,
+ 'missing_in_en' => $missing_in_en,
+ 'critical_keys_ok' => count($missing_critical) === 0,
+]);
+
+// ============================================================
+// LEVERAGE (BOOST) HELPER FUNCTIONS
+// ============================================================
+
+/**
+ * Compute cancel_value for a CPMM leveraged position (simulation)
+ * cancel_value = new_reserve - old_reserve after selling tokens back
+ */
+function leverage_cancel_value_cpmm($market, $tokens, $outcome_index) {
+ $ra = intval($market['reserve_a']);
+ $rb = intval($market['reserve_b']);
+ $k = intval($market['k']);
+ if ($outcome_index == 0) {
+ // Tokens on A: sell back to pool
+ $new_ra = $ra + $tokens;
+ $new_rb = intval($k / $new_ra);
+ return $rb - $new_rb;
+ } else {
+ // Tokens on B: sell back to pool
+ $new_rb = $rb + $tokens;
+ $new_ra = intval($k / $new_rb);
+ return $ra - $new_ra;
+ }
+}
+
+/**
+ * Compute worst-case cancel_value after an opposing bet of M_max on the OTHER side
+ * For CPMM binary: opposing bet moves the price against the leveraged position
+ */
+function leverage_cancel_value_worst_cpmm($market, $collateral, $loan, $outcome_index, $M_max) {
+ // Simulate opposing bet on the other outcome
+ $ra = intval($market['reserve_a']);
+ $rb = intval($market['reserve_b']);
+ $k = intval($market['k']);
+ $total = $collateral + $loan;
+
+ if ($outcome_index == 0) {
+ // Position on A, opposing bet on B
+ $new_rb = $rb + $M_max;
+ $new_ra = intval($k / $new_rb);
+ } else {
+ // Position on B, opposing bet on A
+ $new_ra = $ra + $M_max;
+ $new_rb = intval($k / $new_ra);
+ }
+
+ // Now compute cancel_value at the new reserves
+ $tokens = intval($total); // approximate tokens ≈ bet amount (for deep markets)
+ if ($outcome_index == 0) {
+ $sell_ra = $new_ra + $tokens;
+ $sell_rb = intval($k / $sell_ra);
+ $cancel = $new_rb - $sell_rb;
+ } else {
+ $sell_rb = $new_rb + $tokens;
+ $sell_ra = intval($k / $sell_rb);
+ $cancel = $new_ra - $sell_ra;
+ }
+ return max(0, $cancel);
+}
+
+/**
+ * Compute liquidation threshold = loan + pool_profit
+ */
+function leverage_liquidation_threshold($loan, $R_pct) {
+ return intval($loan * (1 + $R_pct / 100));
+}
+
+/**
+ * Compute max leverage via binary search (Constraint 2 simulation)
+ * Finds max loan L such that cancel_value_worst >= threshold * (1 + S%)
+ */
+function leverage_compute_max_leverage_cpmm($market, $collateral, $outcome_index, $R_pct, $S_pct, $M_max) {
+ // Binary search for max loan
+ $lo = 0;
+ $hi = $collateral * 10; // max 10x leverage for search
+ $best_loan = 0;
+
+ for ($i = 0; $i < 50; $i++) {
+ $mid = intval(($lo + $hi) / 2);
+ if ($mid <= $lo) break;
+
+ $threshold = leverage_liquidation_threshold($mid, $R_pct);
+ $threshold_safe = intval($threshold * (1 + $S_pct / 100));
+ $worst_cv = leverage_cancel_value_worst_cpmm($market, $collateral, $mid, $outcome_index, $M_max);
+
+ if ($worst_cv >= $threshold_safe) {
+ $best_loan = $mid;
+ $lo = $mid;
+ } else {
+ $hi = $mid;
+ }
+ }
+
+ $total_bet = $collateral + $best_loan;
+ $leverage_x = $total_bet / $collateral;
+ return [
+ 'loan' => $best_loan,
+ 'total_bet' => $total_bet,
+ 'leverage_x' => round($leverage_x, 4),
+ 'threshold' => leverage_liquidation_threshold($best_loan, $R_pct),
+ ];
+}
+
+// ============================================================
+// SCENARIO 46: Leverage cancel_value calculation (CPMM)
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 46: Leverage cancel_value calculation (CPMM)\n";
+echo str_repeat('=', 70) . "\n";
+
+// Create a market with deep liquidity
+$lev_market = [
+ 'id' => 100, 'reserve_a' => 5000000, 'reserve_b' => 5000000,
+ 'k' => 5000000 * 5000000, 'liquidity_sum' => 10000000,
+ 'bets_sum' => 0, 'a_bets_sum' => 0, 'b_bets_sum' => 0,
+ 'status' => 1, 'market_type' => 0,
+];
+
+// Test 1: cancel_value for various token amounts
+$cv_1000 = leverage_cancel_value_cpmm($lev_market, 1000000, 0);
+$cv_1000_b = leverage_cancel_value_cpmm($lev_market, 1000000, 1);
+echo "Cancel value for 1000 VIZ tokens on A: " . ($cv_1000/1000) . " VIZ\n";
+echo "Cancel value for 1000 VIZ tokens on B: " . ($cv_1000_b/1000) . " VIZ\n";
+
+$test46_ok = ($cv_1000 > 0 && $cv_1000_b > 0);
+echo "Test 46.1 (cancel_value > 0): " . ($test46_ok ? 'PASS' : 'FAIL') . "\n";
+
+// Symmetric market should give symmetric cancel values
+$test46_sym = ($cv_1000 == $cv_1000_b);
+echo "Test 46.2 (symmetric cancel_value): " . ($test46_sym ? 'PASS' : 'FAIL') . "\n";
+
+// Test 2: cancel_value decreases with larger token amounts (more slippage)
+$cv_5000 = leverage_cancel_value_cpmm($lev_market, 5000000, 0);
+echo "Cancel value for 5000 VIZ tokens on A: " . ($cv_5000/1000) . " VIZ\n";
+$test46_slippage = ($cv_5000 < 5 * $cv_1000); // less than 5x due to slippage
+echo "Test 46.3 (slippage with larger amount): " . ($test46_slippage ? 'PASS' : 'FAIL') . "\n";
+
+step('Scenario 46: CPMM cancel_value', [
+ 'cv_1000_A' => $cv_1000,
+ 'cv_1000_B' => $cv_1000_b,
+ 'cv_5000_A' => $cv_5000,
+ 'symmetric' => $test46_sym,
+ 'slippage' => $test46_slippage,
+]);
+
+// ============================================================
+// SCENARIO 47: Liquidation threshold & safety margin
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 47: Liquidation threshold & safety margin\n";
+echo str_repeat('=', 70) . "\n";
+
+$R_pct = 10; // 10% pool profit
+$S_pct = 1; // 1% safety margin
+$loan_1000 = 1000000; // 1000 VIZ loan
+
+$threshold = leverage_liquidation_threshold($loan_1000, $R_pct);
+$threshold_safe = intval($threshold * (1 + $S_pct / 100));
+
+echo "Loan: " . ($loan_1000/1000) . " VIZ\n";
+echo "Pool profit ({$R_pct}%): " . ($loan_1000 * $R_pct / 100 / 1000) . " VIZ\n";
+echo "Liquidation threshold: " . ($threshold/1000) . " VIZ\n";
+echo "Threshold with {$S_pct}% safety: " . ($threshold_safe/1000) . " VIZ\n";
+
+$test47_threshold = ($threshold == 1100000); // 1000 * 1.10 = 1100 VIZ
+$test47_safe = ($threshold_safe == 1111000); // 1100 * 1.01 = 1111 VIZ
+
+echo "Test 47.1 (threshold = loan * 1.10): " . ($test47_threshold ? 'PASS' : 'FAIL') . "\n";
+echo "Test 47.2 (safe threshold = 1111 VIZ): " . ($test47_safe ? 'PASS' : 'FAIL') . "\n";
+
+step('Scenario 47: Threshold & safety', [
+ 'threshold' => $threshold,
+ 'threshold_safe' => $threshold_safe,
+ 'test_threshold' => $test47_threshold,
+ 'test_safe' => $test47_safe,
+]);
+
+// ============================================================
+// SCENARIO 48: Max leverage binary search (CPMM)
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 48: Max leverage binary search (CPMM)\n";
+echo str_repeat('=', 70) . "\n";
+
+// Deep market with 10000 VIZ liquidity (symmetric)
+$deep_market = [
+ 'id' => 200, 'reserve_a' => 5000000, 'reserve_b' => 5000000,
+ 'k' => 5000000 * 5000000, 'liquidity_sum' => 10000000,
+ 'bets_sum' => 0, 'a_bets_sum' => 0, 'b_bets_sum' => 0,
+ 'status' => 1, 'market_type' => 0,
+];
+
+$collateral_360 = 360000; // 360 VIZ collateral
+$M_max_opposing = 5000000; // Max opposing bet ≈ all of reserve
+
+$max_lev = leverage_compute_max_leverage_cpmm(
+ $deep_market, $collateral_360, 0, $R_pct, $S_pct, $M_max_opposing
+);
+
+echo "Collateral: " . ($collateral_360/1000) . " VIZ\n";
+echo "Max loan: " . ($max_lev['loan']/1000) . " VIZ\n";
+echo "Max total bet: " . ($max_lev['total_bet']/1000) . " VIZ\n";
+echo "Max leverage: " . $max_lev['leverage_x'] . "x\n";
+
+$test48_positive = ($max_lev['loan'] > 0);
+$test48_leverage = ($max_lev['leverage_x'] > 1.0);
+echo "Test 48.1 (loan > 0): " . ($test48_positive ? 'PASS' : 'FAIL') . "\n";
+echo "Test 48.2 (leverage > 1x): " . ($test48_leverage ? 'PASS' : 'FAIL') . "\n";
+
+// Verify: at max leverage, worst-case cancel_value >= safe threshold
+$worst_at_max = leverage_cancel_value_worst_cpmm(
+ $deep_market, $collateral_360, $max_lev['loan'], 0, $M_max_opposing
+);
+$threshold_at_max = intval($max_lev['threshold'] * (1 + $S_pct / 100));
+$test48_safe = ($worst_at_max >= $threshold_at_max - 1000); // within 1 VIZ tolerance
+echo "Test 48.3 (worst CV >= safe threshold at max): " . ($test48_safe ? 'PASS' : 'FAIL') . "\n";
+echo " Worst CV: " . ($worst_at_max/1000) . " VIZ, Safe threshold: " . ($threshold_at_max/1000) . " VIZ\n";
+
+step('Scenario 48: Max leverage search', [
+ 'max_leverage' => $max_lev,
+ 'worst_at_max' => $worst_at_max,
+ 'safe_threshold' => $threshold_at_max,
+ 'test_positive' => $test48_positive,
+ 'test_leverage' => $test48_leverage,
+ 'test_safe' => $test48_safe,
+]);
+
+// ============================================================
+// SCENARIO 49: Leveraged position lifecycle (open → close)
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 49: Leveraged position lifecycle (open -> close)\n";
+echo str_repeat('=', 70) . "\n";
+
+// Open a 2x position on A with 360 VIZ collateral
+$collateral_open = 360000; // 360 VIZ
+$loan_open = 360000; // 360 VIZ (2x leverage)
+$total_bet_open = $collateral_open + $loan_open; // 720 VIZ
+$R_pct_open = 10;
+$pool_profit_open = intval($loan_open * $R_pct_open / 100); // 36 VIZ
+$threshold_open = leverage_liquidation_threshold($loan_open, $R_pct_open); // 396 VIZ
+
+// Place the bet on A — gets tokens from CPMM
+$tokens_received = intval($total_bet_open); // approximate tokens
+$actual_cv = leverage_cancel_value_cpmm($deep_market, $tokens_received, 0);
+
+echo "Opened 2x position: collateral=" . ($collateral_open/1000) . " VIZ, loan=" . ($loan_open/1000) . " VIZ\n";
+echo "Total bet: " . ($total_bet_open/1000) . " VIZ\n";
+echo "Liquidation threshold: " . ($threshold_open/1000) . " VIZ\n";
+echo "Current cancel_value: " . ($actual_cv/1000) . " VIZ\n";
+
+// Verify position is safe at open
+echo "Position is safe (cancel_value > threshold): " . ($actual_cv > $threshold_open ? 'YES' : 'NO') . "\n";
+
+// Voluntary close: cancel_value > threshold
+if ($actual_cv > $threshold_open) {
+ $pool_receives = $threshold_open; // loan + pool profit
+ $bettor_receives = $actual_cv - $threshold_open;
+ echo "Voluntary close: pool receives " . ($pool_receives/1000) . " VIZ, bettor receives " . ($bettor_receives/1000) . " VIZ\n";
+ $test49_close = ($bettor_receives >= 0);
+} else {
+ $test49_close = false;
+}
+
+echo "Test 49.1 (voluntary close OK): " . ($test49_close ? 'PASS' : 'FAIL') . "\n";
+
+// Check: at 2x leverage with no price movement, bettor gets collateral back minus pool profit
+$test49_profit = ($pool_receives == $threshold_open);
+echo "Test 49.2 (pool receives threshold): " . ($test49_profit ? 'PASS' : 'FAIL') . "\n";
+
+step('Scenario 49: Lifecycle open->close', [
+ 'collateral' => $collateral_open,
+ 'loan' => $loan_open,
+ 'total_bet' => $total_bet_open,
+ 'threshold' => $threshold_open,
+ 'cancel_value' => $actual_cv,
+ 'pool_receives' => $pool_receives ?? 0,
+ 'bettor_receives' => $bettor_receives ?? 0,
+ 'test_close' => $test49_close,
+ 'test_profit' => $test49_profit,
+]);
+
+// ============================================================
+// SCENARIO 50: Cancel-bet liquidation waterfall (Case B)
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 50: Cancel-bet liquidation waterfall (Case B)\n";
+echo str_repeat('=', 70) . "\n";
+
+// Setup: market with 5000 VIZ per side
+$liq_market = [
+ 'id' => 300, 'reserve_a' => 2500000, 'reserve_b' => 2500000,
+ 'k' => 2500000 * 2500000, 'liquidity_sum' => 5000000,
+ 'bets_sum' => 0, 'a_bets_sum' => 0, 'b_bets_sum' => 0,
+ 'status' => 1, 'market_type' => 0,
+];
+
+// Bettor opens 3x boosted position on A: collateral=100 VIZ, loan=200 VIZ
+$cb_collateral = 100000; // 100 VIZ
+$cb_loan = 200000; // 200 VIZ
+$cb_total = 300000; // 300 VIZ
+$cb_R = 10;
+$cb_threshold = leverage_liquidation_threshold($cb_loan, $cb_R); // 220 VIZ
+$cb_tokens = $cb_total; // approximate
+echo "Opened 3x on A: collateral=" . ($cb_collateral/1000) . ", loan=" . ($cb_loan/1000) . ", threshold=" . ($cb_threshold/1000) . " VIZ\n";
+
+// Another user cancels a bet on A (same outcome as leveraged position)
+// Cancel-bet executes FIRST (Case B)
+$cancel_amount = 200000; // 200 VIZ cancelled on A
+// After cancel on A, reserve_a increases, reserve_b decreases
+$new_ra_after_cancel = $liq_market['reserve_a'] + $cancel_amount; // tokens returned to pool
+$new_rb_after_cancel = intval($liq_market['k'] / $new_ra_after_cancel);
+
+echo "After cancel-bet on A: reserve_a=" . ($new_ra_after_cancel/1000) . ", reserve_b=" . ($new_rb_after_cancel/1000) . " VIZ\n";
+
+// Now re-evaluate the leveraged position's cancel_value at new reserves
+$market_after_cancel = $liq_market;
+$market_after_cancel['reserve_a'] = $new_ra_after_cancel;
+$market_after_cancel['reserve_b'] = $new_rb_after_cancel;
+
+$cv_after_cancel = leverage_cancel_value_cpmm($market_after_cancel, $cb_tokens, 0);
+echo "Cancel_value after cancel-bet: " . ($cv_after_cancel/1000) . " VIZ\n";
+echo "Liquidation threshold: " . ($cb_threshold/1000) . " VIZ\n";
+
+// Check if position needs liquidation
+$needs_liquidation = ($cv_after_cancel < $cb_threshold);
+echo "Needs liquidation: " . ($needs_liquidation ? 'YES' : 'NO') . "\n";
+
+if ($needs_liquidation) {
+ // Pool receives min(cancel_value, threshold) = cancel_value (since CV < threshold)
+ // Bad debt = threshold - cancel_value
+ $bad_debt = $cb_threshold - $cv_after_cancel;
+ echo "LIQUIDATED: pool would receive " . ($cv_after_cancel/1000) . " VIZ\n";
+ echo "Bad debt (pool shortfall): " . ($bad_debt/1000) . " VIZ\n";
+ $test50_bad_debt = ($bad_debt > 0);
+ echo "Test 50.1 (bad debt exists when CV < threshold): " . ($test50_bad_debt ? 'PASS' : 'FAIL') . "\n";
+
+ // Bettor receives nothing (or residual if CV > 0)
+ $bettor_gets = max(0, $cv_after_cancel - $cb_threshold);
+ echo "Bettor receives: " . ($bettor_gets/1000) . " VIZ\n";
+ $test50_bettor = ($bettor_gets == 0);
+ echo "Test 50.2 (bettor gets 0 when liquidated): " . ($test50_bettor ? 'PASS' : 'FAIL') . "\n";
+} else {
+ // Position survives — no liquidation needed
+ $test50_bad_debt = true; // no bad debt = OK
+ $test50_bettor = true;
+ echo "Position survives cancel-bet. No liquidation needed.\n";
+}
+
+step('Scenario 50: Cancel-bet liquidation', [
+ 'cancel_value_after' => $cv_after_cancel,
+ 'threshold' => $cb_threshold,
+ 'needs_liquidation' => $needs_liquidation,
+ 'bad_debt' => $bad_debt ?? 0,
+ 'test_bad_debt' => $test50_bad_debt,
+ 'test_bettor' => $test50_bettor,
+]);
+
+// ============================================================
+// SCENARIO 51: LMSR max bet amount (Constraint 2 for multi)
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 51: LMSR max bet amount (Constraint 2)\n";
+echo str_repeat('=', 70) . "\n";
+
+// Simple 3-outcome LMSR market
+// b = 1000000 (1000 VIZ depth), q = [0, 0, 0] initial
+$lmsr_b = 1000000;
+$q = [0, 0, 0];
+$slippage_pct = 10;
+
+if (!function_exists('lmsr_cost_leverage_test')) {
+function lmsr_cost_leverage_test($q, $b) {
+ $sum = 0;
+ foreach ($q as $qi) $sum += exp($qi / $b);
+ return intval($b * log($sum));
+}
+}
+
+if (!function_exists('lmsr_sell_return_leverage_test')) {
+function lmsr_sell_return_leverage_test($q, $b, $outcome_index, $delta) {
+ $old_cost = lmsr_cost_leverage_test($q, $b);
+ $q_new = $q;
+ $q_new[$outcome_index] -= $delta;
+ $new_cost = lmsr_cost_leverage_test($q_new, $b);
+ return max(0, $old_cost - $new_cost);
+}
+}
+
+function lmsr_max_bet_sim($q, $b, $slippage_pct) {
+ $lo = 0;
+ $hi = $b * 100;
+ $best = 0;
+ for ($i = 0; $i < 50; $i++) {
+ $mid = intval(($lo + $hi) / 2);
+ if ($mid <= $lo) break;
+ $q_new = $q;
+ // Try all outcomes, find max bet that stays within slippage
+ $all_ok = true;
+ for ($oi = 0; $oi < count($q); $oi++) {
+ $q_test = $q;
+ $q_test[$oi] += $mid;
+ $cost = lmsr_cost_leverage_test($q_test, $b) - lmsr_cost_leverage_test($q, $b);
+ $tokens = $mid;
+ $slippage = ($cost > 0) ? (($cost - $tokens) / $tokens * 100) : 0;
+ if ($slippage > $slippage_pct) {
+ $all_ok = false;
+ break;
+ }
+ }
+ if ($all_ok) {
+ $best = $mid;
+ $lo = $mid;
+ } else {
+ $hi = $mid;
+ }
+ }
+ return $best;
+}
+
+$max_bet = lmsr_max_bet_sim($q, $lmsr_b, $slippage_pct);
+echo "LMSR b=" . ($lmsr_b/1000) . " VIZ, slippage cap=" . $slippage_pct . "%\n";
+echo "Max bet within slippage: " . ($max_bet/1000) . " VIZ\n";
+
+$test51_positive = ($max_bet > 0);
+$test51_reasonable = ($max_bet > 0 && $max_bet < $lmsr_b * 10000); // should be finite
+echo "Test 51.1 (max_bet > 0): " . ($test51_positive ? 'PASS' : 'FAIL') . "\n";
+echo "Test 51.2 (max_bet is bounded): " . ($test51_reasonable ? 'PASS' : 'FAIL') . "\n";
+
+// Verify: at max_bet, slippage is within cap
+$cost_at_max = lmsr_cost_leverage_test([$max_bet, 0, 0], $lmsr_b) - lmsr_cost_leverage_test($q, $lmsr_b);
+$slippage_at_max = ($cost_at_max > 0 && $max_bet > 0) ? (($cost_at_max - $max_bet) / $max_bet * 100) : 0;
+echo "Slippage at max_bet: " . number_format($slippage_at_max, 2) . "%\n";
+$test51_slippage = ($slippage_at_max <= $slippage_pct + 0.5); // 0.5% tolerance
+echo "Test 51.3 (slippage at max <= cap): " . ($test51_slippage ? 'PASS' : 'FAIL') . "\n";
+
+step('Scenario 51: LMSR max bet', [
+ 'max_bet' => $max_bet,
+ 'slippage_at_max' => round($slippage_at_max, 2),
+ 'test_positive' => $test51_positive,
+ 'test_reasonable' => $test51_reasonable,
+ 'test_slippage' => $test51_slippage,
+]);
+
+// ============================================================
+// SCENARIO 52: Convert to Normal Bet calculation
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 52: Convert to Normal Bet calculation\n";
+echo str_repeat('=', 70) . "\n";
+
+// Position: 5x boost on A, collateral=360 VIZ, loan=1800 VIZ
+$conv_collateral = 360000;
+$conv_loan = 1800000;
+$conv_total = 2160000;
+$conv_R = 10;
+$conv_threshold = leverage_liquidation_threshold($conv_loan, $conv_R); // 1980 VIZ
+
+// Position in profit: cancel_value = 2635 VIZ
+$conv_cancel_value = 2635000;
+$conv_profit = $conv_cancel_value - $conv_threshold; // 655 VIZ
+
+$conv_profit_cost = 50; // 50% of unrealized profit
+$conv_fee = intval($conv_profit * $conv_profit_cost / 100); // 327.5 VIZ
+$conv_total_payment = $conv_threshold + $conv_fee; // 1980 + 327.5 = 2307.5 VIZ
+
+echo "Cancel value: " . ($conv_cancel_value/1000) . " VIZ\n";
+echo "Pool obligation: " . ($conv_threshold/1000) . " VIZ\n";
+echo "Unrealized profit: " . ($conv_profit/1000) . " VIZ\n";
+echo "Conversion fee (50%): " . ($conv_fee/1000) . " VIZ\n";
+echo "Total user payment: " . ($conv_total_payment/1000) . " VIZ\n";
+
+$test52_profit = ($conv_profit == 655000); // 655 VIZ
+$test52_fee = ($conv_fee == 327500); // 327.5 VIZ
+$test52_total = ($conv_total_payment == 2307500); // 2307.5 VIZ
+
+echo "Test 52.1 (profit = 655 VIZ): " . ($test52_profit ? 'PASS' : 'FAIL') . "\n";
+echo "Test 52.2 (fee = 327.5 VIZ): " . ($test52_fee ? 'PASS' : 'FAIL') . "\n";
+echo "Test 52.3 (total payment = 2307.5 VIZ): " . ($test52_total ? 'PASS' : 'FAIL') . "\n";
+
+// Verify: conversion only allowed when current_profit > 0
+$conv_no_profit_cv = $conv_threshold - 10000; // below threshold = no profit
+$conv_no_profit_profit = max(0, $conv_no_profit_cv - $conv_threshold);
+$test52_no_profit = ($conv_no_profit_profit == 0);
+echo "Test 52.4 (no conversion when profit <= 0): " . ($test52_no_profit ? 'PASS' : 'FAIL') . "\n";
+
+step('Scenario 52: Convert to Normal Bet', [
+ 'profit' => $conv_profit,
+ 'fee' => $conv_fee,
+ 'total_payment' => $conv_total_payment,
+ 'test_profit' => $test52_profit,
+ 'test_fee' => $test52_fee,
+ 'test_total' => $test52_total,
+ 'test_no_profit' => $test52_no_profit,
+]);
+
+// ============================================================
+// SCENARIO 53: BATCH COMMIT-REVEAL LIFECYCLE
+// Full flow: commit → reveal → batch settle; plus forfeit path
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 53: BATCH COMMIT-REVEAL LIFECYCLE\n";
+echo str_repeat('=', 70) . "\n";
+
+// Add commit-reveal settings
+$SETTINGS['commit_reveal_enabled'] = 1;
+$SETTINGS['commit_no_reveal_penalty_permille'] = 200; // 20%
+$SETTINGS['min_batch_bet'] = 1000; // 1 VIZ
+$SETTINGS['batch_epoch_blocks'] = 20;
+$SETTINGS['reveal_window_blocks'] = 200;
+
+$oracle53 = make_user(500, 'Oracle53', 100000000);
+register_oracle($oracle53, 0, 5, $SETTINGS);
+oracle_deposit_insurance($oracle53, 5000000);
+$maker53 = make_user(501, 'Maker53', 100000000);
+register_creator($maker53, $SETTINGS);
+
+// Market with allow_batch=1, allow_instant_bet=1
+$r53 = create_market($maker53, $oracle53, 200000, 5, 10, $T0,
+ $T0 + 172800, $T0 + 259200, 1, 10, 0, $SETTINGS);
+$m53 = $r53['market'];
+$m53['allow_batch'] = 1;
+$m53['allow_instant_bet'] = 1;
+$m53['forfeit_pool'] = 0;
+$lps53 = [$r53['lp']];
+oracle_accept($m53, $oracle53, $SETTINGS);
+
+$committer_a = make_user(502, 'CommitterA', 100000000);
+$committer_b = make_user(503, 'CommitterB', 100000000);
+$committer_c = make_user(504, 'CommitterC', 100000000); // will forfeit
+
+// 53a: CommitterA commits 100 VIZ on side A
+$escrow_a = 100000;
+$salt_a = 'secret_salt_a53';
+$r_ca = commit_bet($m53, $committer_a, $escrow_a, 0, 100000, 0, $salt_a, $SETTINGS, $T0 + 3600);
+$commit_a = $r_ca['commit'];
+step('53a: CommitterA commits 100 VIZ on A (escrow locked)', [
+ 'commit_id' => $commit_a['id'],
+ 'escrow' => fmt($commit_a['escrow_amount']),
+ 'no_reveal_fee' => $commit_a['no_reveal_fee_permille'] . '‰',
+ 'reveal_deadline' => $commit_a['reveal_deadline'],
+ 'committer_a_balance' => fmt($committer_a['balance']),
+]);
+assert($r_ca['status'] === true);
+assert($committer_a['balance'] == 100000000 - 100000); // escrow deducted
+
+// 53b: CommitterB commits 80 VIZ on side B
+$escrow_b = 80000;
+$salt_b = 'secret_salt_b53';
+$r_cb = commit_bet($m53, $committer_b, $escrow_b, 1, 80000, 0, $salt_b, $SETTINGS, $T0 + 3700);
+$commit_b = $r_cb['commit'];
+step('53b: CommitterB commits 80 VIZ on B', [
+ 'commit_id' => $commit_b['id'],
+ 'escrow' => fmt($commit_b['escrow_amount']),
+]);
+assert($r_cb['status'] === true);
+
+// 53c: CommitterC commits 50 VIZ on side A (will NOT reveal → forfeit)
+$escrow_c = 50000;
+$salt_c = 'secret_salt_c53';
+$r_cc = commit_bet($m53, $committer_c, $escrow_c, 0, 50000, 0, $salt_c, $SETTINGS, $T0 + 3800);
+$commit_c = $r_cc['commit'];
+step('53c: CommitterC commits 50 VIZ on A (will forfeit)', [
+ 'commit_id' => $commit_c['id'],
+]);
+
+// 53d: CommitterA reveals at T0+4000 (within deadline)
+$reveal_time_a = $T0 + 4000;
+$r_ra = reveal_bet($commit_a, $m53, $committer_a, $reveal_time_a);
+$bet_ra = $r_ra['bet'];
+step('53d: CommitterA reveals (within deadline)', [
+ 'bet_id' => $bet_ra['id'],
+ 'amount' => fmt($bet_ra['amount']),
+ 'side' => $bet_ra['side'],
+ 'surplus_refund' => fmt($r_ra['surplus_refund']),
+ 'epoch' => $r_ra['epoch'],
+ 'status' => $bet_ra['status'] . ' (5=queued)',
+]);
+assert($r_ra['status'] === true);
+assert($bet_ra['status'] == 5); // queued
+assert($r_ra['surplus_refund'] == 0); // escrow == amount
+
+// 53e: CommitterB reveals with surplus (escrow > amount)
+// Re-commit with larger escrow to test surplus refund
+$committer_b2 = make_user(505, 'CommitterB2', 100000000);
+$escrow_b2 = 100000; // 100 VIZ escrow but only 60 VIZ bet
+$amount_b2 = 60000;
+$salt_b2 = 'salt_surplus';
+$r_cb2 = commit_bet($m53, $committer_b2, $escrow_b2, 1, $amount_b2, 0, $salt_b2, $SETTINGS, $T0 + 4100);
+$commit_b2 = $r_cb2['commit'];
+$balance_before_reveal = $committer_b2['balance'];
+$r_rb2 = reveal_bet($commit_b2, $m53, $committer_b2, $T0 + 4200);
+$bet_rb2 = $r_rb2['bet'];
+step('53e: CommitterB2 reveals with surplus (escrow 100, bet 60, refund 40)', [
+ 'surplus_refund' => fmt($r_rb2['surplus_refund']),
+ 'balance_before' => fmt($balance_before_reveal),
+ 'balance_after' => fmt($committer_b2['balance']),
+ 'bet_amount' => fmt($bet_rb2['amount']),
+]);
+assert($r_rb2['surplus_refund'] == 40000); // 100 - 60 = 40 VIZ refunded
+assert($committer_b2['balance'] == $balance_before_reveal + 40000);
+
+// 53f: CommitterC does NOT reveal → commit_forfeit at deadline
+$forfeit_time = $commit_c['reveal_deadline'] + 1;
+$c_balance_before_forfeit = $committer_c['balance'];
+$r_fc = commit_forfeit($commit_c, $m53, $committer_c);
+step('53f: CommitterC FORFEITS (deadline passed, no reveal)', [
+ 'penalty' => fmt($r_fc['penalty']) . ' (20% of 50 VIZ = 10 VIZ)',
+ 'refund' => fmt($r_fc['refund']) . ' (80% returned)',
+ 'forfeit_pool' => fmt($r_fc['forfeit_pool']),
+ 'commit_status' => $commit_c['status'] . ' (2=forfeited)',
+]);
+assert($r_fc['penalty'] == 10000); // 50000 * 200/1000 = 10000 (10 VIZ)
+assert($r_fc['refund'] == 40000); // 50000 - 10000 = 40000
+assert($commit_c['status'] == 2);
+assert($m53['forfeit_pool'] == 10000);
+assert($committer_c['balance'] == $c_balance_before_forfeit + 40000);
+
+// 53g: Batch settle all queued bets
+$queued53 = [$bet_ra, $bet_rb2]; // only revealed bets
+$settle_result = batch_settle($m53, $queued53);
+step('53g: BATCH SETTLE (2 revealed bets)', [
+ 'side_totals' => $settle_result['side_totals'],
+ 'side_tokens' => $settle_result['side_tokens'],
+ 'dust' => $settle_result['dust'],
+ 'reserves_after' => $settle_result['reserves'],
+]);
+
+// Verify bets are now active with tokens
+$active_bets = array_filter($queued53, function($b) { return $b['status'] == 0; });
+assert(count($active_bets) == 2, 'Both bets should be active after batch settle');
+foreach ($queued53 as $b) {
+ if ($b['status'] == 0) {
+ assert($b['weight'] > 0, 'Active bet must have tokens');
+ }
+}
+
+// 53h: Resolve market and verify forfeit_pool boosts winners
+$bets53_all = array_filter($queued53, function($b) { return $b['status'] == 0; });
+$payouts53 = [];
+$lps53_ref = $lps53;
+$r_res53 = resolve_market($m53, 0, $bets53_all, $lps53_ref, $payouts53);
+step('53h: Resolve to A — forfeit_pool boosts winners', [
+ 'forfeit_pool_used' => fmt($m53['forfeit_pool']),
+ 'winner_payouts' => $r_res53['winner_payouts'],
+ 'note' => 'forfeit_pool added to fee_pool → LP bonus (part of winners ecosystem)',
+]);
+
+// Verify forfeit_pool was added to the fee pool (boosting LP/winner payouts)
+$test53_forfeit = ($m53['forfeit_pool'] > 0);
+echo "Test 53.1 (forfeit_pool > 0): " . ($test53_forfeit ? 'PASS' : 'FAIL') . "\n";
+echo "Test 53.2 (both commits revealed and settled): " . (count($active_bets) == 2 ? 'PASS' : 'FAIL') . "\n";
+echo "Test 53.3 (forfeited commit penalty = 20%): " . ($r_fc['penalty'] == 10000 ? 'PASS' : 'FAIL') . "\n";
+echo "Test 53.4 (surplus refund on reveal): " . ($r_rb2['surplus_refund'] == 40000 ? 'PASS' : 'FAIL') . "\n";
+
+step('Scenario 53 summary: Batch commit-reveal lifecycle', [
+ 'commits_placed' => 3,
+ 'revealed' => 2,
+ 'forfeited' => 1,
+ 'forfeit_penalty' => fmt($r_fc['penalty']),
+ 'batch_settled' => count($active_bets) . ' bets',
+ 'all_tests_pass' => ($test53_forfeit && count($active_bets) == 2) ? 'YES' : 'NO',
+]);
+
+
+// ============================================================
+// SCENARIO 54: ACCOUNT-MODE DISPUTE RESOLUTION (dispute_mode=1)
+// Centralized resolver account decides the outcome
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 54: ACCOUNT-MODE DISPUTE RESOLUTION (dispute_mode=1)\n";
+echo str_repeat('=', 70) . "\n";
+
+$oracle54 = make_user(510, 'Oracle54', 100000000);
+register_oracle($oracle54, 0, 5, $SETTINGS);
+oracle_deposit_insurance($oracle54, 5000000);
+$maker54 = make_user(511, 'Maker54', 100000000);
+register_creator($maker54, $SETTINGS);
+$resolver54 = make_user(512, 'Resolver54', 100000000); // centralized dispute resolver
+$dao54 = make_user(513, 'DAO54', 0); // DAO fund
+
+// Create market with dispute_mode=1, dispute_resolver=Resolver54
+$r54 = create_market($maker54, $oracle54, 200000, 5, 10, $T0,
+ $T0 + 172800, $T0 + 259200, 1, 10, 0, $SETTINGS);
+$m54 = $r54['market'];
+$m54['dispute_mode'] = 1; // account mode
+$m54['dispute_resolver'] = $resolver54['id'];
+$lps54 = [$r54['lp']];
+oracle_accept($m54, $oracle54, $SETTINGS);
+
+$bettor54a = make_user(514, 'Bettor54A', 100000000);
+$bettor54b = make_user(515, 'Bettor54B', 100000000);
+
+$r54a = place_bet($m54, $bettor54a, 0, 100000, $T0 + 3600); // 100 VIZ on A
+$r54b = place_bet($m54, $bettor54b, 1, 80000, $T0 + 7200); // 80 VIZ on B
+$bets54 = [$r54a['bet'], $r54b['bet']];
+step('Bets placed on account-mode market', [
+ 'bettor_a' => fmt($r54a['bet']['amount']) . ' on A',
+ 'bettor_b' => fmt($r54b['bet']['amount']) . ' on B',
+]);
+
+// Oracle resolves to A (WRONG — should be B)
+$payouts54 = [];
+resolve_market($m54, 0, $bets54, $lps54, $payouts54);
+step('Oracle resolves to A (WRONG, should be B)', ['market_outcome' => $m54['resolved_outcome']]);
+
+// Bettor54B files dispute
+$r_dis54 = create_dispute($m54, $bettor54b, $SETTINGS);
+step('Bettor54B files dispute', ['dispute_fee' => fmt($SETTINGS['dispute_fee'])]);
+
+// 54a: Non-resolver tries to resolve → rejected
+$fake_resolver = make_user(516, 'FakeResolver', 100000000);
+$m54_copy = $m54; // don't modify real market for this test
+$null_creator54 = null;
+$fake_result = resolve_dispute_account_mode($m54_copy, $fake_resolver, $bettor54b, $oracle54, 1,
+ $bets54, $lps54, $payouts54, $SETTINGS['dispute_fee'], 0, 0, 0, 0, 0, $null_creator54, $dao54, $SETTINGS);
+step('54a: Non-resolver tries to resolve → REJECTED', [
+ 'error' => $fake_result['error'],
+ 'test' => (isset($fake_result['error']) && $fake_result['error'] == 'Not the designated resolver') ? 'PASS' : 'FAIL',
+]);
+assert(isset($fake_result['error']));
+
+// 54b: Legitimate resolver resolves — oracle wrong, correct=B, with penalty + oracle ban
+$oracle54_insurance_before = $oracle54['oracle_insurance'];
+$resolver54_balance_before = $resolver54['balance'];
+$r_resolve54 = resolve_dispute_account_mode($m54, $resolver54, $bettor54b, $oracle54, 1,
+ $bets54, $lps54, $payouts54, $SETTINGS['dispute_fee'],
+ 1000000, 1, 0, 0, 0, $maker54, $dao54, $SETTINGS); // 1000 VIZ penalty, ban oracle
+step('54b: Resolver54 resolves: oracle WRONG, correct=B, 1000 VIZ penalty, oracle banned', [
+ 'decision' => $r_resolve54['decision'],
+ 'correct_outcome' => $r_resolve54['correct_outcome'],
+ 'plaintiff_refund' => fmt($r_resolve54['plaintiff_refund']),
+ 'disputer_reward' => fmt($r_resolve54['disputer_reward']),
+ 'reward_pool' => fmt($r_resolve54['reward_pool']),
+ 'penalty' => fmt($r_resolve54['penalty']),
+ 'resolver_fee' => fmt($r_resolve54['resolver_fee']),
+ 'oracle_insurance_before' => fmt($oracle54_insurance_before),
+ 'oracle_insurance_after' => fmt($oracle54['oracle_insurance']),
+ 'oracle_banned' => $oracle54['oracle_banned'],
+]);
+
+// Verify resolver got a fee
+$test54_resolver_fee = ($resolver54['balance'] > $resolver54_balance_before);
+$test54_oracle_slashed = ($oracle54['oracle_insurance'] < $oracle54_insurance_before);
+$test54_plaintiff_rewarded = ($bettor54b['balance'] > 100000000 - $SETTINGS['dispute_fee']); // got refund + reward
+
+echo "Test 54.1 (resolver receives fee): " . ($test54_resolver_fee ? 'PASS' : 'FAIL') . "\n";
+echo "Test 54.2 (oracle insurance slashed): " . ($test54_oracle_slashed ? 'PASS' : 'FAIL') . "\n";
+echo "Test 54.3 (plaintiff rewarded): " . ($test54_plaintiff_rewarded ? 'PASS' : 'FAIL') . "\n";
+echo "Test 54.4 (oracle banned): " . ($oracle54['oracle_banned'] == 1 ? 'PASS' : 'FAIL') . "\n";
+
+// 54c: Auto-payout recalculated for correct outcome B
+$users_map54 = [510 => &$oracle54, 511 => &$maker54, 512 => &$resolver54, 514 => &$bettor54a, 515 => &$bettor54b];
+$r_pay54 = auto_payout($payouts54, $users_map54);
+step('54c: Auto-payout after account-mode dispute (recalculated for B)', [
+ 'paid' => $r_pay54,
+ 'balances' => balances($users_map54),
+]);
+
+// Verify: Bettor54B (side B = winner) should get payout > original bet
+$test54_winner = ($bettor54b['balance'] > 100000000); // net positive
+echo "Test 54.5 (B bettor wins after dispute recalc): " . ($test54_winner ? 'PASS' : 'FAIL') . "\n";
+
+step('Scenario 54 summary: Account-mode dispute', [
+ 'dispute_mode' => 'account (centralized)',
+ 'resolver' => 'Resolver54',
+ 'outcome_flipped' => 'A→B',
+ 'oracle_penalized' => fmt($oracle54_insurance_before - $oracle54['oracle_insurance']),
+ 'all_tests_pass' => ($test54_resolver_fee && $test54_oracle_slashed && $test54_plaintiff_rewarded && $oracle54['oracle_banned']) ? 'YES' : 'NO',
+]);
+
+
+// ============================================================
+// SCENARIO 55: allow_instant_bet=FALSE ENFORCEMENT
+// Instant mode=0 rejected, only batch/commit-reveal allowed
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 55: allow_instant_bet=FALSE ENFORCEMENT\n";
+echo str_repeat('=', 70) . "\n";
+
+$oracle55 = make_user(520, 'Oracle55', 100000000);
+register_oracle($oracle55, 0, 5, $SETTINGS);
+oracle_deposit_insurance($oracle55, 5000000);
+$maker55 = make_user(521, 'Maker55', 100000000);
+register_creator($maker55, $SETTINGS);
+
+// Create market with allow_instant_bet=0, allow_batch=1 (anti-MEV market)
+$r55 = create_market($maker55, $oracle55, 200000, 5, 10, $T0,
+ $T0 + 172800, $T0 + 259200, 1, 10, 0, $SETTINGS);
+$m55 = $r55['market'];
+$m55['allow_batch'] = 1;
+$m55['allow_instant_bet'] = 0; // FORCE batch/commit-reveal only
+$lps55 = [$r55['lp']];
+oracle_accept($m55, $oracle55, $SETTINGS);
+
+$instant_bettor = make_user(522, 'InstantBettor', 100000000);
+$batch_bettor = make_user(523, 'BatchBettor', 100000000);
+$commit_bettor = make_user(524, 'CommitBettor', 100000000);
+
+// 55a: Instant bet (mode=0) on allow_instant_bet=false market → REJECTED
+// Simulate the enforcement check that the chain would do
+function enforce_instant_bet_check($market, $mode) {
+ if ($mode == 0 && empty($market['allow_instant_bet'])) {
+ return ['error' => 'pm_instant_bet_disabled: market requires batch or commit-reveal'];
+ }
+ return ['status' => true];
+}
+
+$instant_check = enforce_instant_bet_check($m55, 0);
+step('55a: Instant bet (mode=0) on allow_instant_bet=false → REJECTED', [
+ 'result' => $instant_check,
+ 'test' => (isset($instant_check['error']) && strpos($instant_check['error'], 'pm_instant_bet_disabled') !== false) ? 'PASS' : 'FAIL',
+]);
+assert(isset($instant_check['error']));
+
+// 55b: Batch bet (mode=1) succeeds
+$r_batch55 = place_bet_batch($m55, $batch_bettor, 0, 100000, $T0 + 3600);
+step('55b: Batch bet (mode=1) on allow_instant_bet=false → ACCEPTED', [
+ 'bet_id' => $r_batch55['bet']['id'],
+ 'amount' => fmt($r_batch55['bet']['amount']),
+ 'status' => $r_batch55['bet']['status'] . ' (5=queued)',
+ 'epoch' => $r_batch55['epoch'],
+]);
+assert($r_batch55['status'] === true);
+assert($r_batch55['bet']['status'] == 5); // queued
+
+// 55c: Commit-reveal (mode=2) succeeds
+$salt55 = 'salt55test';
+$r_commit55 = commit_bet($m55, $commit_bettor, 50000, 1, 50000, 0, $salt55, $SETTINGS, $T0 + 3700);
+step('55c: Commit-reveal (mode=2) on allow_instant_bet=false → ACCEPTED', [
+ 'commit_id' => $r_commit55['commit']['id'],
+ 'escrow' => fmt($r_commit55['commit']['escrow_amount']),
+]);
+assert($r_commit55['status'] === true);
+
+// 55d: Mutual constraint check — market with both false would be unbettable
+function validate_market_betability($allow_instant, $allow_batch) {
+ if (!$allow_instant && !$allow_batch) {
+ return ['valid' => false, 'error' => 'Market unbettable: both instant and batch disabled'];
+ }
+ return ['valid' => true];
+}
+
+$constraint_fail = validate_market_betability(false, false);
+$constraint_pass = validate_market_betability(false, true);
+$constraint_pass2 = validate_market_betability(true, false);
+step('55d: Mutual constraint (allow_instant_bet OR allow_batch must be true)', [
+ 'both_false' => $constraint_fail,
+ 'instant_false_batch_true' => $constraint_pass,
+ 'instant_true_batch_false' => $constraint_pass2,
+]);
+assert($constraint_fail['valid'] === false);
+assert($constraint_pass['valid'] === true);
+assert($constraint_pass2['valid'] === true);
+
+// 55e: Batch settle the queued bet and verify it works
+$queued55 = [$r_batch55['bet']];
+$settle55 = batch_settle($m55, $queued55);
+step('55e: Batch settle on anti-MEV market', [
+ 'side_totals' => $settle55['side_totals'],
+ 'side_tokens' => $settle55['side_tokens'],
+ 'bet_active' => ($queued55[0]['status'] == 0) ? 'YES' : 'NO',
+ 'bet_tokens' => $queued55[0]['weight'],
+]);
+assert($queued55[0]['status'] == 0); // active after settle
+assert($queued55[0]['weight'] > 0);
+
+$test55_instant_rejected = isset($instant_check['error']);
+$test55_batch_accepted = ($r_batch55['status'] === true);
+$test55_commit_accepted = ($r_commit55['status'] === true);
+$test55_constraint = ($constraint_fail['valid'] === false && $constraint_pass['valid'] === true);
+$test55_settled = ($queued55[0]['status'] == 0 && $queued55[0]['weight'] > 0);
+
+echo "Test 55.1 (instant rejected): " . ($test55_instant_rejected ? 'PASS' : 'FAIL') . "\n";
+echo "Test 55.2 (batch accepted): " . ($test55_batch_accepted ? 'PASS' : 'FAIL') . "\n";
+echo "Test 55.3 (commit-reveal accepted): " . ($test55_commit_accepted ? 'PASS' : 'FAIL') . "\n";
+echo "Test 55.4 (mutual constraint enforced): " . ($test55_constraint ? 'PASS' : 'FAIL') . "\n";
+echo "Test 55.5 (batch settled successfully): " . ($test55_settled ? 'PASS' : 'FAIL') . "\n";
+
+step('Scenario 55 summary: allow_instant_bet=false', [
+ 'instant_mode_rejected' => $test55_instant_rejected ? 'YES' : 'NO',
+ 'batch_mode_accepted' => $test55_batch_accepted ? 'YES' : 'NO',
+ 'commit_reveal_accepted' => $test55_commit_accepted ? 'YES' : 'NO',
+ 'mutual_constraint' => $test55_constraint ? 'ENFORCED' : 'BROKEN',
+ 'all_tests_pass' => ($test55_instant_rejected && $test55_batch_accepted && $test55_commit_accepted && $test55_constraint && $test55_settled) ? 'YES' : 'NO',
+]);
+
+
+// ============================================================
+// SCENARIO 56: ORACLE FIXED FEE TRANSFER AT ACCEPT
+// Creator pays oracle_fixed_fee when oracle accepts (non-self)
+// Self-oracle skips the transfer
+// ============================================================
+echo "\n" . str_repeat('=', 70) . "\n";
+echo "SCENARIO 56: ORACLE FIXED FEE TRANSFER AT ACCEPT\n";
+echo str_repeat('=', 70) . "\n";
+
+// 56a: External oracle — fixed fee transferred
+$oracle56a = make_user(530, 'Oracle56A', 100000000);
+register_oracle($oracle56a, 0, 5, $SETTINGS, 15000); // fixed_fee = 15 VIZ
+oracle_deposit_insurance($oracle56a, 5000000);
+
+$maker56a = make_user(531, 'Maker56A', 100000000);
+register_creator($maker56a, $SETTINGS);
+
+$r56a = create_market($maker56a, $oracle56a, 200000, 5, 10, $T0,
+ $T0 + 172800, $T0 + 259200, 1, 10, 0, $SETTINGS);
+$m56a = $r56a['market'];
+$m56a['oracle_fixed_fee'] = 15000; // 15 VIZ
+
+$maker_balance_before = $maker56a['balance'];
+$oracle_balance_before = $oracle56a['balance'];
+
+$r_accept56a = oracle_accept_with_fixed_fee($m56a, $oracle56a, $maker56a, $SETTINGS);
+step('56a: External oracle accepts → fixed fee transferred (15 VIZ)', [
+ 'fixed_fee_transferred' => fmt($r_accept56a['fixed_fee_transferred']),
+ 'is_self_oracle' => $r_accept56a['is_self_oracle'] ? 'YES' : 'NO',
+ 'maker_balance_change' => fmt($maker_balance_before - $maker56a['balance']),
+ 'oracle_balance_change' => fmt($oracle56a['balance'] - $oracle_balance_before),
+ 'market_status' => $m56a['status'],
+]);
+
+$test56a_fee = ($r_accept56a['fixed_fee_transferred'] == 15000);
+$test56a_maker = (($maker_balance_before - $maker56a['balance']) == 15000);
+$test56a_oracle = (($oracle56a['balance'] - $oracle_balance_before) == 15000);
+$test56a_active = ($m56a['status'] == 1);
+
+echo "Test 56.1 (fixed fee = 15 VIZ): " . ($test56a_fee ? 'PASS' : 'FAIL') . "\n";
+echo "Test 56.2 (maker debited 15 VIZ): " . ($test56a_maker ? 'PASS' : 'FAIL') . "\n";
+echo "Test 56.3 (oracle credited 15 VIZ): " . ($test56a_oracle ? 'PASS' : 'FAIL') . "\n";
+echo "Test 56.4 (market active): " . ($test56a_active ? 'PASS' : 'FAIL') . "\n";
+
+// 56b: Self-oracle — fixed fee is SKIPPED (no self-transfer)
+$self56b = make_user(532, 'SelfOracle56B', 100000000);
+register_oracle($self56b, 0, 5, $SETTINGS, 20000); // fixed_fee = 20 VIZ
+oracle_deposit_insurance($self56b, 5000000);
+register_creator($self56b, $SETTINGS);
+
+$r56b = create_market($self56b, $self56b, 200000, 5, 10, $T0,
+ $T0 + 172800, $T0 + 259200, 1, 10, 0, $SETTINGS);
+$m56b = $r56b['market'];
+$m56b['oracle_fixed_fee'] = 20000;
+// Self-oracle auto-approves (status=1) in create_market — reset to 0 to test accept flow
+$m56b['status'] = 0;
+
+$self_balance_before = $self56b['balance'];
+$r_accept56b = oracle_accept_with_fixed_fee($m56b, $self56b, $self56b, $SETTINGS);
+step('56b: Self-oracle accepts → fixed fee SKIPPED (no self-transfer)', [
+ 'fixed_fee_transferred' => fmt($r_accept56b['fixed_fee_transferred']),
+ 'is_self_oracle' => $r_accept56b['is_self_oracle'] ? 'YES' : 'NO',
+ 'balance_unchanged' => ($self56b['balance'] == $self_balance_before) ? 'YES' : 'NO',
+ 'market_status' => $m56b['status'],
+]);
+
+$test56b_skip = ($r_accept56b['fixed_fee_transferred'] == 0);
+$test56b_self = ($r_accept56b['is_self_oracle'] === true);
+$test56b_balance = ($self56b['balance'] == $self_balance_before);
+$test56b_active = ($m56b['status'] == 1);
+
+echo "Test 56.5 (self-oracle fee skipped): " . ($test56b_skip ? 'PASS' : 'FAIL') . "\n";
+echo "Test 56.6 (is_self_oracle=true): " . ($test56b_self ? 'PASS' : 'FAIL') . "\n";
+echo "Test 56.7 (balance unchanged): " . ($test56b_balance ? 'PASS' : 'FAIL') . "\n";
+echo "Test 56.8 (market active): " . ($test56b_active ? 'PASS' : 'FAIL') . "\n";
+
+// 56c: Creator has insufficient balance for fixed fee — partial transfer
+$oracle56c = make_user(533, 'Oracle56C', 100000000);
+register_oracle($oracle56c, 0, 5, $SETTINGS, 50000); // fixed_fee = 50 VIZ
+oracle_deposit_insurance($oracle56c, 5000000);
+
+$poor_maker = make_user(534, 'PoorMaker', 500000); // enough to create market
+register_creator($poor_maker, $SETTINGS);
+
+$r56c = create_market($poor_maker, $oracle56c, 200000, 5, 10, $T0,
+ $T0 + 172800, $T0 + 259200, 1, 10, 0, $SETTINGS);
+$m56c = $r56c['market'];
+$m56c['oracle_fixed_fee'] = 50000; // wants 50 VIZ
+
+// Simulate: after market creation, maker only has 10 VIZ left
+$poor_maker['balance'] = 10000;
+
+$poor_balance = $poor_maker['balance']; // 10000
+$r_accept56c = oracle_accept_with_fixed_fee($m56c, $oracle56c, $poor_maker, $SETTINGS);
+step('56c: Maker with insufficient balance — transfers what they have', [
+ 'fixed_fee_requested' => fmt(50000),
+ 'fixed_fee_transferred' => fmt($r_accept56c['fixed_fee_transferred']),
+ 'maker_balance_after' => fmt($poor_maker['balance']),
+]);
+
+$test56c_partial = ($r_accept56c['fixed_fee_transferred'] == $poor_balance);
+echo "Test 56.9 (partial transfer = available balance): " . ($test56c_partial ? 'PASS' : 'FAIL') . "\n";
+
+step('Scenario 56 summary: Oracle fixed fee at accept', [
+ 'external_transfer' => $test56a_fee ? '15 VIZ transferred' : 'FAIL',
+ 'self_oracle_skip' => $test56b_skip ? 'Skipped (no self-transfer)' : 'FAIL',
+ 'insufficient_balance' => $test56c_partial ? 'Partial transfer' : 'FAIL',
+ 'all_tests_pass' => ($test56a_fee && $test56b_skip && $test56c_partial) ? 'YES' : 'NO',
+]);
+
+
+// ============================================================
+// FINAL SUMMARY
+// ============================================================
+echo "\n\n" . str_repeat('=', 70) . "\n";
+echo "ALL SCENARIOS COMPLETED\n";
+echo str_repeat('=', 70) . "\n";
+echo "Scenarios tested:\n";
+echo " 1. Happy path: oracle resolves correctly, auto-payout\n";
+echo " 2. Oracle rejects market: liquidity returned\n";
+echo " 3. Self-oracle: auto-approve (no pending queue)\n";
+echo " 4. Time penalty: early/mid/late/very-late bets, quadratic penalty deductions\n";
+echo " 5. Oracle misses resolution: refund all + penalty distribution\n";
+echo " 6. Dispute \u2014 oracle WRONG: recalculate payouts, plaintiff rewarded\n";
+echo " 7. Dispute \u2014 oracle RIGHT: committee gets fee, original payouts kept\n";
+echo " 8. Bet cancellation: CPMM reverse + slippage\n";
+echo " 9. Multiple LPs: TIME-WEIGHTED fee distribution (early LP earns more)\n";
+echo " 10. Penalty curve comparison: linear vs quadratic\n";
+echo " 11. Low-volume LP solvency: 1 small bet, one-sided, LP principal guaranteed\n";
+echo " 12. Oracle re-registration: profile update without fee\n";
+echo " 13. Dispute with extra penalty + permanent oracle ban\n";
+echo " 14. Dispute with time-limited creator ban\n";
+echo " 15. Fractional LP withdrawal (partial + remainder)\n";
+echo " 16. Risk score \u2014 oracle insurance coverage (betting block + listing filter)\n";
+echo " 17. Oracle voluntary no-contest (grace period, pending refund payouts, auto-payout)\n";
+echo " 18. Dispute against abusive no-contest (3-outcome resolution: A/B/no-contest + sanctions)\n";
+echo " 19. Auto-close stale dispute (14-day timeout, refund all + oracle penalty)\n";
+echo " 20. Lazy Pool: deposit + shares + lock period\n";
+echo " 21. Lazy Pool: share calculation after profit (reward_per_share accumulator)\n";
+echo " 22. Lazy Pool: late depositor fairness (no old rewards)\n";
+echo " 23. Lazy Pool: deposit unlock consolidation\n";
+echo " 24. Lazy Pool: planned withdrawal (full)\n";
+echo " 25. Lazy Pool: emergency withdrawal with penalty on locked profit\n";
+echo " 26. Lazy Pool: emergency no profit = no penalty\n";
+echo " 27. Lazy Pool: market auto-allocation + profit distribution\n";
+echo " 28. Lazy Pool: edge cases (1:1 first deposit, zero reward, partial withdrawal)\n";
+echo " 29. LMSR Math: prices, cost, buy/sell roundtrip, tokens_for_amount, b_from_liquidity\n";
+echo " 30. Onix Multi Settlement: losers forfeit, fees deducted, winners split by tokens\n";
+echo " 31. Creator Fee in Binary: creator receives fee from losers_sum\n";
+echo " 32. Multi-Market Lifecycle: create \u2192 bet \u2192 resolve \u2192 verify payouts\n";
+echo " 33. LP Principal Guarantee: subsidy architecturally separate\n";
+echo " 34. Edge Cases: all-on-winner, no-on-winner, zero-volume, single bettor\n";
+echo " 35. Position Transfers: full, partial, to-self (fail), excessive (fail), transfer+resolve\n";
+echo " 36. Graduated Early Recall: idle market loses allocation over time, active market keeps it\n";
+echo " 37. Active Market Penalty: recursive 5% per oracle active market\n";
+echo " 38. Fault Penalty Stamps: stamps on faults, auto-healing after 10 days, combined B+C\n";
+echo " 39. Market Metadata: category/subcategory/tags validation\n";
+echo " 40. Metadata JSON: build + localization structure\n";
+echo " 41. Localization fallback resolver (resolve_i18n simulation)\n";
+echo " 42. Jurisdiction filtering: banned/allowed tag logic\n";
+echo " 43. Category structure: i18n completeness check\n";
+echo " 44. Market creation with metadata: simulated API flow\n";
+echo " 45. i18n JSON files: key consistency between en.json and ru.json\n";
+echo " 46. Leverage cancel_value calculation (CPMM)\n";
+echo " 47. Liquidation threshold & safety margin\n";
+echo " 48. Max leverage binary search (CPMM)\n";
+echo " 49. Leveraged position lifecycle (open -> close)\n";
+echo " 50. Cancel-bet liquidation waterfall (Case B)\n";
+echo " 51. LMSR max bet amount (Constraint 2)\n";
+echo " 52. Convert to Normal Bet calculation\n";
+echo " 53. Batch Commit-Reveal: commit → reveal → batch settle + forfeit penalty\n";
+echo " 54. Account-Mode Dispute Resolution (dispute_mode=1): centralized resolver decides\n";
+echo " 55. allow_instant_bet=false: instant rejected, batch/commit-reveal accepted, mutual constraint\n";
+echo " 56. Oracle Fixed Fee Transfer at Accept (external vs self-oracle, insufficient balance)\n";
+echo "\nRun: php tests/workflow_test.php\n";
diff --git a/.qoder/plans/pm-plan/tests/workflow_test_catalog.md b/.qoder/plans/pm-plan/tests/workflow_test_catalog.md
new file mode 100644
index 0000000000..1e16370ce4
--- /dev/null
+++ b/.qoder/plans/pm-plan/tests/workflow_test_catalog.md
@@ -0,0 +1,222 @@
+# Workflow Test Catalog
+
+> Pure-math simulation — no DB required.
+> Run: `php tests/workflow_test.php`
+> File: `tests/workflow_test.php` (≈5 000 lines, 56 numbered scenarios, ~100 `assert` / echo-test checks)
+
+---
+
+## Quick Reference
+
+| # | Scenario | Key Assertions |
+|---|---------|----------------|
+| 1 | Happy path: oracle resolves correctly, auto-payout | resolution → payouts → auto-payout flow |
+| 2 | Oracle rejects market: liquidity returned | status=-1, LP refunded |
+| 3 | Self-oracle: auto-approve (no pending queue) | status=1 at creation |
+| 4 | Time penalty: early/mid/late/very-late bets | quadratic penalty deductions |
+| 5 | Oracle misses resolution | refund all + penalty distribution |
+| 6 | Dispute — oracle WRONG | recalculate payouts, plaintiff rewarded |
+| 7 | Dispute — oracle RIGHT | committee gets fee, original payouts kept |
+| 8 | Bet cancellation | CPMM reverse + slippage |
+| 9 | Multiple LPs: time-weighted fee distribution | early LP earns more |
+| 10 | Penalty curve comparison | linear vs quadratic |
+| 11 | Low-volume LP solvency | 1 small bet, one-sided, LP principal guaranteed |
+| 12 | Oracle re-registration | profile update without fee |
+| 13 | Dispute + extra penalty + permanent oracle ban | insurance slashed, ban enforced |
+| 14 | Dispute + time-limited creator ban | ban_until timestamp |
+| 15 | Fractional LP withdrawal | partial + remainder, position stays active |
+| 16 | Risk score | oracle insurance coverage (betting block + listing filter) |
+| 17 | Oracle voluntary no-contest | grace period, pending refund payouts, auto-payout |
+| 18 | Dispute against abusive no-contest | 3-outcome resolution: A/B/no-contest + sanctions |
+| 19 | Auto-close stale dispute | 14-day timeout, refund all + oracle penalty |
+| 20 | Lazy Pool: deposit + shares + lock period | MasterChef share accounting |
+| 21 | Lazy Pool: share calculation after profit | reward_per_share accumulator |
+| 22 | Lazy Pool: late depositor fairness | no old rewards for new depositor |
+| 23 | Lazy Pool: deposit unlock consolidation | unlock_time merging |
+| 24 | Lazy Pool: planned withdrawal (full) | shares burned, principal + reward |
+| 25 | Lazy Pool: emergency withdrawal | penalty on locked profit |
+| 26 | Lazy Pool: emergency no profit = no penalty | zero-profit edge case |
+| 27 | Lazy Pool: market auto-allocation | profit distribution via rps |
+| 28 | Lazy Pool: edge cases | 1:1 first deposit, zero reward, partial withdrawal |
+| 29 | LMSR Math unit tests | prices, cost, buy/sell roundtrip, tokens_for_amount, b_from_liquidity |
+| 30 | Onix Multi Settlement | losers forfeit, fees deducted, winners split by tokens |
+| 31 | Creator Fee in Binary | creator receives fee from losers_sum |
+| 32 | Multi-Market Lifecycle | create → bet → resolve → verify payouts |
+| 33 | LP Principal Guarantee | subsidy architecturally separate |
+| 34 | Edge Cases | all-on-winner, no-on-winner, zero-volume, single bettor |
+| 35 | Position Transfers | full, partial, to-self (fail), excessive (fail), transfer+resolve |
+| 36 | Graduated Early Recall | idle market loses allocation, active market keeps it |
+| 37 | Active Market Penalty | recursive 5% per oracle active market |
+| 38 | Fault Penalty Stamps | stamps on faults, auto-healing after 10 days, combined B+C |
+| 39 | Market Metadata | category/subcategory/tags validation |
+| 40 | Metadata JSON | build + localization structure |
+| 41 | Localization fallback | resolve_i18n simulation |
+| 42 | Jurisdiction filtering | banned/allowed tag logic |
+| 43 | Category structure | i18n completeness check |
+| 44 | Market creation with metadata | simulated API flow |
+| 45 | i18n JSON files | key consistency between en.json and ru.json |
+| 46 | Leverage cancel_value | CPMM calculation (3 sub-tests) |
+| 47 | Liquidation threshold | safety margin (2 sub-tests) |
+| 48 | Max leverage binary search | CPMM (3 sub-tests) |
+| 49 | Leveraged position lifecycle | open → close (2 sub-tests) |
+| 50 | Cancel-bet liquidation waterfall | Case B: bad debt + bettor (4 sub-tests) |
+| 51 | LMSR max bet amount | Constraint 2 (3 sub-tests) |
+| 52 | Convert to Normal Bet | profit/fee/total calculation (4 sub-tests) |
+| **53** | **Batch Commit-Reveal** | commit → reveal → batch settle + forfeit (4 sub-tests) |
+| **54** | **Account-Mode Dispute** | dispute_mode=1 centralized resolver (5 sub-tests) |
+| **55** | **allow_instant_bet=false** | instant rejected, batch/commit accepted (5 sub-tests) |
+| **56** | **Oracle Fixed Fee Transfer** | external vs self-oracle, insufficient balance (9 sub-tests) |
+
+---
+
+## Detailed Breakdown
+
+### Oracle & Market Lifecycle (1–5, 12)
+
+| # | Scenario | What it tests |
+|---|---------|--------------|
+| 1 | Happy path | Oracle registers, deposits insurance, creator creates market, oracle accepts, bets placed, oracle resolves to A, auto-payout distributes winnings |
+| 2 | Oracle rejects | Oracle rejects pending market → liquidity returned to creator, status=-1 |
+| 3 | Self-oracle | Creator is their own oracle → market auto-approves (status=1, no pending queue) |
+| 4 | Time penalty | Bets at T+1h (early, no penalty), T+24h (mid), T+46.7h (late, quadratic), T+47.9h (very late). Verifies penalty_ratio, quadratic curve, deduction from profit |
+| 5 | Oracle missed | Oracle misses `result_expiration` → cron penalty: 5% of insurance slashed, all bets+LPs refunded pro-rata |
+| 12 | Oracle re-registration | Already-registered oracle updates fee/fixed_fee without paying registration fee again |
+
+### Disputes — Committee Mode (6–7, 13–14, 17–19)
+
+| # | Scenario | What it tests |
+|---|---------|--------------|
+| 6 | Oracle WRONG | Bettor disputes → committee sides with plaintiff → payouts recalculated for correct outcome, oracle insurance slashed (reward_pool) |
+| 7 | Oracle RIGHT | Dispute filed but committee upholds oracle → dispute_fee split: voters 50%, oracle 50% |
+| 13 | Extra penalty + oracle ban | Committee adds 2000 VIZ extra penalty + permanent oracle ban; verifies ban enforcement on next accept |
+| 14 | Creator ban | Time-limited creator ban (30 days); `ban_until` timestamp set |
+| 17 | Oracle no-contest | Oracle voluntarily declares no-contest → penalty = 50% of dispute_fee from insurance → all bets/LPs refunded → grace period → auto-payout |
+| 18 | Dispute against no-contest | Oracle abuses no-contest → resolver picks correct outcome (A/B/no-contest) with sanctions |
+| 19 | Auto-close stale dispute | 14-day timeout → full refund + oracle penalty + disputer fee return |
+
+### Disputes — Account Mode (54)
+
+| # | Scenario | Sub-tests | What it tests |
+|---|---------|-----------|--------------|
+| 54 | Account-mode dispute | 54.1–54.5 | `dispute_mode=1` with designated resolver account. Non-resolver rejected; resolver flips outcome, applies penalty + oracle ban; payout recalculated |
+
+### Betting & Cancellation (8, 35, 55)
+
+| # | Scenario | What it tests |
+|---|---------|--------------|
+| 8 | Bet cancellation | C bets on A, D bets on B (shifts reserves), C cancels → slippage effect |
+| 35 | Position transfers | Full transfer, partial transfer, to-self (fail), excessive amount (fail), transfer+resolve |
+| 55 | allow_instant_bet=false | `mode=0` rejected (`pm_instant_bet_disabled`), `mode=1` batch accepted, `mode=2` commit-reveal accepted, mutual constraint (`allow_instant_bet OR allow_batch`), batch settle |
+
+### Batch Commit-Reveal (53)
+
+| # | Scenario | Sub-tests | What it tests |
+|---|---------|-----------|--------------|
+| 53 | Commit-reveal lifecycle | 53.1–53.4 | 3 commits (2 revealed, 1 forfeited). Hash commitment + escrow lock. Reveal verifies hash, refunds surplus. Forfeit: 20% penalty → `forfeit_pool`, 80% refunded. Batch settle: aggregate CPMM op, pro-rata tokens. `forfeit_pool` boosts winners |
+
+### Oracle Fixed Fee (56)
+
+| # | Scenario | Sub-tests | What it tests |
+|---|---------|-----------|--------------|
+| 56 | Fixed fee at accept | 56.1–56.9 | External oracle: 15 VIZ transferred creator→oracle. Self-oracle: transfer skipped entirely. Insufficient balance: partial transfer = available balance |
+
+### Liquidity (9, 11, 15, 33)
+
+| # | Scenario | What it tests |
+|---|---------|--------------|
+| 9 | Multiple LPs time-weighted | 3 LPs (at creation, T+24h, T+48h-1s) — early LP earns much more fee per VIZ |
+| 11 | Low-volume LP solvency | 1 VIZ bet on A, no bets on B → LP principal guaranteed |
+| 15 | Fractional LP withdrawal | Withdraw 50% (partial), position stays active, then full withdrawal of remainder |
+| 33 | LP principal guarantee | Heavy one-sided betting → subsidy always returned, architecturally separate from losers' pool |
+
+### Lazy Pool (20–28, 36)
+
+| # | Scenario | What it tests |
+|---|---------|--------------|
+| 20 | Deposit + shares | First depositor gets 1:1 shares, lock period enforced |
+| 21 | Reward accumulator | `reward_per_share` increases after profit, user gets correct share |
+| 22 | Late depositor fairness | New depositor doesn't get old rewards |
+| 23 | Unlock consolidation | Multiple deposits merge unlock times |
+| 24 | Planned withdrawal | Full withdrawal: shares burned, principal + reward returned |
+| 25 | Emergency withdrawal | Penalty applied only to locked profit |
+| 26 | Emergency no profit | Zero profit → zero penalty |
+| 27 | Market auto-allocation | Pool allocates % to new market, profit distributed via rps |
+| 28 | Edge cases | 1:1 first deposit, zero reward, partial withdrawal |
+| 36 | Graduated early recall | Idle market loses allocation over time; active market keeps it |
+
+### LMSR Math & Multi-Outcome (29–32, 34, 51)
+
+| # | Scenario | What it tests |
+|---|---------|--------------|
+| 29 | LMSR unit tests | Equal-q → equal prices, cost monotonicity, buy/sell roundtrip, tokens_for_amount, b_from_liquidity, price shift after buy |
+| 30 | Multi settlement | Losers forfeit, oracle/creator/LP fees deducted, winners split by token weight |
+| 31 | Creator fee binary | Creator receives permille of losers_sum |
+| 32 | Multi lifecycle | 4-outcome LMSR: create → bet all outcomes → resolve → verify payouts |
+| 34 | Edge cases | All-on-winner, no-on-winner, zero-volume, single bettor |
+| 51 | LMSR max bet | Constraint 2: max bet within slippage cap |
+
+### Leverage (46–50, 52)
+
+| # | Scenario | What it tests |
+|---|---------|--------------|
+| 46 | Cancel_value calculation | CPMM cancel_value > 0, symmetric, slippage |
+| 47 | Liquidation threshold | `threshold = loan * 1.10`, safe threshold |
+| 48 | Max leverage search | Binary search for max leverage at given R |
+| 49 | Position lifecycle | Open leveraged position → voluntary close |
+| 50 | Liquidation waterfall | Cancel-bet shifts reserves → leveraged position liquidated if CV < threshold |
+| 52 | Convert to normal bet | Profit calculation, 50% conversion fee, total payment |
+
+### Risk & Reputation (16, 37–38)
+
+| # | Scenario | What it tests |
+|---|---------|--------------|
+| 16 | Risk score | `risk_score = insurance / total_bets`; bet blocked below threshold; listing hidden |
+| 37 | Active market penalty | 5% recursive penalty per active market accepted by oracle |
+| 38 | Fault penalty stamps | Stamps on faults, auto-healing after 10 days, combined scenarios |
+
+### Metadata & i18n (39–45)
+
+| # | Scenario | What it tests |
+|---|---------|--------------|
+| 39 | Market metadata | Category/subcategory/tags validation rules |
+| 40 | Metadata JSON | Build + localization structure |
+| 41 | Localization fallback | `resolve_i18n` simulation |
+| 42 | Jurisdiction filtering | Banned/allowed tag logic |
+| 43 | Category structure | i18n completeness check |
+| 44 | Market creation with metadata | Simulated API flow |
+| 45 | i18n JSON files | Key consistency between `en.json` and `ru.json` |
+
+---
+
+## Helper Functions
+
+| Function | Simulates | Notes |
+|----------|-----------|-------|
+| `make_user()` | — | Test user factory |
+| `register_oracle()` | `pm_oracle_register` | Fee + insurance setup |
+| `oracle_deposit_insurance()` | — | Lock insurance bond |
+| `register_creator()` | — | Creator registration |
+| `create_market()` | `pm_create_market` | Binary CPMM market |
+| `oracle_accept()` | `pm_oracle_accept_market` | Basic accept (no fixed fee) |
+| `oracle_accept_with_fixed_fee()` | `pm_oracle_accept_market` | With fixed fee transfer |
+| `oracle_reject()` | — | Reject + refund liquidity |
+| `place_bet()` | `pm_place_bet` (mode=0) | Instant CPMM bet |
+| `place_bet_batch()` | `pm_place_bet` (mode=1) | Queued batch bet |
+| `commit_bet()` | `pm_commit_bet` | Hash commitment + escrow |
+| `reveal_bet()` | `pm_reveal_bet` | Hash verify + enqueue |
+| `commit_forfeit()` | `pm_commit_forfeit` (virtual) | Penalty → forfeit_pool |
+| `batch_settle()` | `pm_batch_settle` (virtual) | Aggregate CPMM settlement |
+| `cancel_bet()` | `pm_cancel_bet` | CPMM reverse |
+| `add_liquidity()` | `pm_add_liquidity` | Proportional reserve add |
+| `withdraw_liquidity()` | `pm_withdraw_liquidity` | Principal-safe withdrawal |
+| `resolve_market()` | `pm_resolve_market` | Parimutuel settlement |
+| `auto_payout()` | `pm_auto_payout` (virtual) | Credit winners |
+| `oracle_penalty()` | `pm_oracle_missed_penalty` (virtual) | Slash + refund |
+| `create_dispute()` | `pm_dispute_create` | Escrow dispute fee |
+| `resolve_dispute_oracle_wrong()` | `pm_dispute_finalize` (committee) | Flip outcome + slash oracle |
+| `resolve_dispute_oracle_right()` | `pm_dispute_finalize` (committee) | Uphold oracle + split fee |
+| `resolve_dispute_account_mode()` | `pm_dispute_resolve` (account) | Centralized resolver decides |
+| `oracle_no_contest()` | `pm_no_contest` | Voluntary no-contest |
+| `calc_risk_score()` | — | Insurance/bets ratio |
+| `fmt()` | — | Format satoshi → VIZ string |
+| `balances()` | — | Snapshot all user balances |
diff --git a/.qoder/plans/pm-plan/theory_concepts/FORECASTER-FIT-ru.md b/.qoder/plans/pm-plan/theory_concepts/FORECASTER-FIT-ru.md
new file mode 100644
index 0000000000..643c9598d2
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/FORECASTER-FIT-ru.md
@@ -0,0 +1,236 @@
+# Forecaster / Протокол Onix — соответствие 90 теоретическим концептам
+
+> Для каждого концепта PM Atlas из [INDEX.md](INDEX.md) здесь показано, **как концепт Forecaster (протокол Onix) закрывает его при работе в виде consensus-level операций на VIZ DLT**, и **нужен ли он вообще** для этой архитектуры.
+>
+> Опорные документы: [whitepaper](../../../docs/onix-protocol-whitepaper.md), [спецификация](../../../docs/onix-protocol-specification.md), [committee DAO и диспуты](../../../docs/committee-dao-and-prediction-markets.md).
+>
+> 🇬🇧 Английская версия: [FORECASTER-FIT.md](FORECASTER-FIT.md).
+
+## Легенда
+
+| Метка | Значение |
+|------|---------|
+| ✅ **Решено** | Дизайн Onix напрямую решает/обрабатывает это |
+| ⚪ **Имманентно** | Свойство, которое Onix демонстрирует/наследует by construction (доп. работа не нужна) |
+| ➖ **Не нужно** | Архитектурно избыточно в Onix |
+| 🟡 **Частично / Roadmap** | Частично решено сегодня; остальное — в роадмапе VIZ |
+| 🏛 **Слой клиента** | Решается на уровне юрисдикционного клиента, не протокола |
+| 🔴 **Открыто / Риск** | Остаётся живой проблемой; не решено полностью |
+
+Главное структурное отличие от всех остальных платформ: **принципал LP структурно гарантирован (победителям платят только из проигранных ставок), ценообразование — CPMM для бинарных и LMSR-softmax + parimutuel-сеттлмент для мульти, и нет ордербука.** Большая часть концептов «ликвидность и трейдинг» существует именно для управления инвентарным риском маркет-мейкера CLOB/LMSR — и поэтому **не применима** к Onix, где нет инвентарь-несущего мейкера.
+
+> **Актуализация on-chain (HF14 / live).** Эта таблица изначально писалась по whitepaper/спецификации. Ряд пунктов, помеченных тогда как *roadmap*, теперь **реализованы как consensus-операции** и проверены в `consensus_sim`:
+> - **Батч-аукционы + commit-reveal ставки** — `pm_commit_bet` / `pm_reveal_bet` / `pm_batch_settle`, per-market `allow_batch` / `allow_instant_bet`, median kill-switch `pm_commit_reveal_enabled`. **Только бинарные** (мульти форсит `allow_instant_bet` — LMSR-батча пока нет).
+> - **Опциональный leverage-сабсистем** — `pm_leverage_open/close/convert`, фондируется из Lazy Pool, kill-switch `pm_leverage_enabled` (по умолчанию off). Прямо закрывает **Position Collateralization (#30)**.
+> - **Сам Lazy Pool** — синглтон, auto-allocation, MasterChef-учёт, leverage-займы, graduated recall.
+> - Поле рынка **`endogeneity_tier`**; on-chain **баны создателей** (`pm_creator_ban_object`); расширенные settlement-vop (`pm_payout` на каждого беттора, `pm_leverage_resolve`, `pm_market_accepted`, `pm_auto_payout`) + методы плагина.
+> - **Новое относительно спеки:** **стейк в lazy pool учитывается как вес в голосовании** — и в PM-диспутах, *и* в голосовании за заявки DAO-комитета (конвертация в vesting-shares, гейт HF14).
+>
+> **Намеренно НЕ делаем:** commit-reveal голосование в *диспутах* — диспуты комитета это **открытые публичные слушания by design** (голоса остаются публичными через `pm_dispute_vote`, и бюллетень можно менять до закрытия). **Остаётся roadmap:** автоматические экзогенные data-оракулы. Строки и таблица смягчений ниже приведены к этому live-состоянию.
+
+---
+
+## 1. Информационная теория (Information Theory)
+
+| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли |
+|---|---------|---------|--------------------------------------------------|
+| 1 | **Brier Score** | ⚪ Имманентно | Не механизм протокола, а *метрика* оценки рынков Onix. На VIZ каждая ставка/резолюция — consensus-validated событие, значит истории цен и исходы полностью on-chain → Brier-скоринг платформы (и оракулов) считается кем угодно. Нужен как вход для аналитики/репутации, не как ядро. |
+| 2 | **Calibration** | ⚪ Имманентно | Цены Onix — настоящие вероятности (CPMM `P(A)=reserve_b/(reserve_a+reserve_b)`, LMSR-softmax суммируется в 1). Калибровка — emergent-свойство для *измерения*, косвенно улучшается time-penalty (давит no-info ставки в последнюю секунду) и глубокой LP-ликвидностью. Протокол её не навязывает. |
+| 3 | **Credibility Markets** | 🟡 Частично | Bonded-oracle + 14-метричная репутация + composite trust score — это по сути credibility-рынок для *резолверов*. Ставка репутацией против исхода — нативна. Общий продукт «стейкай репутацию на заявлениях» — возможная сборка на слое клиента, не ядро. |
+| 4 | **Distribution Markets** | 🟡 Roadmap | Onix Multi (3–10 дискретных исходов) аппроксимирует распределение через бакеты. Настоящие непрерывные distribution-рынки (CDF/скаляр) **не** в скоупе сегодня; нужна скалярная операция исхода. Примыкает к roadmap-пункту «category-level AMMs». |
+| 5 | **Endogeneity** | 🟡 Частично (смягчено) | Риск, что рынок меняет то, что предсказывает. Категориально-специфичен (эконом-данные чисто, политика/соц рискованно). Смягчается **живым полем `endogeneity_tier`** (тег оракула 1/2/3) и **опциональными commit-reveal/батч ставками (теперь on-chain)**, которые не дают публичной цене «течь» во время приёма (канал «термостата»); экзогенная резолюция через automated data oracles — **всё ещё roadmap**. См. [раздел Смягчения](#смягчения-для-семейства-рефлексивности). |
+| 6 | **Forecasting Accuracy** | ⚪ Имманентно | Вся ценностная гипотеза. Onix улучшает её косвенно: risk-free LP → глубже книги → меньше слиппедж → больше информированного участия → лучше цены. Точность — это выход для измерения, не фича. |
+| 7 | **Info Finance** | ⚪ Имманентно | Onix *и есть* инфо-финансовый инструмент: consensus-level операции превращают информацию в priced, settle-able позиции. Миграция на VIZ делает информационный слой цензуроустойчивым и композируемым. |
+| 8 | **Information Aggregation** | ✅ Решено | Основная функция. CPMM/LMSR агрегируют разрозненные ставки в единую вероятность; глубокая risk-free LP-ликвидность — именно тот рычаг, которым Onix заставляет агрегацию работать (флайвил §7.3 whitepaper). |
+| 9 | **Information Asymmetry** | 🟡 Частично | Дизайн Onix (parimutuel/CPMM) означает, что информированные трейдеры извлекают прибыль из *других проигравших бетторов*, а не из LP — поэтому асимметрия не банкротит ликвидность (в отличие от CLOB/LMSR-мейкеров). **Опциональные commit-reveal + батч ставки теперь live (бинарные):** закоммиченные ставки сеттлятся по единой батч-цене, убирая утечку направления через mempool; per-market `allow_batch` + median kill-switch оставляют это опциональным. |
+| 10 | **Legibility** | ✅ Решено | Каждое финансовое действие — consensus-validated VIZ-операция с audit trail `market_log` (до/после резервов). Полностью читаемо/аудируемо любым узлом — строго легибельнее централизованного бэкенда или непрозрачного CLOB. Новые settlement-vop (**`pm_payout`** на каждого беттора, **`pm_leverage_resolve`**, **`pm_market_accepted`**) + методы плагина делают per-bettor исходы и leverage-резолюции напрямую запрашиваемыми. |
+| 11 | **Longshot Bias** | 🟡 Частично | CPMM/LMSR всё ещё могут давать favorite-longshot bias из поведения бетторов; Onix не правит это напрямую. Time penalty и глубокая ликвидность гасят искажение, но это поведенческий выход, не устранён. |
+| 12 | **Noise Decomposition** | ⚪ Имманентно | Аналитическая линза, не фича протокола. On-chain ряды цен/объёмов на VIZ делают разложение «сигнал-шум» возможным для аналитиков. В ядре не нужно. |
+| 13 | **Nowcasting** | ⚪ Имманентно | Цены Onix обновляются на каждую ставку (~3с блоки VIZ), давая real-time nowcast-оценки. Имманентно любому живому AMM-рынку; доп. механизма нет. |
+| 14 | **Price Discovery** | ✅ Решено | CPMM и LMSR-softmax — непрерывные движки price discovery; когерентность цен (`Σ price = 1`) держится *by construction*, без слоя арбитража/split-merge. |
+| 15 | **Probability Infrastructure** | ✅ Решено | Это по сути тезис Onix на VIZ: prediction markets как **first-class consensus операции** (`pm_*`), не смарт-контракты — base-layer вероятностный примитив. Прямая цель миграции. |
+| 16 | **Superforecasting** | ⚪ Имманентно | Концепт индивидуального скилла; Onix награждает точных бетторов через payout losers→winners. Трансфер позиций + репутация могут поддержать идентичность суперфорекастера, но это черта участника, не логика протокола. |
+| 17 | **Wisdom of Crowds** | ✅ Решено | Механизм, который Onix монетизирует. Risk-free LP снижает барьер, чтобы участвовала бóльшая часть толпы, заостряя агрегат. Ядро дизайнерского обоснования. |
+| 18 | **Yes Bias** | 🟡 Частично | Поведенческий перекос к «Yes». Симметричный CPMM и profit-only time penalty структурно не благоприятствуют Yes, но и не правят человеческий bias. Смягчается глубиной ликвидности; вопрос измерения. |
+
+---
+
+## 2. Дизайн механизмов (Mechanism Design)
+
+| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли |
+|---|---------|---------|--------------------------------------------------|
+| 19 | **Binary Contracts** | ✅ Решено | Onix Binary = CPMM (`x·y=k`) на двух исходах. Доказательство AM-GM гарантирует `reserve_a+reserve_b ≥ L`, значит принципал LP покрыт. Основной тип рынка. |
+| 20 | **Combinatorial Prediction Markets** | ➖ Не нужно (сегодня) | LMSR естественно раскладывается по комбинаторным пространствам, но Onix Multi ограничен 3–10 *независимыми* исходами и намеренно опускает CTF split/merge. Комбинаторные/conditional-бандлы явно вне скоупа; для модели LP-гарантии не требуются. |
+| 21 | **Incentive Compatibility** | ✅ Решено | LMSR наследует IC от log scoring rule (truth-telling доминантна). Onix добавляет согласование стимулов через bonded-оракулов (insurance > прибыли манипуляции), сеттлмент losers-fund-winners и time-weighted LP-награды. |
+| 22 | **Keynesian Beauty Contest** | ✅ Решено (рынок) / ⚪ (слой диспутов — принято by design) | KBC — патология *относительного/peer-скоринга*. **Рыночный** слой Onix платит бетторам против внешней истины (parimutuel), значит он структурно анти-KBC — ты зарабатываешь, *отклоняясь* от цены толпы, когда она неправа. Единственная экспозиция KBC — **stake-weighted голосование комитета в диспуте** (peer-механизм). **Решение: commit-reveal в диспутах внедряться НЕ будет** — диспут комитета это **открытые публичные слушания**, и доверие к ДАО держится на максимально прозрачном разрешении споров; сокрытие голосов это доверие подорвало бы. Остаточный риск KBC принят и структурно мал: голосующим **не платят** за совпадение с большинством (нет bandwagon-бонуса), а **бюллетень можно менять** до закрытия (честные апдейты на новые доводы ожидаемы, а не подавляются). Участники пула также **с правом голоса** (стейк lazy pool → vesting-shares, HF14). См. [раздел Смягчения](#смягчения-для-семейства-рефлексивности). |
+| 23 | **LMSR** | ✅ Решено (ключевая инновация) | Файл концепта отмечает, что LMSR *провалился на бинарных* (перманентный убыток на границе 0/1). Ответ Onix: **CPMM для бинарных, а LMSR только для мульти, где мейкер НЕ контрагент** — parimutuel-сеттлмент платит победителям из проигравших, поэтому LMSR-субсидия никогда не под риском (`макс. убыток LP = 0` против `b·ln(N)`). Центральный ход дизайна. |
+| 24 | **LOX (Log-Odds Excess Lateness)** | ➖ Не нужно | Специализированная метрика скоринга/запаздывания. Onix вместо этого использует **квадратичный time penalty на прибыль** для стимулов поздних ставок — проще, на этапе сеттлмента. LOX-скоринг не часть модели. |
+| 25 | **Market Manipulation** | 🟡 Частично | Bonded-оракул (бонд > прибыли манипуляции), DPoS-валидируемые операции и арбитраж комитета повышают стоимость манипуляции. Манипуляция ценой большими ставками ограничена глубиной и — на **батч/commit-reveal рынках (теперь live)** — единой клиринговой ценой, нейтрализующей speed-снайпинг («налог снайпера»); активный surveillance — всё ещё roadmap. |
+| 26 | **Market Scoring Rules** | ✅ Решено | Onix Multi — реализация market scoring rule (LMSR), переиспользованная с parimutuel-сеттлментом. Используется напрямую. |
+| 27 | **Multi-Outcome Markets** | ✅ Решено | Onix Multi обрабатывает N=3–10 через LMSR-softmax + parimutuel payout, с `b = S/ln(N)`. First-class тип рынка. |
+| 28 | **Parimutuel Markets** | ✅ Решено (фундамент) | Сеттлмент в *обоих* типах рынков — parimutuel: проигранные ставки формируют `winners_pool`, распределяемый по доле токенов. Именно это делает гарантию LP структурной, а не страховой. |
+| 29 | **Peer Prediction** | ➖ Не нужно | Схемы truth-telling без ground-truth. Onix опирается на bonded-оракулов + комитет, не на peer-prediction скоринг. Могло бы помочь резолюции субъективных рынков, но не используется. |
+| 30 | **Position Collateralization** | ✅ Решено (+ опц. leverage, live) | По умолчанию каждая ставка полностью предоплачена (вся сумма входит в резервы; нет комиссий при ставке) — тотальная коллатерализация by construction. Теперь Onix **также** реализует «next level» концепта: **опциональный leverage-сабсистем** (`pm_leverage_open/close/convert`, kill-switch `pm_leverage_enabled`, по умолчанию off). Маржа — это **займ из Lazy Pool** (без эмиссии токенов — zero-sum сохранён), поэтому позиция остаётся полностью обеспеченной *с точки зрения системы*. Бинарный «jump risk», ломающий liquidation-движки CLOB (по этому концепту), решается **ликвидацией по pre-bet резервам**: opposing-bet / settlement force-close возвращает `min(cancel_value, obligation) ≥ loan`, так что пул получает заём + проценты; **единственный** ограниченный путь bad-debt — same-side `pm_cancel_bet` (Case B). Каскад ликвидации намеренно **не** гейтится kill-switch'ем, поэтому выключение leverage никогда не снимает защиту с открытых позиций. |
+| 31 | **Proper Scoring Rules** | ✅ Решено | LMSR — cost-function дуал log proper scoring rule; Onix Multi наследует его truthful-elicitation свойство. |
+| 32 | **Reflexivity** | 🟡 Частично (смягчено) | Родитель endogeneity. Смягчается тем же набором — **commit-reveal/батч ставки + `endogeneity_tier` теперь live**, экзогенная резолюция всё ещё roadmap — **плюс on-chain баны создателей** (`pm_creator_ban_object`) для вредной рефлексивности (рынки убийств/«hit», пропаганда-рынки, создающие «конституенцию за исход»); сам *список* запрещённых категорий остаётся на слое клиента. Глубокая risk-free LP-ликвидность также повышает стоимость newsworthy-манипуляции ценой. См. [раздел Смягчения](#смягчения-для-семейства-рефлексивности). |
+
+---
+
+## 3. Ликвидность и трейдинг (Liquidity & Trading)
+
+> **Заголовок:** в Onix **нет ордербука и нет инвентарь-несущего маркет-мейкера**. Большой класс этих концептов существует именно для управления инвентарным риском CLOB/мейкера, поэтому **не применим** к Onix.
+
+| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли |
+|---|---------|---------|--------------------------------------------------|
+| 33 | **Adverse Selection** | ✅ Решено (переосмыслено) | Классическая проблема (информированный поток банкротит мейкера) **не может обанкротить LP Onix**: победителям платят из проигравших, не из принципала LP (гарантия AM-GM / parimutuel). Информированные извлекают из других *бетторов*, не из LP. Устраняет ключевой режим отказа LP. |
+| 34 | **Arbitrage** | ⚪ Имманентно | Внутрирыночный арбитраж не нужен: когерентность (`Σ price = 1`) держится by construction в CPMM и LMSR-softmax. Слой split/merge-арбитража не нужен. |
+| 35 | **Batched Auctions** | ✅ Решено (опц., live) | Реализовано как **per-market uniform-price батч** (`mode=1` ставки + commit-reveal → `pm_batch_settle` на каждой эпохе `pm_batch_epoch_blocks`); AMM двигает только **нетто-остаток**, поэтому все same-side филлы клирятся по одной цене, а speed-снайпинг («налог снайпера») нейтрализуется. Per-market `allow_batch` + median kill-switch `pm_commit_reveal_enabled`; **только бинарные** сегодня (мульти форсит `allow_instant_bet`). Инвариант LP `Σreserve ≥ L` не тронут — каждый батч это один валидный CPMM-переход. |
+| 36 | **Bid-Ask Spread** | ➖ Не нужно | Нет ордербука → нет котируемого спреда. «Стоимость торговли» проявляется как слиппедж CPMM/LMSR, зависящий от глубины, не от спредов мейкера. Концепт не мапится. |
+| 37 | **Bonding Trades** | ⚪ Имманентно | Ставки *и есть* bonded-сделки: капитал внесён в резервы и высвобождается только при резолюции (или через отмену/трансфер). Нативное поведение. |
+| 38 | **Continuous Double Auction** | ➖ Не нужно | CDA — модель CLOB, которую Onix явно отвергает в пользу AMM. Не используется. |
+| 39 | **Covariance Markets** | ➖ Не нужно | Торговля корреляцией событий требует комбинаторной/conditional структуры, которую Onix опускает. Вне скоупа. |
+| 40 | **Cross-Platform Arbitrage** | 🟡 Частично | Цены Onix могут расходиться с Polymarket/Kalshi; арбитраж между платформами возможен, но внешний для протокола. Открытый API VIZ + headless-клиент делают данные доступными; нативного моста нет. |
+| 41 | **Execution Quality** | ✅ Решено (переосмыслено) | Нет частичных филлов/очереди. Качество исполнения = детерминированный слиппедж + опц. `min_tokens`/`min_return`-гард, валидируемый на консенсусе. Предсказуемо by construction. |
+| 42 | **Gap Risk** | ✅ Решено (для LP) | Gap-риск (резкий скачок к 0/1, выносящий мейкера) — режим отказа, который структурная гарантия LP Onix устраняет: LP никогда не держит терминальный риск проигравшей стороны. Бетторы по-прежнему несут свой риск исхода (как и задумано). |
+| 43 | **Hedging** | 🟡 Частично | Бетторы могут хеджироваться встречными позициями, трансфером (`pm_transfer_position`), отменой ставки (если разрешена) через reverse CPMM и теперь **опциональным leverage** (`pm_leverage_open/convert`) для капиталоэффективного оффсета. Нативных мульти-leg деривативов нет; базовый + leveraged хедж возможен. |
+| 44 | **Implied Correlation** | ➖ Не нужно | Требует мульти-событийных/комбинаторных рынков, которые Onix опускает. Вне скоупа. |
+| 45 | **Insider Trading** | 🟡 Частично / 🏛 Клиент | Протокол не может детектить инсайд; смягчается time penalty (поздние инфо-ставки дают меньше прибыли) и bonded-резолюцией. KYC/surveillance для полиции инсайдеров — ответственность **слоя клиента** (регулируемые клиенты). |
+| 46 | **Kelly Criterion** | ⚪ Имманентно | Стратегия сайзинга беттора, не фича протокола. Onix отдаёт чистые вероятности и полную коллатерализацию, так что Kelly-сайзинг считается участниками; новый **опциональный leverage** позволяет беттору отыгрывать fractional-Kelly эдж с маржой (фондируется из пула, ограничено ликвидацией). Ядро не вовлечено сверх предоставления примитивов. |
+| 47 | **Liquidity Fragmentation** | 🟡 Roadmap | Per-market пулы фрагментируют ликвидность сегодня. Топ-приоритет роадмапа — **shared/category-level AMM-пулы** — архитектурный фикс. Lazy Pool уже мьютуализирует *депозиты* между рынками. |
+| 48 | **Liquidity Provision** | ✅ Решено (ключевой дифференциатор) | Risk-free LP — заголовок: принципал структурно гарантирован, time-weighted награды, Lazy Pool auto-allocation + MasterChef-учёт. Решает проблему «LP теряют деньги», ради которой и создан протокол. |
+| 49 | **Market Making** | ✅ Решено (переосмыслено) | Активный мейкер не нужен — AMM + LP-пул *и есть* мейкер, и он не несёт инвентарного риска. «Market making» схлопывается в пассивное risk-free предоставление ликвидности. |
+| 50 | **Minimum Viable Liquidity** | ✅ Решено | Принудительный пол: мин. начальная ликвидность 100 VIZ; Lazy Pool авто-засевает каждый новый рынок `free_balance × allocation_%`. MVL структурно бутстрапится, не оставлен на случай. |
+| 51 | **Order Book** | ➖ Не нужно | Onix на AMM; ордербука нет by design. |
+| 52 | **Orderflow Arbitrage** | ➖ Не нужно | Нет ордербука / нет PFOF-роутинга потока → неприменимо. |
+| 53 | **Relative Value Trading** | ➖ Не нужно | Кросс-инструментный RV требует коррелированных/комбинаторных рынков, которые Onix опускает. Вне скоупа. |
+| 54 | **Retail Flow** | ✅ Решено (переосмыслено) | В CLOB ритейл-поток субсидирует убытки мейкера от toxic flow. В Onix мейкера, которого надо защищать, нет — ритейл и информированные бетторы платят в один parimutuel-пул; LP безразличен. Напряжение «ритейл vs toxic» растворяется на слое LP. |
+| 55 | **Semantic Tick Size** | ➖ Не нужно | Гранулярность тика — концепт CLOB. Цены Onix — непрерывные AMM-функции; точность — фикс. mVIZ-единица (1/1000). Дизайн тика не нужен. |
+| 56 | **Temporal Arbitrage** | 🟡 Частично | Ставка раньше vs позже несёт разный риск; **time penalty на прибыль** в Onix — именно тот механизм, что закладывает в цену запаздывание, демпфируя «жди-определённости» арбитраж. Не устранён, но явно дестимулирован. |
+| 57 | **Time Arbitrage** | 🟡 Частично | То же семейство, что #56 — эксплуатация тайминга информации. Квадратичный time penalty + ~3с такт блоков снижают, но не убирают edge. Учтён по дизайн-намерению. |
+| 58 | **Toxic Flow** | ✅ Решено (для LP) | Определяющая проблема CLOB/LMSR (снайпер выкупает книгу по 10¢ на исходе-99¢, мейкер ест 80¢) **не бьёт по LP Onix** — payouts из ставок проигравших, а субсидия LP возвращается безусловно. Toxic flow здесь просто значит, что информированные бетторы выигрывают parimutuel-пул, как и задумано. Крупный структурный выигрыш. |
+| 59 | **Wash Trading** | 🟡 Частично / 🏛 Клиент | Отсутствие комиссий при ставке убирает один стимул wash, но накрутка объёма возможна; трансферы — чистая переуступка (там фарм комиссий невозможен). Детекция/surveillance — забота клиента + роадмапа. |
+
+---
+
+## 4. Оракул и резолюция (Oracle & Resolution)
+
+| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли |
+|---|---------|---------|--------------------------------------------------|
+| 60 | **Corruption Value Multiple (CVM)** | ✅ Решено (принципом дизайна) | Явный security-инвариант протокола: **бонд страховки оракула должен превышать потенциальную прибыль манипуляции**. Risk factor (insurance/bets) питает composite trust score. CVM — напрямую обоснование бондинга. |
+| 61 | **Dispute Resolution** | ✅ Решено | Полная система: 12ч grace, `dispute_fee`, обязательный ответ оракула, per-market резолвер (`dispute_mode==0` stake-weighted голосование комитета / `==1` именованный резолвер), slashing страховки, 3-исходные no-contest диспуты, 14-дневный auto-close против заморозки, on-chain баны создателя/оракула. Одна из наиболее проработанных частей. **Дополнение HF14:** депозитчики lazy pool сохраняют вес голоса в диспуте (NAV пула → vesting-shares, добавляется к `effective_vesting_shares`). |
+| 62 | **Oracle Design** | ✅ Решено | Bonded-модель: рег-комиссия, ≥5000 VIZ страховки, явный акцепт, резолюция с доказательствами, 14-метричная репутация, freshness decay, механика банов. Ядровая подсистема. |
+| 63 | **Resolution Criteria** | 🟡 Частично / 🏛 Клиент | Вопрос/критерии рынка в `url`/описании (custom_json, display-only). Протокол навязывает *процесс* (кто резолвит, диспуты), но не *качество* критерия — двусмысленные критерии — ответственность создателя/клиента, полиция ретроспективно через диспуты + **on-chain баны создателя** (`pm_creator_ban_object`, теперь live). |
+| 64 | **Self-Resolving Markets** | ➖ Не нужно (сегодня) | Резолюция Onix — oracle-driven, не алгоритмическая. Automated data oracles (Chainlink-style фиды) — высокоприоритетный roadmap для объективных рынков, что аппроксимировало бы self-resolution. |
+| 65 | **UMA Protocol** | ➖ Не нужно (заменено) | Оптимистичный оракул UMA (у Polymarket) функционально заменён bonded-оракулом Onix + диспут-моделью комитета VIZ. Та же проблема, нативное VIZ-решение — без внешней зависимости от оракула. |
+
+---
+
+## 5. Governance и решения (Governance & Decisions)
+
+| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли |
+|---|---------|---------|--------------------------------------------------|
+| 66 | **Attention Markets** | ➖ Не нужно | Торговля вниманием/виральностью — отдельный продукт; Onix фокусируется на резолюции событий. Возможна категория на слое клиента, не ядро. |
+| 67 | **Conditional Tokens** | ➖ Не нужно (явно) | Whitepaper §7.2 доказывает, что CTF split/merge **архитектурно не нужен** — когерентность цен математическая, не навязанная токенами. *Единственную* полезную фичу CTF (трансфер позиций) реализуют нативно как `pm_transfer_position` с шифрованными memo. Намеренно опущено. |
+| 68 | **Decision Markets** | 🟡 Возможно | Onix Multi мог бы выразить decision-рынки, но conditional-структура «если-политика-то-метрика» не нативна (нет conditional tokens). Собираемо на слое клиента; не ядровый примитив. |
+| 69 | **Futarchy** | 🟡 Возможно (клиент) | Файл концепта: футархия = decision-рынки на conditional futures. У Onix нет нативных conditional-рынков, поэтому полная футархия в ядре не поддержана. Stake-weighted комитет VIZ уже управляет *параметрами*; governance-by-market — конструкция клиента/роадмапа. |
+| 70 | **Hyperstition Markets** | ➖ Не нужно (намеренно) | Рефлексивность-как-фича (координировать, не предсказывать). Это *дизайн-выбор, а не баг для фикса*: требование Onix, чтобы исход был **внешне верифицируем bonded-оракулом**, структурно исключает hyperstition-рынки по умолчанию. Может существовать как отдельный «coordination market» продукт на слое клиента с milestone-резолюцией, но не ядровая цель. См. [раздел Смягчения](#смягчения-для-семейства-рефлексивности). |
+| 71 | **Impact Markets** | ➖ Не нужно | Retrospective-funding/impact-сертификаты — отдельный домен. Возможна категория на слое клиента; не ядро. |
+| 72 | **No-Loss Prediction Markets** | 🟡 Смежно | Onix не no-loss для *бетторов* (проигравшие теряют ставки — это финансирует победителей). Но он **no-loss для *LP*** (принципал гарантирован). Yield-funded no-loss вариант (стейкаешь yield, принципал возвращается) — другая модель; no-loss на стороне LP уже реализован. |
+| 73 | **Opportunity Markets** | ⚪ Имманентно (смежно) | **Opportunity-cost protection** Lazy Pool (graduated recall, active-market penalty, fault stamps) напрямую адресует opportunity-cost капитала — хотя «opportunity markets» как категория продукта вне скоупа. |
+
+---
+
+## 6. Бизнес и платформы (Business & Platforms)
+
+| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли |
+|---|---------|---------|--------------------------------------------------|
+| 74 | **AI agents** | 🟡 Roadmap | Headless-клиент + открытые VIZ-операции делают программных агентов (бетторы, LP, автоматические оракулы) простыми. AI-driven ликвидность/резолюция — естественное расширение, ещё не специфицировано. |
+| 75 | **Cross-subsidization** | ⚪ Имманентно | Lazy Pool кросс-субсидирует ликвидность по многим рынкам с одного депозита; MasterChef `reward_per_share` шарит fee-yield. Кросс-субсидирование встроено в экономику пула. |
+| 76 | **Demand markets** | ➖ Не нужно | Рынки замера/агрегации спроса — категория продукта; не ядровый примитив Onix. Слой клиента. |
+| 77 | **Distribution moat** | 🟡 Стратегия | Моат Onix — risk-free LP-yield + VIZ-нативная инфраструктура (флайвил). Дистрибуция (Telegram WebApp сегодня → headless web-клиент) — go-to-market, частично решается платформонезависимостью после миграции. |
+| 78 | **Election markets** | 🏛 Клиент | Поддержаны как обычные бинарные/мульти рынки; их *легальность* — вопрос юрисдикционного клиента (whitelisted оракулы, фильтры категорий). Протокол нейтрален. |
+| 79 | **Event contracts** | ⚪ Имманентно | Каждый рынок Onix *и есть* event contract. Регуляторная классификация — вопрос клиента/права, не логика протокола. |
+| 80 | **Federal preemption** | 🏛 Клиент (N/A протоколу) | Файл концепта: зависит от того, «swaps» ли US event contracts. VIZ DLT — **инфраструктура, не оператор** (whitepaper §6.2) — как Bitcoin это леджер. Правовые обязательства привязаны к клиентам, не к консенсусу. Не забота протокола. |
+| 81 | **Long-tail markets** | ✅ Решено | Именно ниша, которую включает bounded-loss LMSR — и Onix делает её *risk-free* для засева через Lazy Pool auto-allocation + пол ликвидности. Жизнеспособность long-tail — ядровый аргумент продажи. |
+| 82 | **Market structure** | ✅ Решено (определено) | Onix определяет чёткую структуру: AMM-ценообразование, parimutuel-сеттлмент, bonded-оракулы, DPoS-управляемые параметры, consensus-level операции. Связная, новая рыночная структура против CLOB-платформ. |
+| 83 | **Market surveillance** | 🟡 Roadmap / 🏛 Клиент | Полный on-chain audit trail (`market_log`, каждая операция consensus-validated) делает surveillance *возможным* для любого. Активный surveillance/энфорсмент — забота клиента + роадмапа. |
+| 84 | **Network effects** | 🟡 Стратегия | Флайвил (risk-free LP → глубина → бетторы → комиссии → ещё LP) — задуманный сетевой эффект. Shared liquidity pools (roadmap) усиливают его. Go-to-market, не механика протокола. |
+| 85 | **Parlays** | ➖ Не нужно | Многоногие комбо-ставки требуют conditional/комбинаторной структуры, которую Onix опускает. Вне скоупа (возможна сборка клиента поверх независимых рынков). |
+| 86 | **Platform competition** | 🟡 Стратегия | Конкурирует уникальным «passive yield without impermanent loss» против Polymarket/Kalshi (таблица сравнения whitepaper §7.1). Стратегическое позиционирование, не логика протокола. |
+| 87 | **Polymarket** | ⚪ Референс | Главный бенчмарк. Onix отличается по всем осям: CPMM/LMSR vs CLOB, ноль LP-риска vs инвентарный риск, нативные операции vs контракты Polygon, bonded-оракул vs UMA, без CTF. Используется для сравнения, не перенят. |
+| 88 | **Regulatory arbitrage** | 🏛 Клиент | Модель юрисдикционного клиента: каждый регион строит свой compliant (или permissionless) клиент на нейтральных рельсах VIZ. Регуляторное позиционирование живёт целиком на слое клиента. |
+| 89 | **Regulatory classification** | 🏛 Клиент | Являются ли рынки swaps/gaming/securities — решается per-юрисдикция на слое клиента; протокол классификационно-нейтрален (те же `pm_*` для permissionless и регулируемых клиентов). Не забота протокола. |
+
+---
+
+## Итог — что реально меняет дизайн Onix-на-VIZ
+
+**Решено структурно (ключевые выигрыши):**
+- LP-сторонние **adverse selection, toxic flow, gap risk, impermanent loss, инвентарный риск мейкера** → все устранены, т.к. победителям платят только из проигранных ставок, а принципал LP возвращается безусловно (доказательство AM-GM для CPMM; parimutuel-сеттлмент для LMSR).
+- **Провал LMSR на бинарных рынках** → обойдён использованием CPMM для бинарных и ограничением LMSR мульти-рынками, где мейкер не контрагент.
+- **Liquidity provision, minimum viable liquidity, жизнеспособность long-tail** → risk-free LP + Lazy Pool auto-allocation.
+- **Oracle design, dispute resolution, CVM** → bonded-оракул + 14-метричная репутация + stake-weighted диспуты комитета.
+- **Price discovery, arbitrage, conditional tokens** → когерентность цен математическая (`Σ price = 1`), значит не нужны ордербук, split/merge, внутренний слой арбитража.
+
+**Не нужно / намеренно опущено:** ордербук, CDA, bid-ask spread, semantic tick size, orderflow arbitrage, CTF split/merge, комбинаторные/covariance/correlation/relative-value/parlay рынки, UMA, peer prediction, LOX.
+
+**Вытолкнуто на слой юрисдикционного клиента:** federal preemption, regulatory classification/arbitrage, легальность election-рынков, KYC/энфорсмент инсайда, surveillance.
+
+**Реализовано с момента написания таблицы (теперь live on-chain, HF14):** опциональные батч-аукционы + commit-reveal ставки (бинарные), опциональный leverage-сабсистем (position collateralization), Lazy Pool, `endogeneity_tier`, on-chain баны создателей, settlement-vop на беттора/leverage + методы плагина, и вес стейка lazy pool в PM-диспутах + голосовании за заявки DAO-комитета.
+
+**В роадмапе VIZ (частично сегодня):** shared/category liquidity pools (фикс фрагментации), automated data oracles (→ self-resolving объективные рынки), distribution markets, AI agents. *(Прим.: commit-reveal в **диспутах** в этом списке **нет** — он намеренно отклонён; слушания диспутов остаются публичными. Commit-reveal для **ставок** уже live.)*
+
+**Остаётся открытым / поведенческое (смягчено, не устранено):** longshot/yes bias, манипуляция ценой через глубину, кросс-платформенный арбитраж. У семейства рефлексивности (endogeneity, reflexivity, KBC, hyperstition) есть конкретный план смягчения — см. ниже.
+
+---
+
+## Смягчения для семейства рефлексивности
+
+Endogeneity, reflexivity, Keynesian beauty contest (KBC) и hyperstition — это **одна корневая проблема на разных слоях**: рынок/цена влияет на исход, который он измеряет. Один небольшой набор примитивов адресует все четыре.
+
+### Карта корневой причины
+
+| Слой | Концепт | Канал |
+|------|---------|-------|
+| Уровень форекастера | **Keynesian Beauty Contest** | стадность к видимому консенсусу в *относительном/peer-скоринге* |
+| Уровень рынка | **Endogeneity** | *существование/видимость* рынка меняет поведение (категориально) |
+| Уровень рынка | **Reflexivity** | общая обратная связь цена↔реальность; манипуляция-как-пропаганда |
+| По дизайну | **Hyperstition** | рефлексивность используется *намеренно* для координации исхода |
+
+### Примитивы смягчения
+
+| Примитив | Статус | Что чинит | Заметки |
+|----------|--------|-----------|---------|
+| **Commit-reveal голосование в диспутах** | **отклонено (делать НЕ будем)** | KBC | Диспут комитета это **открытые публичные слушания**: `pm_dispute_vote` — публичный бюллетень, и **остаётся таким by design** — доверие к ДАО держится на прозрачном разрешении споров. Бюллетень **можно менять** до закрытия (повтор перезаписывает), чтобы голосующие честно обновляли решение на новые доводы; остаточный KBC принят (за совпадение с большинством не платят). |
+| **Commit-reveal ставки (батчем)** | **live (опц., бинарные)** | endogeneity, reflexivity, инфо-асимметрия | `pm_commit_bet`/`pm_reveal_bet`/`pm_batch_settle` скрывают направление/размер in-flight потока, чтобы публичная цена не «текла» во время приёма (убивает канал термостата). Сеттлится единой ценой **батчем** (см. ниже). |
+| **Поле рынка `endogeneity_tier`** | **живое поле** | endogeneity | Оракул тегирует tier 1 (эконом-данные — чисто), 2 (спорт/расписание), 3 (политика/соц — рискованно); UI показывает уровень рефлексивного риска; клиенты могут ограничивать tier-3. |
+| **Экзогенная резолюция (automated data oracles)** | roadmap (high) | endogeneity, reflexivity | Резолюция привязана к внешнему фиду (BLS/ФРС/спорт-API), на который рынок не влияет → чистый термометр. |
+| **Список запрещённых категорий + бан создателя** | **бан создателя live on-chain**; список — слой клиента | вредная рефлексивность, hyperstition | Блокировать рынки, где YES создаёт «конституенцию за исход» (рынки убийств/«hit»/терактов, пропаганда-рынки). Через on-chain бан создателя (`pm_creator_ban_object`) + фильтр категорий клиента. |
+| **Глубокая risk-free LP-ликвидность** | ядро сегодня | манипуляционная рефлексивность | Флайвил делает книгу глубокой, поэтому двигать цену ради «newsworthy» манипулированного заголовка — дорого. |
+
+### KBC: почему рыночный слой уже безопасен
+
+KBC — патология **относительного скоринга** (платят за близость к peers → стадность к peers). Рыночный слой Onix платит бетторам против **внешней истины через parimutuel-сеттлмент** — тебя награждают за *отклонение* от неправильной цены толпы, а не за совпадение. Значит слой бетторов структурно анти-KBC. Единственный относительный/peer-механизм в протоколе — **stake-weighted голосование комитета в диспуте**, и остаточный риск KBC там **принят by design** — диспут оставлен открытыми публичными слушаниями (без commit-reveal), потому что доверие к ДАО держится на прозрачном разрешении споров; риск ограничивает другое — голосующим не платят за совпадение с большинством, а бюллетень можно менять по мере поступления доводов.
+
+### Commit-reveal против инварианта CPMM `a·b=k`
+
+CPMM **path-dependent** (токены зависят от резервов в момент исполнения), поэтому commit-reveal *нельзя* делать ставка-за-ставкой против живой кривой — порядок раскрытия вернул бы MEV и слил бы цену. Фикс (и почему §8.3 ставит commit-reveal рядом с batch-auction): **перестать обновлять кривую per-bet; обновлять её раз в эпоху через uniform-price батч-сеттлмент.**
+
+1. **Commit:** прислать `hash(side, amount, salt, min_tokens)` и заэскроить `amount`.
+2. **Reveal:** раскрыть `(side, amount, salt)`; раскрытия собираются, но **не применяются** до конца эпохи (видеть чужие reveal бесполезно — своё уже закоммичено).
+3. **Сеттлмент раз:** встречный поток (`A_in` vs `B_in`) неттится между бетторами по единой клиринговой цене `p*`; только **нетто-остаток** двигает AMM, поэтому `k` пересчитывается **один раз**. Все A-филлы получают `p*`, все B-филлы — `1−p*` → нет внутрибатчевого преимущества по порядку.
+4. **Флор `min_tokens`:** поскольку цена невидима в момент commit, per-bet флор по токенам обязателен; если `tokens < min_tokens` на сеттлменте, ставка отклоняется и возвращается из эскроу.
+5. **Анти-griefing:** нераскрытие → штраф из эскроу в LP-fee/DAO-пул, убивая атаку «закоммить опциональность, раскрой только победителей».
+
+**Гарантия LP не тронута:** каждый батч-сеттлмент — валидный CPMM-переход, значит AM-GM `reserve_a + reserve_b ≥ L` держится. Меняется только *гранулярность* обновления кривой (per-bet → per-epoch). Onix Multi аналогично через агрегированную LMSR cost-функцию.
+
+**Фазировка:** (1) сначала uniform-price батч-аукционы — уже дёшево убивают ordering-MEV/фронт-раннинг; (2) поверх — commit-reveal сокрытие — добавляет in-flight конфиденциальность, включается выборочно для tier-3 (endogeneity-чувствительных) рынков. **И (1), и (2) теперь реализованы on-chain для бинарных рынков** (мульти всё ещё форсит instant-ставки — LMSR-батча пока нет).
+
+### Изменение вердиктов
+
+| Концепт | Было | Стало |
+|---------|------|-------|
+| Keynesian Beauty Contest | 🔴 Открыто | ✅ рынок / ⚪ диспут — голоса публичны **by design** (без commit-reveal; бюллетень изменяем, голосующим не платят, участники пула с правом голоса) |
+| Endogeneity | 🔴 Открыто | 🟡 смягчено — `endogeneity_tier` + commit-reveal/батч **live**; экзогенные оракулы roadmap |
+| Reflexivity | 🔴 Открыто | 🟡 смягчено — бан создателя **live on-chain**; список категорий — слой клиента |
+| Hyperstition | 🔴 Открыто | ➖ исключено по дизайну (опц. продукт клиента) |
diff --git a/.qoder/plans/pm-plan/theory_concepts/FORECASTER-FIT.md b/.qoder/plans/pm-plan/theory_concepts/FORECASTER-FIT.md
new file mode 100644
index 0000000000..a444fe5083
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/FORECASTER-FIT.md
@@ -0,0 +1,236 @@
+# Forecaster / Onix Protocol — Fit Against the 90 Theory Concepts
+
+> For each PM Atlas concept in [INDEX.md](INDEX.md), this maps **how the Forecaster concept (Onix Protocol) addresses it once it runs as consensus-level operations on VIZ DLT**, and **whether it is needed or not** for this architecture.
+>
+> Grounding docs: [whitepaper](../../../docs/onix-protocol-whitepaper.md), [specification](../../../docs/onix-protocol-specification.md), [committee DAO & disputes](../../../docs/committee-dao-and-prediction-markets.md).
+>
+> 🇷🇺 Russian version: [FORECASTER-FIT-ru.md](FORECASTER-FIT-ru.md).
+
+## Legend
+
+| Mark | Meaning |
+|------|---------|
+| ✅ **Resolved** | Onix design directly solves or handles it |
+| ⚪ **Inherent** | A property Onix exhibits/inherits by construction (no extra work) |
+| ➖ **Not needed** | Architecturally unnecessary under Onix |
+| 🟡 **Partial / Roadmap** | Partly addressed today; rest is on the VIZ roadmap |
+| 🏛 **Client-layer** | Handled by the jurisdictional client, not the protocol |
+| 🔴 **Open / Risk** | Still a live concern; not fully solved |
+
+The single biggest structural difference from every other platform: **LP principal is structurally guaranteed (winners paid only from losers' forfeited stakes), pricing is CPMM for binary and LMSR-softmax + parimutuel settlement for multi, and there is no order book.** Most "liquidity & trading" concepts that exist to manage market-maker inventory risk simply **do not apply** because Onix has no inventory-bearing maker.
+
+> **On-chain actualization (HF14 / live).** This mapping was first written against the whitepaper/spec. Several items then marked *roadmap* are now **implemented as consensus operations** and verified in `consensus_sim`:
+> - **Batch auctions + commit-reveal betting** — `pm_commit_bet` / `pm_reveal_bet` / `pm_batch_settle`, per-market `allow_batch` / `allow_instant_bet`, median kill-switch `pm_commit_reveal_enabled`. **Binary only** (multi forces `allow_instant_bet` — no LMSR batch yet).
+> - **Opt-in leverage subsystem** — `pm_leverage_open/close/convert`, lazy-pool-funded, kill-switch `pm_leverage_enabled` (default off). Directly answers **Position Collateralization (#30)**.
+> - **The Lazy Pool itself** — singleton, auto-allocation, MasterChef accounting, leverage loans, graduated recall.
+> - **`endogeneity_tier`** market field; on-chain **creator bans** (`pm_creator_ban_object`); richer settlement virtual ops (`pm_payout` per bettor, `pm_leverage_resolve`, `pm_market_accepted`, `pm_auto_payout`) + matching plugin API.
+> - **New since the spec:** **lazy-pool stake counts as governance weight** in PM disputes *and* DAO committee-request voting (converted to vesting-shares, HF14-gated).
+>
+> **Deliberately NOT built:** commit-reveal *dispute* voting — committee disputes are **open public hearings by design** (votes stay public via `pm_dispute_vote`, and ballots are revisable until close). **Still roadmap:** automated exogenous data oracles. Rows and the mitigations table below are updated to this live state.
+
+---
+
+## 1. Information Theory
+
+| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed |
+|---|---------|---------|--------------------------------------------------|
+| 1 | **Brier Score** | ⚪ Inherent | Not a protocol mechanism but the *metric* by which Onix markets are judged. On VIZ, every bet/resolution is a consensus-validated event, so per-market price histories and outcomes are fully on-chain → Brier scoring of the platform (and of oracles) is computable by anyone. Needed only as an analytics/reputation input, not core logic. |
+| 2 | **Calibration** | ⚪ Inherent | Onix prices are genuine probabilities (CPMM `P(A)=reserve_b/(reserve_a+reserve_b)`, LMSR softmax sums to 1). Calibration is an emergent property to *measure*, improved indirectly by the time-penalty (discourages no-info last-second bets) and deep LP liquidity. Not something the protocol enforces. |
+| 3 | **Credibility Markets** | 🟡 Partial | The bonded-oracle + 14-metric reputation + composite trust score is effectively a credibility market for *resolvers*. Staking credibility against an outcome is native. A general "stake reputation on claims" product is a possible client-layer build, not core. |
+| 4 | **Distribution Markets** | 🟡 Roadmap | Onix Multi (3–10 discrete outcomes) approximates a distribution via bucketed outcomes. True continuous distribution markets (CDF/scalar) are **not** in scope today; would need a scalar-outcome operation. Listed-adjacent to the roadmap's "category-level AMMs." |
+| 5 | **Endogeneity** | 🟡 Partial (mitigated) | The risk that the market changes the thing it predicts. Category-specific (econ-data clean, political/social risky). Mitigated by the **live `endogeneity_tier` market field** (oracle-tagged 1/2/3) and **opt-in commit-reveal/batch betting (now on-chain)** that stops the public price from "leaking" mid-window (the thermostat channel); exogenous resolution via automated data oracles is **still roadmap**. See [Mitigations §](#mitigations-for-the-reflexivity-family). |
+| 6 | **Forecasting Accuracy** | ⚪ Inherent | The whole value proposition. Onix improves it indirectly: zero-risk LP → deeper books → less slippage → more informed participation → better prices. Accuracy is the output to measure, not a feature to build. |
+| 7 | **Info Finance** | ⚪ Inherent | Onix *is* an info-finance instrument: consensus-level operations turn information into priced, settleable positions. VIZ migration makes the information layer censorship-resistant and composable. |
+| 8 | **Information Aggregation** | ✅ Resolved | Core function. CPMM/LMSR pricing aggregates dispersed bets into a single probability; deep risk-free LP liquidity is precisely the lever Onix pulls to make aggregation work (the flywheel in §7.3 of the whitepaper). |
+| 9 | **Information Asymmetry** | 🟡 Partial | Onix's parimutuel/CPMM design means informed traders extract from *other losing bettors*, not from the LP — so asymmetry doesn't bankrupt liquidity (unlike CLOB/LMSR makers). **Opt-in commit-reveal + batch betting is now live (binary):** committed bets settle at a uniform batch price, removing the mempool-direction leak; per-market `allow_batch` + median kill-switch keep it optional. |
+| 10 | **Legibility** | ✅ Resolved | Every financial action is a consensus-validated VIZ operation with a `market_log` audit trail (before/after reserves). Fully legible/auditable by any node — strictly more legible than a centralized backend or opaque CLOB. New settlement virtual ops (**`pm_payout`** per bettor, **`pm_leverage_resolve`**, **`pm_market_accepted`**) + dedicated plugin API methods make per-bettor outcomes and leverage resolutions directly queryable. |
+| 11 | **Longshot Bias** | 🟡 Partial | CPMM/LMSR pricing can still exhibit favorite-longshot bias from bettor behavior; Onix doesn't correct it directly. The time penalty and deep liquidity dampen distortion, but bias is a behavioral output, not eliminated. |
+| 12 | **Noise Decomposition** | ⚪ Inherent | Analytical lens, not a protocol feature. On-chain price/volume series on VIZ make signal-vs-noise decomposition feasible for analysts. Not needed in core. |
+| 13 | **Nowcasting** | ⚪ Inherent | Onix prices update continuously per bet (~3s VIZ blocks), giving real-time nowcast estimates. Inherent to any live AMM market; no extra mechanism. |
+| 14 | **Price Discovery** | ✅ Resolved | CPMM and LMSR-softmax are continuous price-discovery engines; price coherence (`Σ price = 1`) holds *by construction* with no arbitrage/split-merge layer needed. |
+| 15 | **Probability Infrastructure** | ✅ Resolved | This is essentially Onix's thesis on VIZ: prediction markets as **first-class consensus operations** (`pm_*`), not smart contracts — a base-layer probability primitive. Directly the migration goal. |
+| 16 | **Superforecasting** | ⚪ Inherent | Individual-skill concept; Onix rewards accurate bettors via the losers→winners payout. Position transfers + reputation could support superforecaster identity, but it's a participant trait, not protocol logic. |
+| 17 | **Wisdom of Crowds** | ✅ Resolved | The mechanism Onix monetizes. Risk-free LP lowers the barrier so more of the crowd participates, sharpening the aggregate. Core to the design rationale. |
+| 18 | **Yes Bias** | 🟡 Partial | Behavioral tilt toward "Yes." Onix's symmetric CPMM and profit-only time penalty don't structurally favor Yes, but they don't correct human bias either. Mitigated by liquidity depth; a measurement concern. |
+
+---
+
+## 2. Mechanism Design
+
+| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed |
+|---|---------|---------|--------------------------------------------------|
+| 19 | **Binary Contracts** | ✅ Resolved | Onix Binary = CPMM (`x·y=k`) on two outcomes. AM-GM proof guarantees `reserve_a+reserve_b ≥ L`, so LP principal is covered. This is the primary market type. |
+| 20 | **Combinatorial Prediction Markets** | ➖ Not needed (today) | LMSR decomposes naturally over combinatorial spaces, but Onix Multi caps at 3–10 *independent* outcomes and deliberately omits CTF split/merge. Combinatorial/conditional bundles are explicitly out of scope; not required for the LP-guarantee model. |
+| 21 | **Incentive Compatibility** | ✅ Resolved | LMSR inherits IC from the log scoring rule (truth-telling dominant). Onix adds incentive alignment via bonded oracles (insurance > manipulation profit), losers-fund-winners settlement, and time-weighted LP rewards. |
+| 22 | **Keynesian Beauty Contest** | ✅ Resolved (market) / ⚪ (dispute layer — accepted by design) | KBC is a pathology of *relative/peer scoring*. The Onix **market** layer pays bettors against external ground truth (parimutuel), so it is structurally anti-KBC — you profit by *deviating* from the crowd price when it's wrong. The only KBC exposure is the **stake-weighted committee dispute vote** (a peer mechanism). **Decision: commit-reveal dispute voting will NOT be implemented** — a committee dispute is an **open public hearing**, and the DAO's credibility depends on resolving disputes as transparently as possible; hiding ballots would erode that trust. The residual KBC risk is accepted and structurally small: voters are **not paid** for matching the majority (no bandwagon bounty) and **ballots are revisable** until close (so honest updates on new evidence are expected, not suppressed). Pooled DAO members are also **enfranchised** (lazy-pool stake → vesting-shares, HF14). See [Mitigations §](#mitigations-for-the-reflexivity-family). |
+| 23 | **LMSR** | ✅ Resolved (the key innovation) | The concept file notes LMSR *failed for binaries* (permanent loss on the 0/1 boundary). Onix's answer: **use CPMM for binary, and use LMSR only for multi where the maker is NOT the counterparty** — parimutuel settlement pays winners from losers, so the LMSR subsidy is never at risk (`LP max loss = 0` vs `b·ln(N)`). This is the central design move. |
+| 24 | **LOX (Log-Odds Excess Lateness)** | ➖ Not needed | A specialized scoring/lateness metric. Onix instead uses a **quadratic time penalty on profit** to handle late-bet incentives — a simpler, settlement-time mechanism. LOX scoring is not part of the model. |
+| 25 | **Market Manipulation** | 🟡 Partial | Bonded oracle (bond must exceed manipulation profit), DPoS-validated operations, and committee dispute arbitration raise manipulation cost. Price manipulation via large bets is bounded by depth and — on **batch/commit-reveal markets (now live)** — by uniform-price settlement that neutralises speed-based sniping (the "sniper's tax"); active surveillance is still roadmap. |
+| 26 | **Market Scoring Rules** | ✅ Resolved | Onix Multi is a market scoring rule (LMSR) implementation, repurposed with parimutuel settlement. Directly used. |
+| 27 | **Multi-Outcome Markets** | ✅ Resolved | Onix Multi handles N=3–10 via LMSR softmax pricing + parimutuel payout, with `b = S/ln(N)`. First-class market type. |
+| 28 | **Parimutuel Markets** | ✅ Resolved (foundational) | Settlement in *both* market types is parimutuel: losers' forfeited stakes form `winners_pool`, distributed by token share. This is what makes the LP guarantee structural rather than insured. |
+| 29 | **Peer Prediction** | ➖ Not needed | Truth-telling-without-ground-truth schemes. Onix relies on bonded oracles + committee dispute, not peer-prediction scoring. Could inform subjective-market resolution but not used. |
+| 30 | **Position Collateralization** | ✅ Resolved (+ opt-in leverage, live) | By default every bet is fully prepaid (full amount enters reserves; no fees at bet time) — total collateralization by construction. Onix now **also** ships the concept's "next level": an **opt-in leverage subsystem** (`pm_leverage_open/close/convert`, kill-switch `pm_leverage_enabled`, default off). Margin is a **loan from the Lazy Pool** (no token emission — zero-sum preserved), so the position stays fully collateralized *from the system's view*. The binary "jump risk" that breaks CLOB liquidation engines (per this concept) is handled by **liquidating against pre-bet reserves**: opposing-bet / settlement force-close recovers `min(cancel_value, obligation) ≥ loan`, so the pool gets loan + interest back; the **only** bounded bad-debt path is a same-side `pm_cancel_bet` (Case B). The liquidation cascade is deliberately **not** gated by the kill-switch, so toggling leverage off never strips protection from open positions. |
+| 31 | **Proper Scoring Rules** | ✅ Resolved | LMSR is the cost-function dual of the log proper scoring rule; Onix Multi inherits its truthful-elicitation property. |
+| 32 | **Reflexivity** | 🟡 Partial (mitigated) | Parent of endogeneity. Mitigated by the same toolkit — **commit-reveal/batch betting + `endogeneity_tier` are now live**, exogenous resolution still roadmap — **plus on-chain creator bans** (`pm_creator_ban_object`) for harmful reflexivity (assassination/"hit" markets, propaganda markets that create a "constituency for the outcome"); the prohibited-category *list* itself stays client-layer. Deep risk-free LP depth also raises the cost of newsworthy price manipulation. See [Mitigations §](#mitigations-for-the-reflexivity-family). |
+
+---
+
+## 3. Liquidity & Trading
+
+> **Headline:** Onix has **no order book and no inventory-bearing market maker**. A large class of these concepts exists specifically to manage CLOB/maker inventory risk and therefore **do not apply** to Onix.
+
+| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed |
+|---|---------|---------|--------------------------------------------------|
+| 33 | **Adverse Selection** | ✅ Resolved (reframed) | The classic problem (informed flow bankrupts the maker) **cannot bankrupt the Onix LP**: winners are paid from losers, never from LP principal (AM-GM / parimutuel guarantee). Informed traders extract from other *bettors*, not the LP. Eliminates the core LP failure mode. |
+| 34 | **Arbitrage** | ⚪ Inherent | Intra-market arbitrage is unnecessary: price coherence (`Σ price = 1`) holds by construction in both CPMM and LMSR-softmax. No split/merge arbitrage layer needed. |
+| 35 | **Batched Auctions** | ✅ Resolved (opt-in, live) | Implemented as a **per-market uniform-price batch** (`mode=1` bets + commit-reveal → `pm_batch_settle` at each `pm_batch_epoch_blocks` epoch); only the **net residual** moves the AMM, so all same-side fills clear at one price and speed-based sniping (the "sniper's tax") is neutralised. Per-market `allow_batch` + median kill-switch `pm_commit_reveal_enabled`; **binary only** today (multi forces `allow_instant_bet`). The LP `Σreserve ≥ L` invariant is untouched — each batch is one valid CPMM transition. |
+| 36 | **Bid-Ask Spread** | ➖ Not needed | No order book → no quoted spread. "Cost of trading" appears as CPMM/LMSR slippage, governed by liquidity depth, not maker spreads. Concept doesn't map. |
+| 37 | **Bonding Trades** | ⚪ Inherent | Bets *are* bonded trades: capital is committed into reserves and only released at resolution (or via cancellation/transfer). Native behavior. |
+| 38 | **Continuous Double Auction** | ➖ Not needed | CDA is the CLOB model Onix explicitly rejects in favor of AMM pricing. Not used. |
+| 39 | **Covariance Markets** | ➖ Not needed | Trading correlation between events requires combinatorial/conditional structure Onix deliberately omits. Out of scope. |
+| 40 | **Cross-Platform Arbitrage** | 🟡 Partial | Onix prices can diverge from Polymarket/Kalshi; arbitrage across platforms is possible but external to the protocol. VIZ's open API + headless client make price data accessible; no native cross-platform bridge. |
+| 41 | **Execution Quality** | ✅ Resolved (reframed) | No partial fills/queue position. Execution quality = deterministic slippage + optional `min_tokens`/`min_return` slippage guards, validated at consensus. Predictable by construction. |
+| 42 | **Gap Risk** | ✅ Resolved (for LP) | Gap risk (sudden jump to 0/1 wiping the maker) is the failure mode Onix's structural LP guarantee eliminates — the LP never holds the losing side's terminal risk. Bettors still bear their own outcome risk (as intended). |
+| 43 | **Hedging** | 🟡 Partial | Bettors can hedge by taking offsetting positions, transferring positions (`pm_transfer_position`), bet cancellation (if allowed) via reverse CPMM, and now **opt-in leverage** (`pm_leverage_open/convert`) for capital-efficient offsetting. No native multi-leg derivatives; basic + leveraged hedging is possible. |
+| 44 | **Implied Correlation** | ➖ Not needed | Requires multi-event/combinatorial markets Onix omits. Out of scope. |
+| 45 | **Insider Trading** | 🟡 Partial / 🏛 Client | Protocol can't detect insider info; mitigated by time penalty (late-info bets earn less profit) and bonded-oracle resolution. KYC/surveillance to police insiders is a **client-layer** responsibility (regulated clients). |
+| 46 | **Kelly Criterion** | ⚪ Inherent | A bettor staking strategy, not a protocol feature. Onix exposes clean probabilities and full collateralization so Kelly sizing is computable by participants; the new **opt-in leverage** lets a bettor act on a fractional-Kelly edge with margin (pool-funded, liquidation-bounded). No core involvement beyond exposing the primitives. |
+| 47 | **Liquidity Fragmentation** | 🟡 Roadmap | Per-market pools fragment liquidity today. The whitepaper's top-priority roadmap item — **shared/category-level AMM pools** — is the architectural fix. Lazy Pool already mutualizes *deposits* across markets. |
+| 48 | **Liquidity Provision** | ✅ Resolved (core differentiator) | Risk-free LP is the headline: principal structurally guaranteed, time-weighted fee rewards, Lazy Pool auto-allocation + MasterChef accounting. Solves the "LPs lose money" problem that motivates the whole protocol. |
+| 49 | **Market Making** | ✅ Resolved (reframed) | No active maker needed — the AMM + LP pool *is* the maker, and it bears no inventory risk. "Market making" collapses into passive, risk-free liquidity provision. |
+| 50 | **Minimum Viable Liquidity** | ✅ Resolved | Enforced floor: min initial liquidity 100 VIZ; Lazy Pool auto-seeds every new market with `free_balance × allocation_%`. MVL is structurally bootstrapped rather than left to chance. |
+| 51 | **Order Book** | ➖ Not needed | Onix is AMM-based; no order book by design. |
+| 52 | **Orderflow Arbitrage** | ➖ Not needed | No order book / no PFOF-style flow routing → not applicable. |
+| 53 | **Relative Value Trading** | ➖ Not needed | Cross-instrument RV requires correlated/combinatorial markets Onix omits. Out of scope. |
+| 54 | **Retail Flow** | ✅ Resolved (reframed) | In CLOB models retail flow subsidizes maker losses to toxic flow. In Onix there is no maker to protect — retail and informed bettors all pay into the same parimutuel pool; LP is indifferent. The "retail-vs-toxic" tension dissolves at the LP layer. |
+| 55 | **Semantic Tick Size** | ➖ Not needed | Tick granularity is a CLOB concept. Onix prices are continuous AMM functions; precision is the fixed mVIZ unit (1/1000). No tick design needed. |
+| 56 | **Temporal Arbitrage** | 🟡 Partial | Betting earlier vs later carries different risk; Onix's **time penalty on profit** is precisely the mechanism that prices in lateness, dampening "wait-for-certainty" temporal arbitrage. Not eliminated, but explicitly disincentivized. |
+| 57 | **Time Arbitrage** | 🟡 Partial | Same family as #56 — exploiting information timing. Quadratic time penalty + ~3s block cadence reduce, but don't remove, the edge. Addressed by design intent. |
+| 58 | **Toxic Flow** | ✅ Resolved (for LP) | The defining CLOB/LMSR problem (sniper clears the book at 10¢ on a 99¢ outcome, maker eats 80¢) **does not hit the Onix LP** — payouts come from losers' stakes, and the LP subsidy is returned unconditionally. Toxic flow simply means informed bettors win the parimutuel pool, as intended. Major structural win. |
+| 59 | **Wash Trading** | 🟡 Partial / 🏛 Client | No fees at bet time removes one wash incentive, but volume-faking is still possible; transfers are pure reassignment (no fee farming there). Detection/surveillance is a client + roadmap surveillance concern. |
+
+---
+
+## 4. Oracle & Resolution
+
+| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed |
+|---|---------|---------|--------------------------------------------------|
+| 60 | **Corruption Value Multiple (CVM)** | ✅ Resolved (by design principle) | The protocol's explicit security invariant: oracle **insurance bond must exceed potential manipulation profit**. Risk factor (insurance/bets ratio) feeds the composite trust score. CVM is directly the bonding rationale. |
+| 61 | **Dispute Resolution** | ✅ Resolved | Full system: 12h grace, `dispute_fee`, mandatory oracle response, per-market resolver (`dispute_mode==0` committee stake-weighted vote / `==1` named resolver), insurance slashing, 3-outcome no-contest disputes, 14-day auto-close anti-freeze, on-chain creator/oracle bans. Among the most fully specified parts. **HF14 addition:** lazy-pool depositors keep their dispute vote weight (pool NAV → vesting-shares, added to `effective_vesting_shares`). |
+| 62 | **Oracle Design** | ✅ Resolved | Bonded oracle model: registration fee, ≥5000 VIZ insurance, explicit acceptance, evidence-backed resolution, 14-metric reputation, freshness decay, ban mechanics. Core subsystem. |
+| 63 | **Resolution Criteria** | 🟡 Partial / 🏛 Client | Market question/criteria live in `url`/description (custom_json, display-only). Protocol enforces *process* (who resolves, disputes) but not criterion *quality* — ambiguous criteria are a creator/client responsibility, policed retroactively via disputes + **on-chain creator bans** (`pm_creator_ban_object`, now live). |
+| 64 | **Self-Resolving Markets** | ➖ Not needed (today) | Onix resolution is oracle-driven, not algorithmic self-resolution. Automated data oracles (Chainlink-style feeds) are a high-priority roadmap item for objective markets, which would approximate self-resolution. |
+| 65 | **UMA Protocol** | ➖ Not needed (replaced) | UMA's optimistic oracle (used by Polymarket) is functionally replaced by Onix's bonded oracle + VIZ committee dispute model. Same problem, native VIZ solution — no external oracle dependency. |
+
+---
+
+## 5. Governance & Decisions
+
+| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed |
+|---|---------|---------|--------------------------------------------------|
+| 66 | **Attention Markets** | ➖ Not needed | Trading attention/virality is a distinct product; Onix is event-resolution focused. Could be a client-layer market category, not core. |
+| 67 | **Conditional Tokens** | ➖ Not needed (explicit) | Whitepaper §7.2 argues CTF split/merge is **architecturally unnecessary** — price coherence is mathematical, not enforced by tokens. The *one* useful CTF feature (transferable positions) is reimplemented natively as `pm_transfer_position` with encrypted memos. Deliberately omitted. |
+| 68 | **Decision Markets** | 🟡 Possible | Onix Multi could express decision markets, but conditional "if-policy-then-metric" structure isn't native (no conditional tokens). Buildable at client layer; not a core primitive. |
+| 69 | **Futarchy** | 🟡 Possible (client) | Concept file: futarchy = decision markets on conditional futures. Onix lacks native conditional markets, so full futarchy isn't supported in core. VIZ's stake-weighted committee already governs *parameters*; governance-by-market would be a client/roadmap construction. |
+| 70 | **Hyperstition Markets** | ➖ Not needed (deliberate) | Reflexivity-as-a-feature (coordinate, don't forecast). This is a *design choice, not a bug to fix*: Onix's requirement that an outcome be **externally verifiable by a bonded oracle** structurally excludes hyperstition markets by default. Could exist as a separate client-layer "coordination market" product with milestone resolution, but is not a core target. See [Mitigations §](#mitigations-for-the-reflexivity-family). |
+| 71 | **Impact Markets** | ➖ Not needed | Retrospective-funding/impact-certificate markets are a separate domain. Possible client-layer category; not core. |
+| 72 | **No-Loss Prediction Markets** | 🟡 Adjacent | Onix isn't no-loss for *bettors* (losers forfeit stakes — that funds winners). But it **is** "no-loss" for *LPs* (principal guaranteed). The yield-funded no-loss variant (stake yield, principal returned) is a different model; LP-side no-loss is already delivered. |
+| 73 | **Opportunity Markets** | ⚪ Inherent (adjacent) | The Lazy Pool's **opportunity-cost protection** (graduated recall, active-market penalty, fault stamps) addresses capital-opportunity-cost directly — though "opportunity markets" as a product category is out of scope. |
+
+---
+
+## 6. Business & Platforms
+
+| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed |
+|---|---------|---------|--------------------------------------------------|
+| 74 | **AI agents** | 🟡 Roadmap | Headless client + open VIZ operations make programmatic agents (bettors, LPs, automated oracles) straightforward. AI-driven liquidity/resolution is a natural extension, not yet specified. |
+| 75 | **Cross-subsidization** | ⚪ Inherent | The Lazy Pool cross-subsidizes liquidity across many markets from one deposit; MasterChef `reward_per_share` shares fee yield. Cross-subsidization is built into the pool economics. |
+| 76 | **Demand markets** | ➖ Not needed | Markets that gauge/aggregate demand are a product category; not a core Onix primitive. Client-layer. |
+| 77 | **Distribution moat** | 🟡 Strategy | Onix's moat is risk-free LP yield + VIZ-native infrastructure (the flywheel). Distribution (Telegram WebApp today → headless web client) is a go-to-market concern, partly addressed by platform-independence post-migration. |
+| 78 | **Election markets** | 🏛 Client | Supported as ordinary binary/multi markets; their *legality* is a jurisdictional-client matter (whitelisted oracles, category filters). Protocol-neutral. |
+| 79 | **Event contracts** | ⚪ Inherent | Every Onix market *is* an event contract. The regulatory classification of these contracts is a client/legal question, not protocol logic. |
+| 80 | **Federal preemption** | 🏛 Client (N/A to protocol) | Concept file: turns on whether US event contracts are "swaps." VIZ DLT is **infrastructure, not an operator** (whitepaper §6.2) — like Bitcoin is a ledger. Legal obligations attach to clients, not consensus. Not a protocol concern. |
+| 81 | **Long-tail markets** | ✅ Resolved | The exact niche LMSR's bounded-loss enables — and Onix makes it *risk-free* to seed via Lazy Pool auto-allocation + min-liquidity floor. Long-tail viability is a core selling point. |
+| 82 | **Market structure** | ✅ Resolved (defined) | Onix defines a clear structure: AMM pricing, parimutuel settlement, bonded oracles, DPoS-governed parameters, consensus-level ops. A coherent, novel market structure vs CLOB platforms. |
+| 83 | **Market surveillance** | 🟡 Roadmap / 🏛 Client | Full on-chain audit trail (`market_log`, every op consensus-validated) makes surveillance *possible* by anyone. Active surveillance/enforcement is a client + roadmap concern. |
+| 84 | **Network effects** | 🟡 Strategy | The flywheel (risk-free LP → depth → bettors → fees → more LP) is the intended network effect. Shared liquidity pools (roadmap) strengthen it. Go-to-market, not protocol mechanics. |
+| 85 | **Parlays** | ➖ Not needed | Multi-leg combined bets need conditional/combinatorial structure Onix omits. Out of scope (could be a client construction over independent markets). |
+| 86 | **Platform competition** | 🟡 Strategy | Competes on the unique "passive yield without impermanent loss" angle vs Polymarket/Kalshi (whitepaper §7.1 comparison table). Strategic positioning, not protocol logic. |
+| 87 | **Polymarket** | ⚪ Reference | The primary benchmark. Onix differs on every axis: CPMM/LMSR vs CLOB, zero LP risk vs inventory risk, native ops vs Polygon contracts, bonded oracle vs UMA, no CTF. Used as comparison, not adopted. |
+| 88 | **Regulatory arbitrage** | 🏛 Client | Jurisdictional-client model means each region builds its compliant (or permissionless) client on neutral VIZ rails. Regulatory positioning lives entirely at the client layer. |
+| 89 | **Regulatory classification** | 🏛 Client | Whether markets are swaps/gaming/securities is decided per-jurisdiction at the client layer; the protocol is classification-neutral (same `pm_*` ops for permissionless and regulated clients). Not a protocol concern. |
+
+---
+
+## Summary — what the Onix-on-VIZ design actually changes
+
+**Solved structurally (the core wins):**
+- LP-side **adverse selection, toxic flow, gap risk, impermanent loss, market-maker inventory risk** → all eliminated because winners are paid only from losers' forfeited stakes and LP principal is returned unconditionally (CPMM AM-GM proof; LMSR parimutuel settlement).
+- **LMSR's binary-market failure** → sidestepped by using CPMM for binary and confining LMSR to multi-outcome markets where the maker is not the counterparty.
+- **Liquidity provision, minimum viable liquidity, long-tail viability** → risk-free LP + Lazy Pool auto-allocation.
+- **Oracle design, dispute resolution, CVM** → bonded oracle + 14-metric reputation + stake-weighted committee disputes.
+- **Price discovery, arbitrage, conditional tokens** → price coherence is mathematical (`Σ price = 1`), so no order book, no split/merge, no internal arbitrage layer needed.
+
+**Not needed / deliberately omitted:** order book, CDA, bid-ask spread, semantic tick size, orderflow arbitrage, CTF split/merge, combinatorial/covariance/correlation/relative-value/parlay markets, UMA, peer prediction, LOX.
+
+**Pushed to the jurisdictional client layer:** federal preemption, regulatory classification/arbitrage, election-market legality, KYC/insider-trading enforcement, surveillance.
+
+**Newly implemented since the spec-era mapping (now live on-chain, HF14):** opt-in batch auctions + commit-reveal betting (binary), the opt-in leverage subsystem (position collateralization), the Lazy Pool, `endogeneity_tier`, on-chain creator bans, per-bettor/leverage settlement vops + plugin API, and lazy-pool governance weight in PM disputes + DAO committee-request voting.
+
+**On the VIZ roadmap (partial today):** shared/category liquidity pools (fixes fragmentation), automated data oracles (→ self-resolving objective markets), distribution markets, AI agents. *(Note: commit-reveal **dispute** voting is **not** on this list — it is deliberately rejected; dispute hearings stay public. Commit-reveal **betting** is already live.)*
+
+**Still open / behavioral (mitigated, not eliminated):** longshot/yes bias, market manipulation via depth, cross-platform arbitrage. The reflexivity family (endogeneity, reflexivity, KBC, hyperstition) has a concrete mitigation plan — see below.
+
+---
+
+## Mitigations for the reflexivity family
+
+Endogeneity, reflexivity, the Keynesian beauty contest (KBC), and hyperstition are **one root problem at different layers**: the market/price influences the outcome it measures. One small set of primitives addresses all four.
+
+### Root-cause map
+
+| Layer | Concept | Channel |
+|-------|---------|---------|
+| Forecaster-level | **Keynesian Beauty Contest** | herding to visible consensus in *relative/peer scoring* |
+| Market-level | **Endogeneity** | market *existence/visibility* changes behavior (category-specific) |
+| Market-level | **Reflexivity** | general price↔reality feedback; manipulation-as-propaganda |
+| By-design | **Hyperstition** | reflexivity used *intentionally* to coordinate an outcome |
+
+### Mitigation primitives
+
+| Primitive | Status | Fixes | Notes |
+|-----------|--------|-------|-------|
+| **Commit-reveal dispute voting** | **rejected (will NOT be built)** | KBC | A committee dispute is an **open public hearing**: `pm_dispute_vote` is a public ballot and **stays that way by design** — DAO credibility depends on transparent adjudication. Ballots are **revisable** until close (re-vote overwrites) so voters update honestly on new evidence; KBC residual is accepted (voters aren't paid for matching the majority). |
+| **Commit-reveal betting (batched)** | **live (opt-in, binary)** | endogeneity, reflexivity, info-asymmetry | `pm_commit_bet`/`pm_reveal_bet`/`pm_batch_settle` hide in-flight order direction/size so the public price doesn't "leak" during the betting window (kills the thermostat channel). Settled as a uniform-price **batch** (see below). |
+| **`endogeneity_tier` market field** | **live field** | endogeneity | Oracle tags tier 1 (econ data — clean), 2 (sports/scheduled), 3 (political/social — risky); UI surfaces the reflexive-risk level; clients can restrict tier-3. |
+| **Exogenous resolution (automated data oracles)** | roadmap (high) | endogeneity, reflexivity | Resolution bound to an external feed (BLS/Fed/sports API) the market can't influence → clean thermometer. |
+| **Prohibited-category list + creator ban** | **creator ban live on-chain**; list client-layer | harmful reflexivity, hyperstition | Block markets where YES creates a "constituency for the outcome" (assassination/"hit"/terror markets, propaganda markets). Enforced via on-chain creator ban (`pm_creator_ban_object`) + client category filter. |
+| **Deep risk-free LP liquidity** | core today | manipulation-driven reflexivity | The flywheel makes the book deep, so moving price for a "newsworthy" manipulated headline is expensive. |
+
+### KBC: why the market layer is already safe
+
+KBC is a pathology of **relative scoring** (pay for closeness to peers → herd to peers). Onix's market layer pays bettors against **external ground truth via parimutuel settlement** — you are rewarded for *deviating* from a wrong crowd price, not for matching it. So the bettor layer is structurally anti-KBC. The only relative/peer mechanism in the protocol is the **stake-weighted committee dispute vote**, and the residual KBC risk there is **accepted by design** — the dispute is kept an open public hearing (no commit-reveal) because DAO credibility depends on transparent adjudication; what bounds the risk instead is that voters aren't paid for matching the majority and ballots stay revisable as evidence comes in.
+
+### Commit-reveal vs. the CPMM `a·b=k` invariant
+
+CPMM is **path-dependent** (tokens depend on reserves at execution time), so commit-reveal *cannot* be done bet-by-bet against the live curve — reveal ordering would re-introduce MEV and leak price. The fix (and why §8.3 pairs commit-reveal with the batch-auction model): **stop updating the curve per-bet; update it once per epoch via a uniform-price batch settlement.**
+
+1. **Commit:** submit `hash(side, amount, salt, min_tokens)` and escrow `amount`.
+2. **Reveal:** reveal `(side, amount, salt)`; reveals are collected but **not applied** until epoch close (seeing others' reveals is useless — yours is already committed).
+3. **Settle once:** opposing flow (`A_in` vs `B_in`) nets between bettors at a single clearing price `p*`; only the **net residual** moves the AMM, so `k` is recomputed **once**. All A-fills get `p*`, all B-fills get `1−p*` → no intra-batch ordering advantage.
+4. **`min_tokens` floor:** since price is invisible at commit time, a per-bet token floor is mandatory; if `tokens < min_tokens` at settlement, the bet is rejected and refunded from escrow.
+5. **Anti-griefing:** non-reveal forfeits a penalty from escrow to the LP-fee/DAO pool, killing the "commit optionality, reveal only winners" attack.
+
+**The LP guarantee is untouched:** each batch settlement is still a valid CPMM transition, so AM-GM `reserve_a + reserve_b ≥ L` still holds. Only the *granularity* of curve updates changes (per-bet → per-epoch). Onix Multi is analogous via the aggregated LMSR cost function.
+
+**Phasing:** (1) uniform-price batch auctions first — already kill ordering MEV/front-running cheaply; (2) commit-reveal hiding on top — adds in-flight confidentiality, enabled selectively for tier-3 (endogeneity-sensitive) markets. **Both (1) and (2) are now implemented on-chain for binary markets** (multi still forces instant betting — no LMSR batch yet).
+
+### Net verdict change
+
+| Concept | Before | After |
+|---------|--------|-------|
+| Keynesian Beauty Contest | 🔴 Open | ✅ market / ⚪ dispute — votes public **by design** (no commit-reveal; revisable ballots, unpaid voters, pooled voters enfranchised) |
+| Endogeneity | 🔴 Open | 🟡 mitigated — `endogeneity_tier` + commit-reveal/batch **live**; exogenous oracles roadmap |
+| Reflexivity | 🔴 Open | 🟡 mitigated — creator ban **live on-chain**; category list client-layer |
+| Hyperstition | 🔴 Open | ➖ excluded by design (optional client product) |
diff --git a/.qoder/plans/pm-plan/theory_concepts/INDEX.md b/.qoder/plans/pm-plan/theory_concepts/INDEX.md
new file mode 100644
index 0000000000..cdb594cb95
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/INDEX.md
@@ -0,0 +1,111 @@
+# Prediction Market Theory Concepts
+
+> Extracted from [PM Atlas](https://www.pmatlas.xyz/concepts) — 90 concepts across 6 categories.
+
+## Information Theory
+
+- [Brier Score](brier-score.md)
+- [Calibration](calibration.md)
+- [Credibility Markets](credibility-markets.md)
+- [Distribution Markets](distribution-markets.md)
+- [Endogeneity](endogeneity.md)
+- [Forecasting Accuracy](forecasting-accuracy.md)
+- [Info Finance](info-finance.md)
+- [Information Aggregation](information-aggregation.md)
+- [Information Asymmetry](information-asymmetry.md)
+- [Legibility](legibility.md)
+- [Longshot Bias](longshot-bias.md)
+- [Noise Decomposition](noise-decomposition.md)
+- [Nowcasting](nowcasting.md)
+- [Price Discovery](price-discovery.md)
+- [Probability Infrastructure](probability-infrastructure.md)
+- [Superforecasting](superforecasting.md)
+- [Wisdom of Crowds](wisdom-of-crowds.md)
+- [Yes Bias](yes-bias.md)
+
+## Mechanism Design
+
+- [Binary Contracts](binary-contracts.md)
+- [Combinatorial Prediction Markets](combinatorial-prediction-markets.md)
+- [Incentive Compatibility](incentive-compatibility.md)
+- [Keynesian Beauty Contest](keynesian-beauty-contest.md)
+- [LMSR (Logarithmic Market Scoring Rule)](lmsr-logarithmic-market-scoring-rule.md)
+- [LOX (Log-Odds Excess Lateness)](lox-log-odds-excess-lateness.md)
+- [Market Manipulation](market-manipulation.md)
+- [Market Scoring Rules](market-scoring-rules.md)
+- [Multi-Outcome Markets](multi-outcome-markets.md)
+- [Parimutuel Markets](parimutuel-markets.md)
+- [Peer Prediction](peer-prediction.md)
+- [Position Collateralization](position-collateralization.md)
+- [Proper Scoring Rules](proper-scoring-rules.md)
+- [Reflexivity](reflexivity.md)
+
+## Liquidity & Trading
+
+- [Adverse Selection](adverse-selection.md)
+- [Arbitrage](arbitrage.md)
+- [Batched Auctions](batched-auctions.md)
+- [Bid-Ask Spread](bid-ask-spread.md)
+- [Bonding Trades](bonding-trades.md)
+- [Continuous Double Auction](continuous-double-auction.md)
+- [Covariance Markets](covariance-markets.md)
+- [Cross-Platform Arbitrage](cross-platform-arbitrage.md)
+- [Execution Quality](execution-quality.md)
+- [Gap Risk](gap-risk.md)
+- [Hedging](hedging.md)
+- [Implied Correlation](implied-correlation.md)
+- [Insider Trading](insider-trading.md)
+- [Kelly Criterion](kelly-criterion.md)
+- [Liquidity Fragmentation](liquidity-fragmentation.md)
+- [Liquidity Provision](liquidity-provision.md)
+- [Market Making](market-making.md)
+- [Minimum Viable Liquidity](minimum-viable-liquidity.md)
+- [Order Book](order-book.md)
+- [Orderflow Arbitrage](orderflow-arbitrage.md)
+- [Relative Value Trading](relative-value-trading.md)
+- [Retail Flow](retail-flow.md)
+- [Semantic Tick Size](semantic-tick-size.md)
+- [Temporal Arbitrage](temporal-arbitrage.md)
+- [Time Arbitrage](time-arbitrage.md)
+- [Toxic Flow](toxic-flow.md)
+- [Wash Trading](wash-trading.md)
+
+## Oracle & Resolution
+
+- [Corruption Value Multiple (CVM)](corruption-value-multiple.md)
+- [Dispute Resolution](dispute-resolution.md)
+- [Oracle Design](oracle-design.md)
+- [Resolution Criteria](resolution-criteria.md)
+- [Self-Resolving Markets](self-resolving-markets.md)
+- [UMA Protocol](uma-protocol.md)
+
+## Governance & Decisions
+
+- [Attention Markets](attention-markets.md)
+- [Conditional Tokens](conditional-tokens.md)
+- [Decision Markets](decision-markets.md)
+- [Futarchy](futarchy.md)
+- [Hyperstition Markets](hyperstition-markets.md)
+- [Impact Markets](impact-markets.md)
+- [No-Loss Prediction Markets](no-loss-prediction-markets.md)
+- [Opportunity Markets](opportunity-markets.md)
+
+## Business & Platforms
+
+- [AI agents](ai-agents.md)
+- [Cross-subsidization](cross-subsidization.md)
+- [Demand markets](demand-markets.md)
+- [Distribution moat](distribution-moat.md)
+- [Election markets](election-markets.md)
+- [Event contracts](event-contracts.md)
+- [Federal preemption](federal-preemption.md)
+- [Long-tail markets](long-tail-markets.md)
+- [Market structure](market-structure.md)
+- [Market surveillance](market-surveillance.md)
+- [Network effects](network-effects.md)
+- [Parlays](parlays.md)
+- [Platform competition](platform-competition.md)
+- [Polymarket](polymarket.md)
+- [Regulatory arbitrage](regulatory-arbitrage.md)
+- [Regulatory classification](regulatory-classification.md)
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/adverse-selection.md b/.qoder/plans/pm-plan/theory_concepts/adverse-selection.md
new file mode 100644
index 0000000000..acbbc75fac
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/adverse-selection.md
@@ -0,0 +1,92 @@
+# Adverse Selection
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Adverse Selection](https://www.pmatlas.xyz/concepts/adverse-selection)
+
+---
+
+## Definition
+
+**Quick definition.** The risk that counterparties trade against you because they hold superior information, causing systematic losses for the liquidity provider. In prediction markets adverse selection is unusually severe because some counterparties hold *near-perfect* information about outcomes that resolve cleanly to $0 or $1.
+
+## Key Insights
+
+- semaji.eth's framing: "Conditional on someone trading with you, you should be less confident your trade was good." Adverse selection is the single biggest reason classical market-making models fail in prediction markets.
+- semaji.eth ranks: Indian options easy → crypto medium → prediction markets *legendary*. The reason is gap risk: an informed counterparty can lift an entire order book on a tweet because they know the resolution.
+- sybilpm's "Sniper's Tax": sniping geopolitical strike markets at 10c. In traditional markets sniping costs basis points; in PMs it costs **80 cents on the dollar** when 0.10 → 0.99 on a single tweet. The concrete case: on January 3, 2026 a trader "dudukos" cleared the entire order book of "Will Israel strike Gaza on January 3, 2026?" in a single trade, buying from $0.10 all the way up to $0.80 · then repeated the pattern on Jan 10, 11, 12 across dozens of Israel-strike markets. He didn't know more than the MMs; he was just slightly faster to react.
+- sybilpm's MM expected-profit math: **E[π] = (s/2 · V(s)) − P_news · L_snipe**. When P_news is high (geopolitical, breaking-news markets) the sniping term dominates and MMs only have two responses · widen s or pull quotes. "This isn't a failure of will. It's the only rational response."
+- sybilpm on the "unhedgeable problem": if you're an MM holding too much Apple, you sell Nasdaq futures. If you're holding too much "Will Israel strike Lebanon today?" · there's no correlated instrument. You're just exposed.
+- Mitts & Ofir screened **93,000 Polymarket markets across 50,000 wallets, Feb 2024–Feb 2026** and flagged 210,718 wallet-market pairs with a **69.9% win rate** · more than 60 standard deviations above chance under a permutation test. Aggregate anomalous profit ≈ **$143M**, a conservative lower bound (buy-side only, excludes positions <$500, can't detect deliberately small trades). Their 5-signal composite score combined cross-sectional bet size, within-trader bet size, profitability, pre-event timing, and directional concentration.
+- Deleep et al. ("How Wise Is the Crowd?"): whales are not the sharpest participants · heavily capitalized traders systematically bleed expected value to small-order traders, likely driven by ideological conviction rather than informational edge. The classic favorite-longshot bias may be a statistical artifact masking a pervasive YES bias.
+- functionSPACE: "noisy traders fund the probability space" · they are not exit liquidity but the necessary fuel that makes adverse-selection-tolerant market making possible.
+- 0xnagu's LOX (log-odds excess lateness) metric: decomposes late volume into *hazard* (info arrives late) vs. *toxicity* (early entry is punished by adverse selection); boxing markets cluster with news markets behavior-wise.
+- Dougie's "Discovery vs Betrayal" framework: in distributed-truth markets (elections) informed traders sharpen the signal because no one holds the full answer; in concentrated-truth markets (earnings, planned events) insiders simply monetize a sealed result.
+- Nic Carter: insider trading is **inescapable**; platforms face a calibration problem · too permissive and noise traders flee perceiving rigging; too strict and informed flow disappears and prices decay into sentiment.
+- Hariharan proposes three layers of defense: platform-level detection + position limits scaled to account size; market-design mechanisms (dynamic spread widening, MM insurance pools); legal frameworks (compliance, CFTC guidance). His concrete example of catastrophic adverse selection: AlphaRaccoon predicted 22 of 23 Google Year-in-Search rankings correctly for >$1M PnL, and earlier $150k on the Gemini 3.0 release date.
+- Roan ("Why Prediction Markets Aren't Gambling? (The Math)"): the actionable diagnostic for adverse selection is **fill quality** · measure your realized fill vs. mid; if you're systematically getting filled worse than mid you're being adversely selected.
+- Human Invariant: FCFS order matching creates a latency arms race and forces defensive spread widening. "FCFS creates wider spreads and other negative externalities" · colocation arms race + defensive quote widening. Priority batch auctions (cancels → makers → takers) reduce this by letting makers update their quotes before snipers can fill stale ones.
+- Polymarket order book shows surface symmetry at top-of-book but systematic **ask-side skew at deeper levels** · MMs are pricing in asymmetric adverse selection (@allquantor).
+- Becker's category gradient maps adverse-selection intensity: Finance has a 0.17pp taker-maker gap (efficient); World Events is 7.32pp; Media 7.28pp; Entertainment 4.79pp. "Financial questions attract traders who think in probabilities and expected values rather than fans betting on their favorite team."
+- Lebron warning: large-scale prediction markets lack heterogeneous risk preferences for efficiency; they rely on continuous retail losses, with reflexivity creating incentives toward "negative, sensational outcomes."
+- Ruzicka on leverage: gap risk + adverse selection together explain why every team trying 10x or 20x leverage converges to 1x–1.5x.
+- Alan Wu: information markets are public goods; their liquidity costs fall on a narrow trader base, creating cross-subsidization opportunities (profitable markets fund socially valuable ones).
+- 23 Reasons (Lin): adverse selection sits alongside capital efficiency, oracle governance, and regulatory fragmentation as one of the four structural pillars of why prediction markets are stuck.
+- Nic Carter's core tension: "The social value of prediction markets derives from financially incentivizing insiders to divulge confidential information, but this collapses noise trader confidence in the market over time." Robin Hanson explicitly endorses the insider trade-off; Kalshi bans it in ToS; Polymarket has a catch-all law-violation clause but does not explicitly ban insider trading.
+
+## Notable Quotes
+
+> "Conditional on someone trading with you, you should be less confident your trade was good."
+> — *semaji.eth*
+
+> "In traditional markets, sniping costs basis points; in prediction markets, it costs 80 cents on the dollar."
+> — *sybilpm, "The Sniper's Tax"*
+
+> "Insider trading in prediction markets is an inescapable problem... platforms face a calibration problem: too permissive and noise traders flee perceiving rigging, too strict and informed flow disappears."
+> — *Nic Carter*
+
+> "The social value of prediction markets derives from financially incentivizing insiders to divulge confidential information, but this collapses noise trader confidence in the market over time."
+> — *Nic Carter*
+
+> "Conditional on someone trading with you, you should be less confident your trade was good. You weren't providing liquidity to a forecaster. You were exit liquidity for a bot."
+> — *sybilpm*
+
+> "Flagged traders achieved a 69.9% win rate"
+> — *a result that exceeds the null distribution of random chance by more than 60 standard deviations." · Mitts & Ofir, Harvard Corporate Governance forum*
+
+## Where It Matters
+
+Adverse selection is the master concept that explains nearly every other pathology: why spreads are wide, why AMMs lose money, why CLOBs concentrate liquidity in 23 pro market makers, why leverage is bounded at 1×, and why long-tail markets stay empty. Any platform serious about scaling beyond politics and sports must engineer specifically *for* asymmetric information · through batch auctions, dynamic fees, insurance pools, position limits, surveillance, and contract design that limits how much edge any one trader can extract.
+
+## Related Concepts
+
+- **Market making** · adverse selection is what MMs are paid (via the spread) to bear.
+- **Toxic flow** · adverse selection in motion: orders that systematically move against the LP.
+- **Insider trading** · the legal/ethical frame of one source of adverse selection.
+- **Gap risk** · the binary-specific amplifier.
+- **Information asymmetry** · the upstream cause.
+- **Batched auctions** · a leading mitigation.
+- **Kelly criterion** · sizing under edge; the inverse problem from the LP perspective.
+- **Retail flow** · the "good" flow that subsidizes losses from informed flow.
+- **Bid-ask spread** · partially a tax on adverse selection.
+
+## Sources
+
+- [How Prediction Markets Can Ascend](https://x.com/alanwu/article/2036438579824496906) — Alan Wu · Mar 25, 2026
+- [Discovery and Betrayal: Insiders in Prediction Markets](https://x.com/DougieDeLuca/article/2033910178341413105) — Dougie · Mar 18, 2026
+- [Noisy Traders Are Not Dumb Money](https://x.com/functionspaceHQ/article/2032397043579462028) — functionSPACE · Mar 13, 2026
+- [Polymarket Doesn't Have a Money Problem. It Has a Plumbing Problem.](https://x.com/ZEITFinance/article/2031500989576949770) — @allquantor · Mar 11, 2026
+- [The Sniper's Tax](https://sybilpm.substack.com/p/the-snipers-tax) — sybilpm · Mar 8, 2026
+- [How Wise Is the Crowd? Bias and Edge in Prediction Markets](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6322678) — Deleep, Lee, Bai, Suresh, Dhawan · Feb 28, 2026
+- [23 Reasons Prediction Markets Are Broken Today](https://x.com/linfluence/status/2026757563518431696) — Alexander Lin · Feb 26, 2026
+- [Prediction Markets Are Not Good Markets (Yet)](https://murmurationstwo.substack.com/p/prediction-markets-are-not-good-markets) — Nic Carter · Feb 21, 2026
+- [How to Solve Insider Trading in Prediction Markets](https://www.dopaminemarkets.com/p/how-to-solve-insider-trading-in-prediction) — Shreyas Hariharan · Feb 10, 2026
+- [Why Prediction Markets Aren't Gambling? (The Math)](https://x.com/RohOnChain/article/2020565633453412751) — Roan · Feb 9, 2026
+- [Thoughts on the Law of Insider Trading and Prediction Markets](https://x.com/dbarabander/article/2019769802735178236) — Daniel Barabander · Feb 6, 2026
+- [The Option Value of Waiting in Prediction Markets](https://x.com/0xnagu/article/2016620973391564951) — 0xnagu · Jan 28, 2026
+- [Everyone's Promising 20x Leverage on Prediction Markets. Here's Why It's Hard.](https://medium.com/@nick.c.ruzicka/everyones-promising-20x-leverage-on-prediction-markets-here-s-why-it-s-hard-8684d9a77e11) — Nick Ruzicka · Jan 27, 2026
+- [On Prediction Markets](https://x.com/outpxce/status/2013397772074905950) — outpxce · Jan 20, 2026
+- [The Case For Alternative Ordering Mechanisms in Prediction Markets](https://www.humaninvariant.com/blog/pm-ordering) — Human Invariant · Nov 12, 2025
+- [The Liquidity Problem in Prediction Markets, Part II: Adverse Selection in Prediction Markets](https://x.com/semajeth/status/1975211193418563697) — semaji.eth · Oct 6, 2025
+- [The Liquidity Problem in Prediction Markets, Part I: Adverse Selection and Market Making](https://x.com/semajeth/status/1967598604006134024) — semaji.eth · Sep 15, 2025
+- [The Liquidity Problem in Prediction Markets: Part 0](https://x.com/semajeth/status/1966144230826414225) — semaji.eth · Sep 11, 2025
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/ai-agents.md b/.qoder/plans/pm-plan/theory_concepts/ai-agents.md
new file mode 100644
index 0000000000..6a5f4ce5ee
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/ai-agents.md
@@ -0,0 +1,59 @@
+# AI agents
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — AI agents](https://www.pmatlas.xyz/concepts/ai-agents)
+
+---
+
+## Definition
+
+**Quick definition.** Autonomous software systems that trade on PMs · potentially improving liquidity and accuracy, but currently underperforming in live experiments.
+
+## Key Insights
+
+- Prediction Arena experiment (Arcada Labs): six AI models (Claude, GPT, Gemini, two Groks, GLM) trade real money on Kalshi with $10K each. After three weeks, five of six are underwater. Only Grok 4.20 ("Mystery Model Alpha") is up ~10%. The smartest LLMs are losing while occasionally placing "Blind Trades" after hitting data quota limits.
+- The Avci diagnosis: successful PM traders profit from embodied, local knowledge (monitoring flights, calling embassies) rather than synthesizing public information · a domain where AI remains fundamentally constrained. Raw information processing isn't edge.
+- OddChain's Market-Conditioned Prompting (MCP) research: LLMs perform better as updaters than as cold predictors. Giving GPT 5.1 the market-implied probability as a Bayesian prior and asking it to update with news + earnings transcripts outperforms naive prediction. The MCP method beats market baseline most when prices are uncertain (50-70% range).
+- Zhao's four-layer agent architecture: data, analysis, execution, learning. Maps the ecosystem of existing agents, compares Kelly criterion vs fixed-fraction bet sizing, surveys cross-platform arbitrage, and outlines business models (agent-as-a-service, liquidity mining, data sales). Predicts AI agents become dominant participants within two years.
+- Ben Fielding's agentic bazaar: PMs are the natural marketplace for sovereign AI agents to trade their core commodity · information. Decentralized PMs become the venue where agents monetize alpha through positions, market creators earn fees from surfacing unanswered questions, and reproducible computation enables incorruptible AI judges for dispute resolution.
+- Andy Hall (a16z crypto): the hardest PM problem isn't pricing but deciding what actually happened. Proposes cryptographically-committed LLMs as resolution judges · trading human bias and conflicts of interest for more tractable technical vulnerabilities. Cites Polymarket disputes (Venezuela election, Ukraine map manipulation, government shutdown) as evidence current systems fail at scale.
+- Hall & Paschal (Truth Machine): only 1.3% of political markets are liquid enough to be manipulation-resistant. Proposed fix includes deploying AI market makers where human interest is insufficient · using AI to subsidize long-tail political liquidity.
+- Will Owens (Galaxy): AI is the interface layer for fragmented venues. Convergence toward derivatives · event contracts as hedges, collateral, and composable financial primitives · needs AI aggregators so retail can interact across rails.
+- Hiroki Kotabe's AI quartet: content creators (generating markets), event recommenders (personalization), liquidity allocators, and information aggregators. Argues this enables PMs at microscopic scale · making them personally relevant.
+- @allquantor's Polymarket plumbing analysis: AI flow is now categorizable as a third order-flow type alongside soft (retail) and hard (professional). Polymarket's order book has surface symmetry at top-of-book but systematic ask-side skew at deeper levels.
+- The deeper plumbing problem (@allquantor): capital is trapped · dollars reserved multiple times against mutually exclusive outcomes. Better netting and capital efficiency, not more AI agents, is the actual fix.
+- Sealaunch wallet data: crypto markets on Polymarket are dominated by algorithmic execution; politics by casual event-driven participants. Different categories have very different AI-vs-human compositions.
+- The "AI as oracle judge" debate (Hall + Fielding): if LLMs commit to their reasoning cryptographically, they become a viable arbitration layer for ambiguous outcomes · but at the cost of new attack surfaces (prompt injection, model jailbreaking).
+
+## Notable Quotes
+
+> "An AI quartet of content creators, event recommenders, liquidity allocator, and information aggregators can catalyze massive new activity in this space."
+> — *Hiroki Kotabe, *The Prediction Market Primitive**
+
+> "Integrating these AIs into the current prediction market framework can enable prediction markets at a microscopic scale, making them personally attractive and relevant."
+> — *Hiroki Kotabe, *The Prediction Market Primitive**
+
+## Where It Matters
+
+AI agents are the leverage layer for both liquidity (subsidized MMs in long-tail markets) and resolution (LLM judges for ambiguous outcomes). They're also the existential threat to today's retail trader edge · once agents become reliable, raw forecasting alpha disappears. For Dekant: an LLM-driven curve-drawing assistant would meaningfully lower the cognitive barrier to using continuous markets, where novice users currently can't translate a belief into a distribution shape.
+
+## Related Concepts
+
+- Liquidity provision · AI MMs as the subsidy mechanism for thin markets
+- Oracle design · LLMs as judges in dispute resolution
+- Forecasting accuracy · current AI agents underperform, but MCP improves
+- Long-tail markets · where AI MMs can plausibly bootstrap liquidity
+- Market manipulation · AI flow detection is a new surveillance challenge
+
+## Sources
+
+- [Can LLMs Beat the Market?](https://www.oddchain.com/p/can-llms-beat-the-market) — OddChain · Mar 19 2026 ·
+- [Polymarket Doesn't Have a Money Problem. It Has a Plumbing Problem.](https://x.com/ZEITFinance/article/2031500989576949770) — @allquantor · Mar 11 2026 ·
+- [Turning Probability into Assets: A Look Ahead at Prediction Market Agents](https://x.com/0xjacobzhao/article/2029229969914904694) — Jacob Zhao · Mar 5 2026 ·
+- [Prediction Markets are the Agentic Bazaar](https://x.com/benfielding/article/2023130119708242317) — Ben Fielding · Feb 16 2026 ·
+- [Building the Truth Machine](https://freesystems.substack.com/p/building-the-truth-machine) — Andy Hall, Elliot Paschal · Feb 13 2026 ·
+- [Is AI Any Good at Predicting?](https://reachavci.substack.com/p/is-ai-any-good-at-predicting) — Mehmet Avci · Feb 2 2026 ·
+- [What to Do When Prediction Markets Fail](https://a16zcrypto.substack.com/p/how-ai-judges-can-scale-prediction) — Andy Hall · Jan 24 2026 ·
+- [The Shape of Prediction Markets to Come](https://www.galaxy.com/insights/research/prediction-markets-leverage-ai-agents-defi) — Will Owens · Jan 19 2026 ·
+- [The Prediction Market Primitive](https://medium.com/inception-capital/the-prediction-market-primitive-e48a055676bf) — Hiroki Kotabe · Apr 4 2024 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/arbitrage.md b/.qoder/plans/pm-plan/theory_concepts/arbitrage.md
new file mode 100644
index 0000000000..82ba8f78a8
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/arbitrage.md
@@ -0,0 +1,69 @@
+# Arbitrage
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Arbitrage](https://www.pmatlas.xyz/concepts/arbitrage)
+
+---
+
+## Definition
+
+**Quick definition.** Exploiting price differences for equivalent outcomes across markets or platforms for risk-free profit. In prediction markets, "risk-free" is misleading · most apparent arbs carry resolution risk, oracle risk, or capital-lock risk that can dwarf the spread.
+
+## Key Insights
+
+- Nekt0 catalogs **eight distinct arbitrage strategies** on PMs: classic YES+NO mispricing, cross-platform, range, conditional, time, hedged, resolution, orderflow. Each comes with dollar examples and risk factors.
+- Saguillo et al. (arXiv 2508.03474): identify two distinct arbitrage forms on Polymarket · **Market Rebalancing Arbitrage** (within a single market, e.g., YES+NO < $1 mispricings) and **Combinatorial Arbitrage** (across related markets). The naive cross-market comparison is O(2^(n+m)); they use heuristic reductions based on timeliness, topical similarity, and combinatorial relationships. Empirical estimate: **$40M in realized arbitrage profits extracted** from Polymarket via on-chain order-book analysis.
+- michaellwy: apparent Polymarket↔Kalshi arb is **rarely risk-free** · different rule definitions, resolution criteria, and oracle systems mean "identical" markets can resolve differently. PMs are **fundamentally non-fungible** due to different referee systems.
+- Jon Turek's implied-correlation thesis: Polymarket prices a 60%+ chance of 5% unemployment alongside only a 10% chance of aggressive Fed cuts · but every historical episode of that unemployment spike triggered an average of seven cuts. The historical anchor: rate-cut counts during episodes when unemployment rose 1.2pp+ in a year · 1974-75 (oil shock), 1980-82 (double-dip), 1990-91 (Gulf War), 2001 (dot-com), 2007-09, 2020 (COVID); average ≈ 7 cuts. Cross-market mispricings like this are common and create relative-value trades · Turek's actual proposed trade: NO on unemployment outcomes + YES on Fed cuts.
+- Turek's framing on PM correlation arb: relative-value trading + correlation trading is "very familiar to the trading community" via dual digitals, dispersion trading, etc. PM's listed markets are "priced idiosyncratically but many have implied correlation" · the structural ineffiiency is that traders treat each market in isolation.
+- Roan: "Polymarket's CLOB creates renewable structural arbitrage by design" · the diagnostic toolkit is Kelly sizing, fill-quality measurement of adverse selection, and probability-term-structure analysis.
+- Jeff Opinion's Meta Pool: cross-chain infrastructure to unify PM liquidity. Resolution-aware meta-pools, CredibilityTokens for oracle trust, ConvergenceTokens for divergence hedging. Estimates **$3.4–8.5M in annual efficiency losses** from current fragmentation.
+- John Sides on Clinton-Huang research (2,500 political prediction markets, >$2B notional, final 5 weeks of 2024 cycle across IEM, Kalshi, PredictIt, Polymarket): **PredictIt 93%, Kalshi 78%, Polymarket 67% accuracy**, with cross-platform divergences for identical contracts; daily price changes were weakly correlated or negatively autocorrelated; arbitrage opportunities peaked in the final two weeks. CNN/CNBC partnerships create incentives for sensational coverage of thin markets.
+- keshav: collateralization solves the capital lock-up problem in long-dated markets, *correcting persistent mispricings like longshot mispricing*. Liquidation risk in binaries is the catch.
+- LessWrong "Will Jesus Christ Return": **~$1M lockup needed for 1% return** to fade 3% → 1% · classic textbook example of why time-cost-of-capital destroys arbitrage in long-dated PM markets. The deeper finding (Eric Neyman, Time-Value-of-PM-Cash): late-October 2024 Kamala-in-Kentucky and Trump-in-Massachusetts traded at 0.3% and spiked to 1.5% on election day as No-holders desperately needed cash for other trades · a 5x intra-window arb. "Time value of Polymarket cash" is its own tradable asset, decoupled from the global risk-free rate because moving USD in/out is illegal for Americans and takes days.
+- Rasooly & Rozzi (arXiv 2503.03312): randomized field experiment shocking prices across **817 separate markets** to test manipulability. Effects of the trades persisted for **60 days post-intervention** · markets are manipulable but the effects fade. Markets with more traders, more volume, and an external probability anchor are harder to manipulate (a hedge against the Gunitsky regime-power thesis).
+- a16z (Immerman & Rodriguez) institutional adoption stages: data → compliance → hedging. The arbitrageur layer is currently between data and compliance · institutions can extract signal from PM mispricings without yet using PMs as the execution venue.
+
+## Notable Quotes
+
+> "Prediction markets are fundamentally non-fungible due to differing referee systems."
+> — *michaellwy*
+
+> "Polymarket's CLOB creates renewable structural arbitrage by design."
+> — *Roan*
+
+> "$40M in profits were extracted through exploitation of these pricing inconsistencies."
+> — *Saguillo et al.*
+
+> "These markets have a high degree of implied correlation and that is not being reflected in the current prices."
+> — *Jon Turek*
+
+> "We find that prediction markets can be manipulated: the effects of our trades are visible even 60 days after they have occurred. However, the effects of the manipulations somewhat fade over time."
+> — *Rasooly & Rozzi*
+
+## Where It Matters
+
+Arbitrage is what would normally enforce price coherence across platforms · but in PMs every "arb" is conditional on resolution agreement, oracle agreement, and capital-availability assumptions. That means the existence of arbitrage strategies (eight of them, per Nekt0) doesn't mean prices converge; it means a narrow set of sophisticated actors capture *that* edge while the broader market remains fragmented. Cross-platform meta-pools, standardized resolution criteria, and tokenized PM positions are the leading attempts to make true risk-free arbitrage exist in the asset class for the first time.
+
+## Related Concepts
+
+- **Cross-platform arbitrage** · the most-studied subset.
+- **Temporal / time arbitrage** · the time dimension.
+- **Orderflow arbitrage** · large-order-impact strategies.
+- **Implied correlation / relative value trading** · multi-market arbs.
+- **Resolution criteria / oracle design** · the binding "non-fungibility."
+- **Position collateralization** · frees the capital that underpins arbs.
+- **Liquidity fragmentation** · what creates the price gaps in the first place.
+
+## Sources
+
+- [Prediction Markets and Implied Correlation](https://cheapconvexity.substack.com/p/prediction-markets-and-implied-correlation) — Jon Turek · Mar 24, 2026
+- [Turning Probability into Assets: A Look Ahead at Prediction Market Agents](https://x.com/0xjacobzhao/article/2029229969914904694) — Jacob Zhao · Mar 5, 2026
+- [Why Prediction Markets Aren't Gambling? (The Math)](https://x.com/RohOnChain/article/2020565633453412751) — Roan · Feb 9, 2026
+- [All Types of Arbitrage on Prediction Markets](https://x.com/Nekt_0/article/2019107985079816395) — Nekt0 · Feb 5, 2026
+- [The Perils of Election Prediction Markets](https://goodauthority.org/news/the-perils-of-election-prediction-markets/) — John Sides · Dec 18, 2025
+- [Prediction Markets: The Next Level](https://x.com/0xkeshav_/article/1970163909224169626) — keshav · Sep 23, 2025
+- [Meta Pool: A Unified Infrastructure for Liquidity and Resolution Trust in Prediction Markets](https://x.com/jeff__opinion/article/1957611486991487486) — Jeff Opinion · Aug 19, 2025
+- [Unravelling the Probabilistic Forest: Arbitrage in Prediction Markets](https://arxiv.org/abs/2508.03474) — Saguillo, Ghafouri, Kiffer, Suarez-Tangil · Aug 5, 2025
+- [The Risk Behind Arbitrage in Prediction Markets](https://x.com/michael_lwy/status/1925890768654254571) — michaellwy · May 23, 2025
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/attention-markets.md b/.qoder/plans/pm-plan/theory_concepts/attention-markets.md
new file mode 100644
index 0000000000..8a0184380f
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/attention-markets.md
@@ -0,0 +1,69 @@
+# Attention Markets
+
+**Category:** Governance & Decisions
+**Source:** [PM Atlas — Attention Markets](https://www.pmatlas.xyz/concepts/attention-markets)
+
+---
+
+## Definition
+
+**Quick definition.** Markets that trade on what topics, events, or narratives will capture public attention. Most onchain implementations (Trendle is the prototype) treat attention as a normalized engagement index aggregated across social platforms, and let users long/short the index perpetual-futures-style.
+
+## Key Insights
+
+- The thesis (Trendle/Monad blog): in the digital age, **attention is the only scarce resource** · information, content, and tools for creation are essentially infinite, but the human cognitive bandwidth that "ratifies" content is fixed. Every actor in the attention economy competes for the same fixed pool, which makes attention a credible underlying for a derivative.
+- Kyla Scanlon's "attention supply chain" is the framework: outrage, novelty, and identity are raw inputs → memes, clips, short-form packaging turn them into digestible units → feeds and news cycles push them into circulation → people invest emotionally or financially in where the narrative goes. If attention has a supply chain it can be quantified, priced, and traded.
+- **Attention indexes are the underlying** (not a binary event). Trendle aggregates engagement signals from X, Reddit, and YouTube (with Google Search planned) and normalizes them onto a 0–1 scale per topic. The platform layers a perpetual-style market on top.
+- Anti-gaming is the central design challenge · Goodhart's Law explicitly: when a measure becomes a target, it stops being a good measure. Naive metrics like "post count" can be pumped with bots; Trendle's defenses are:
+- **Source diversification**: multi-platform ingestion (X, Reddit, YouTube + Google Search planned) raises the cost of coordinated manipulation.
+- **Normalization & smoothing**: 0–1 scaling, outlier trimming, exponential decay.
+- **Deseasonalization**: a sports team's pre-game attention spike doesn't count as breakout if it follows the historical pattern.
+- **Quantile clipping**: extreme values are bounded rather than allowed to dominate the index.
+- The signals ingested per platform are deliberately heterogeneous: Reddit (posts, votes, comments, hot rank, velocity), YouTube (views, view-weighted likes/comments), Twitter (retweets, bookmarks, impressions, quotes, replies). The bet is that manipulating *all of these consistently* is dramatically harder than manipulating one.
+- **Trendle is structurally a perpetual, not a binary**: the two-layer architecture separates (1) the **index layer** (attention index computed independently of positioning) from (2) the **market layer** (perpetual-style derivative built around it). Up to 5x leverage; users with leverage can be liquidated.
+- **Funding-rate purpose differs from crypto perps**: in conventional perpetuals, funding rates exist to keep the perp price near an external oracle spot. Trendle has no tradable spot attention asset · there's nothing to arbitrage against. Instead funding rates act as a **crowding tax**: penalize the crowded side, reward the minority. "The most obvious trade becomes more expensive to hold."
+- This is a meaningful design choice: it converts "betting on attention going up" into a more sophisticated game of timing the *position-crowdedness* rather than just the direction. A crowded long doesn't mean the index won't rise · it means the cost of maintaining the long compounds, and the short side earns funding as compensation for taking the uncrowded view.
+- **Hedging use case** (Mike's "How Prediction Markets Turn Into Risk Instruments"): attention markets can serve as a sentiment hedge against binary prediction-market positions. If you're long YES on a discrete event, you can short attention on the same topic · if the narrative dies before resolution, the binary may still resolve YES while the attention short pays out, and vice versa.
+- Attention markets are positioned as a **second layer on prediction markets**, analogous to how derivatives form a second layer on stock exchanges. Mike groups them with cross-platform hedging (Gondor lending against PM positions, DFlow tokenizing Kalshi contracts) as the next phase of PM composability.
+- **Liquidity fragmentation, execution risk, and UX** are the named adoption barriers. Attention markets compete with social-media engagement loops for the same user attention they trade · a structural irony.
+- An attention index will **never be perfect**. Trendle explicitly acknowledges that if the financial reward for gaming is large enough, manipulators will find ways. The bet is that the index stays trustworthy enough relative to manipulation cost, evolving with more data sources (TikTok, news media) and statistical safeguards.
+- Cultural framing: attention markets convert the social-media metric culture (likes, shares, follows) into a tradable instrument. The implicit claim is that price-as-attention is a more honest measure than engagement metrics gamed for advertiser audits.
+- The conceptual parent is **prediction market reflexivity**: trading on attention *is* attention. The market participants generate part of the very signal they trade. Trendle dampens this with the funding-rate crowding tax, but the loop is structural.
+- Adjacent design space includes Cookie.fun's mindshare attention measurement and Mindshare N1's attention scoring · they share the index-construction problem but most have not yet wrapped it in tradable perpetual instruments.
+
+## Notable Quotes
+
+> "Attention is the only scarce resource in the digital age as everything else keeps expanding towards abundance."
+> — *michaellwy, *Perpetual Attention Markets**
+
+> "When a measure becomes a target, it stops being a good measure."
+> — *Goodhart's Law (quoted by michaellwy as the central design constraint)*
+
+> "Manipulating one platform might already be costly. Manipulating several at once, in a coordinated way that produces a consistent attention signal, is much harder. The wide range of inputs raises the cost of manipulation."
+> — *michaellwy*
+
+> "Trendle rewards the trader who understands the difference between 'this narrative is rising' and 'this narrative is already fully priced in by positioning.'"
+> — *michaellwy*
+
+> "Since there is no external spot price as reference, the funding rates on Trendle serve as a 'crowding tax' to penalize the crowded side and reward the minority side."
+> — *michaellwy*
+
+## Where It Matters
+
+Attention markets are the most concrete answer to the question "what else can prediction-market infrastructure trade?" beyond binary events and asset prices. They turn the prediction-market category into a substrate for narrative finance · making "is X going viral?" a hedgeable, tradable view rather than a vibe. For Dekant in particular, attention markets are an interesting parallel: both are continuous distributions wrapped in a market-pricing mechanism, but Dekant's distribution is over discrete outcome values while attention markets' distribution is over a normalized engagement index.
+
+## Related Concepts
+
+- **Info finance** · Attention markets fit cleanly inside Vitalik's info-finance framing: a market deliberately designed to price a specific informational signal (here, attention).
+- **Hedging** · Attention shorts as sentiment hedge against binary YES positions on the same topic.
+- **Conditional tokens** · Trendle's mechanism is a perpetual rather than a conditional-token design, but cross-margining with conditional tokens is the implied next step.
+- **Cross-platform arbitrage** · Trendle's multi-source index is itself a cross-platform aggregation; manipulation must clear cross-platform consistency.
+- **Reflexivity** · Trading attention generates attention; the funding rate is the explicit attempt to dampen the loop.
+- **Parlays** · Multi-topic attention combinations would be a natural parlay product (attention on A AND on B).
+- **Toxic flow / market manipulation** · The defining adversarial concern; the design exists in dialogue with the bot/farm problem.
+
+## Sources
+
+- [How Prediction Markets Turn Into Risk Instruments](https://x.com/mikhryc0x/article/2020802941091741972) — Mike · Feb 9, 2026
+- [Perpetual Attention Markets](https://blog.monad.xyz/blog/perpetual-attention-markets) — michaellwy · Dec 15, 2025 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/batched-auctions.md b/.qoder/plans/pm-plan/theory_concepts/batched-auctions.md
new file mode 100644
index 0000000000..22e081159e
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/batched-auctions.md
@@ -0,0 +1,71 @@
+# Batched Auctions
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Batched Auctions](https://www.pmatlas.xyz/concepts/batched-auctions)
+
+---
+
+## Definition
+
+**Quick definition.** A trading mechanism that collects orders over a time window and clears them simultaneously at a single uniform price, instead of matching continuously as orders arrive. In prediction markets, batched auctions are the leading proposed alternative to continuous limit order books · they neutralize speed-based sniping, reduce defensive spread widening, and shift trader competition from latency to price accuracy.
+
+## Key Insights
+
+- sybilpm ("The Sniper's Tax") opens with a named case study: on **January 3, 2026**, trader "**dudukos**" swept the entire order book of the "Will Israel strike Gaza on January 3, 2026?" market in a single trade · buying from **$0.10 all the way up to $0.80** · then repeated the pattern on Jan 10, 11, 12 and dozens of other Israel-strike markets. The MMs had quoted 10c reflecting a ~10% probability; once a strike happened the fair price jumped to ~100% and dudukos cleaned out the stale offers before they could be cancelled.
+- sybilpm formalizes the MM math: `E[π] = (s/2 · V(s)) − P_news · L_snipe`. In equities `L_snipe` is a few ticks; in PMs it's "**80 cents on the dollar**" when `P_news` is high. MMs respond by widening `s` or pulling quotes entirely · both degrade the market for everyone else, and rationally so.
+- sybilpm names the structural pathology a "**liquidity mirage**": at 3am Tuesday the order book *looks* deep, but that liquidity vanishes the instant news arrives · and once it's gone, the price can be shoved around with "hundreds of dollars".
+- sybilpm's distributional finding: Polymarket and Kalshi LP-rebate programs "burn millions of dollars a year" but the rebates were *never* designed to cover 80-cent sniping losses · so they amount to "a quiet subsidy flowing from retail liquidity providers to sophisticated snipers". The retail LP "thinks they're earning yield. They're actually exit liquidity, again."
+- sybilpm cites Budish-Cramton-Shim 2015 directly, and concludes batching "shifts the competitive frontier from network hardware to predictive intelligence".
+- Human Invariant adds a hard datum from Nasdaq research: on FCFS exchanges, market makers update their quotes in time **only ~13% of the time** · meaning 87% of the time they get picked off by takers. Stable in equities (continuous asset, fee schedule), catastrophic in PMs (binary asset, 1%→100% jumps on a single news item).
+- Human Invariant's priority-batch ordering rule is explicit and ordered: **(1) cancel orders → (2) maker orders → (3) taker orders**. Suggested batch intervals are tailored to the market type: **10 sec for live sports**, **1 min for news/government-shutdown markets**, **1 hr for election markets with 3+ years to resolution**. Even more granular: dynamic batches at each *dead ball* in basketball or *changeover* in tennis.
+- Human Invariant's incumbent prediction: Polymarket, Kalshi, Opinion, and Limitless are unlikely to migrate because (a) they benefit from raw transaction counts and headline volume "including wash trading, to juice their vanity metrics", (b) technical risk, (c) anchored trader base, (d) airdrops/rebates reward "sheer activity rather than quality liquidity". The opportunity is for **new entrants** to launch batched-only.
+- Will Howard's mechanism is sourced from real exchanges: NYSE's pre-open and pre-close **call auctions** already use single-price uniform clearing · collect orders, broadcast an "Indicative Match Price" each second, clear at the price that maximises matched volume. Howard's design proposal is to extend this to a "**Frequent Batch Auction**" running at every fixed interval with no continuous trading between.
+- Howard's social-utility argument is concrete: he claims there is **no non-zero-sum use case** where it's valuable to know Maxwell-cut-a-deal or room-temp-superconductor news in 100ms vs 1s vs 1hr. The fast-reaction effort is pure rent-extraction. He cites the 2024 Biden drop-out market on Polymarket as the canonical case · the price moved "literally the moment" Biden's tweet went out, "too fast to read the post and buy through the web UI".
+- Howard surfaces a UX angle the other two miss: batched auctions also have better **ergonomics** for retail browsers · "a fair price will be negotiated between a group of buyers and sellers" instead of "should you take the midpoint of the bid-ask spread, or the last executed trade, or some average?".
+- Underlying theme across all three: in PMs, batching is a defense against gap risk and toxic flow simultaneously · the same mechanism that protects MMs from snipers also disincentivizes informed traders from racing to be first, because the batch clear price already reflects the new information.
+
+## Notable Quotes
+
+> "On January 3, 2026, a trader named 'dudukos' cleared the entire order book of the 'Will Israel strike Gaza on January 3, 2026?' market in a single trade, buying from $0.10 all the way up to $0.80."
+> — *sybilpm*
+
+> "In traditional markets, being slightly slower than the fastest trader costs you a few basis points. In prediction markets, it costs you the entire value of your position."
+> — *sybilpm*
+
+> "Polymarket and Kalshi burn millions of dollars a year on liquidity incentive programs"
+> — *basically paying people to post orders. The pitch is: earn rewards for providing liquidity. The reality is: you're being paid to stand in front of the train." · sybilpm*
+
+> "On Nasdaq, market makers are only able to update their quotes in time approximately 13% of the time. This means that 87% of the time they are picked off by takers."
+> — *Human Invariant*
+
+> "Reasonable starting points for prediction markets might include: 10 sec for live sports such as basketball and baseball, 1 min for news-based events such as the government shutdown ending and mention markets, 1 hr for election markets with three years or more until resolution."
+> — *Human Invariant*
+
+> "Existing prediction market players benefit from maximizing raw transaction counts and headline volume, including wash trading, to juice their vanity metrics."
+> — *Human Invariant*
+
+> "For almost all markets, there is no social utility to having a reaction time faster than about 1 second, and for many markets the 'socially useful' reaction time could be a day or more."
+> — *Will Howard*
+
+> "Rather than waiting for one person to show up and take up your order at the limit price, you can walk away from the market knowing a fair price will be negotiated between a group of buyers and sellers."
+> — *Will Howard*
+
+## Where It Matters
+
+Batched auctions are the most consensus-friendly post-CLOB redesign in the literature. Unlike LMSR (capital-destructive) or parimutuel (timing-rigid), batching keeps the order-book mental model traders already understand while eliminating most of the pathologies that make passive market making unprofitable. The open question is whether incumbents (Polymarket, Kalshi) move to batching voluntarily or whether new entrants build batched-only venues to compete on spread quality.
+
+## Related Concepts
+
+- **Continuous double auction** · what batched auctions replace.
+- **Order book** · batched auctions still use one, but clear it periodically.
+- **Market making / adverse selection / toxic flow / gap risk** · all the problems batching is designed to mitigate.
+- **Bid-ask spread** · what batching tightens.
+- **Execution quality** · improves under batching (less sniping, less defensive widening).
+- **Insider trading / market manipulation** · batching reduces the value of speed-based MNPI exploitation.
+
+## Sources
+
+- [The Sniper's Tax](https://sybilpm.substack.com/p/the-snipers-tax) — sybilpm · Mar 8, 2026
+- [The Case For Alternative Ordering Mechanisms in Prediction Markets](https://www.humaninvariant.com/blog/pm-ordering) — Human Invariant · Nov 12, 2025
+- [Many Prediction Markets Would Be Better Off as Batched Auctions](https://antidiluvian.substack.com/p/many-prediction-markets-would-be) — Will Howard · Aug 2, 2025
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/bid-ask-spread.md b/.qoder/plans/pm-plan/theory_concepts/bid-ask-spread.md
new file mode 100644
index 0000000000..ff9eb6d2ef
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/bid-ask-spread.md
@@ -0,0 +1,70 @@
+# Bid-Ask Spread
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Bid-Ask Spread](https://www.pmatlas.xyz/concepts/bid-ask-spread)
+
+---
+
+## Definition
+
+**Quick definition.** The difference between the highest price a buyer will pay and the lowest price a seller will accept · the cost of immediacy. In prediction markets the spread reflects adverse-selection risk, gap risk, and inventory holding costs in addition to the usual MM compensation, which is why headline spreads can be 10–50% on long-tail contracts.
+
+## Key Insights
+
+- XO Labs's classical Avellaneda–Stoikov adaptation broke because **PM prices are bounded (0–1) probabilities with non-constant volatility and guaranteed terminal convergence**. Logit-space transformation lost $1,114 in backtest; probability-space engine with inventory-skewed spreads, regime detection, and multi-outcome coordination was profitable at $453.
+- @allquantor's **"semantic tick size"** thesis: on 600M Polymarket datapoints, **~70% of one-cent price moves do not continue in the same direction**. Because each penny reads as a 1-percentage-point probability revision, PMs *narrativize microstructure noise*. A contrarian fade strategy harvests this overreaction.
+- Sam Schneider on Kalshi: fee formula **fee = 0.07 × C × P × (1-P)** peaks at 50/50 · fees incentivize trading near the meaningful middle, and effectively widen the "true" spread retail pays.
+- Rose-Berman makes the spread-as-tax economics explicit on Kalshi: every trade has a maker and a taker, fees apply both directions, and retail is structurally the taker because they don't have the systems to MM. A 50/50 coin-flip becomes a **3.4% expected loss** purely from crossing the maker spread + fees, regardless of who wins.
+- Marinson ("Seeing Like a Market"): the cost differential between PMs and derivatives is **"apparatus rent"** paid for constructed dealer infrastructure. **FOMC compressed from a 12pp gap (March 2024) to <2pp (Feb 2026) at $3M** · driven by liquidity compression, not vol expansion. High-VRP categories (BTC 4.83%, elections) have crossed the threshold; silver (VRP −10.19%) has not and never will under current structure. The 87-contract / 2.89M-trade dataset found 30 PM wins / 12 at threshold / 45 losses.
+- Marinson's BTC natural experiment: 5 BTC contracts at identical 4.08% VRP · PM wins at every strike where volume is sufficient ($100k/$13.3M, $105k/$7.1M, $110k/$4.9M, $150k/$32.8M) and loses only at the thin one ($125k/$2.2M). Liquidity is the only variable.
+- Della Vedova: automated traders profit by paying **2.52 cents less per contract** than retail. The spread *is* the wealth-transfer mechanism.
+- Becker on the spread-as-wealth-transfer category gradient: Finance gap 0.17pp, Sports 2.23pp, Crypto 2.69pp, Weather 2.57pp, Entertainment 4.79pp, Media 7.28pp, World Events 7.32pp. Category-level differences in spread economics swamp any platform-level differences.
+- Human Invariant: FCFS order matching forces **defensive spread widening**; priority batch auctions (cancels → makers → takers) let MMs tighten quotes. The mechanism: in FCFS the MM's quote is fillable until they cancel; if news arrives faster than they can cancel, they're filled at stale prices. Priority batch auctions let cancels execute before takers, so stale quotes get pulled rather than picked off.
+- sybilpm on dynamic spread economics: MM expected profit **E[π] = (s/2 · V(s)) − P_news · L_snipe**. In news-sensitive markets the sniping term dominates and spreads must widen to compensate; for "Will Israel strike Lebanon today?" type contracts the equilibrium spread is wide enough that retail can't trade economically.
+- Jay Malavia: peer-to-peer exchange overround is ~3% (Betfair) vs. ~12% for traditional bookmakers · the spread differential is the exchange-vs-house-edge model difference made numerical.
+- Taetaehoho: PM prices are 100–300 bps better than sportsbooks on liquid markets, but long-tail markets have **10–50% spreads** (where pro MM competition is absent).
+- Ranger Global: finance markets have the tightest spreads (**0.17%**); crypto the widest (**2.69%**) · efficiency correlates strongly with informed-trader presence.
+- Lotus: the same contract trades at **58–67 cents on different venues simultaneously** · cross-venue spread fragmentation is its own asset.
+- Hall & Paschal complement: **bid-ask spreads exceed 20% on most political contracts**; only 1.3% are liquid enough to be manipulation-resistant. The spread is the most diagnostic stat for a market's status.
+
+## Notable Quotes
+
+> "70% of one-cent price moves do not continue in the same direction."
+> — *@allquantor*
+
+> "Each penny reads as a one-percentage-point probability change, creating overreactions that a contrarian fade strategy can profitably harvest."
+> — *@allquantor, "Prediction Markets Have a Semantic Tick Size"*
+
+> "Automated traders profit by paying 2.52 cents less per contract."
+> — *Della Vedova*
+
+> "The cost differential is not a quality discount. It is apparatus rent. Same distributional content. Different transmission cost."
+> — *Lauris Marinson*
+
+> "FCFS creates wider spreads and other negative externalities."
+> — *Human Invariant*
+
+## Where It Matters
+
+The spread is the single best summary statistic for a market's health: it bakes in liquidity depth, MM competition, adverse-selection risk, gap risk, and fee structure. Headline averages mask huge cross-section variation · finance markets at 17 bps next to entertainment at 700+ bps · which is why category mix is a strategic decision for every platform. The semantic-tick-size finding from @allquantor is particularly load-bearing: the same 1-cent move that traders read as new information is, 70% of the time, noise the MM can fade.
+
+## Related Concepts
+
+- **Market making** · spread is the MM's revenue stream.
+- **Adverse selection** · wide spreads compensate for it.
+- **Semantic tick size** · explains why PM spreads carry narrative weight beyond the price.
+- **Execution quality** · measured against the spread mid.
+- **Batched auctions / continuous double auction** · design choices that shape spread width.
+- **Liquidity provision / order book** · the venue and capital that produce spreads.
+- **Gap risk / hedging** · risks the spread must price in.
+
+## Sources
+
+- [Market Making for Prediction Markets: A Probability-Space Approach](https://x.com/xolabs_/status/2045161249588269257) — XO Labs · Apr 17, 2026
+- [Prediction Markets Have a Semantic Tick Size](https://x.com/ZEITFinance/article/2034314079599243694) — allquantor · Mar 19, 2026
+- [What's Kalshi's Revenue? Analyzing All 203 Million Trades on Kalshi.](https://read.technically.dev/p/whats-a-prediction-market) — Sam Schneider · Mar 12, 2026 (paywalled intro only)
+- [Seeing Like a Market: Event Contracts and Market Topology](https://www.slampaper.xyz/) — Lauris Marinson · Mar 1, 2026
+- [Who Profits from Prediction Markets? Execution, Not Information](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6191618) — Joshua Della Vedova · Feb 7, 2026
+- [The Case For Alternative Ordering Mechanisms in Prediction Markets](https://www.humaninvariant.com/blog/pm-ordering) — Human Invariant · Nov 12, 2025
+- [Who Are You Really Playing Against?](https://x.com/thejay/article/1968392782998835518) — Jay Malavia · Sep 18, 2025
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/binary-contracts.md b/.qoder/plans/pm-plan/theory_concepts/binary-contracts.md
new file mode 100644
index 0000000000..a51e2bca74
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/binary-contracts.md
@@ -0,0 +1,68 @@
+# Binary Contracts
+
+**Category:** Mechanism Design
+**Source:** [PM Atlas — Binary Contracts](https://www.pmatlas.xyz/concepts/binary-contracts)
+
+---
+
+## Definition
+
+Prediction market contracts that resolve to exactly one of two values, typically $1 (yes) or $0 (no), based on whether a specified event occurs. The binary structure eliminates ambiguity at resolution and enables direct probabilistic interpretation of prices · but it also forces continuous questions into a Pareto-distributed grid of independent order books.
+
+## Key Insights
+
+- functionSPACE analyzed 36,777 Polymarket events: when one continuous question is split into ~20 binaries, the top 3 markets capture >75% of volume regardless of event size · the bottom of the distribution is structurally untradeable "ghost markets."
+- The $0.01 tick size compounds the fragmentation problem · at very low probabilities, a one-cent tick is a 50%+ relative move, creating a "rounding tax" that makes low-probability contracts structurally imprecise.
+- functionSPACE V2 (Apr 2026) split events into continuous (price brackets, weather ranges) vs categorical (teams, candidates): both concentrate ~90% of volume in top 5–6 markets, but ghost markets are largely a *categorical* phenomenon. Continuous events distribute volume more evenly and survive the liquidity cliff at high N. Continuous events overtook categorical by count in 2026Q1 · the continuous-distribution primitive applies to a growing share of the platform.
+- Kalshi's revenue formula `fee = 0.07 × C × P × (1-P)` peaks at 50% probability · economically incentivizes trading near coinflip markets, structurally more poker-rake than sportsbook (Schneider analysis of 203M trades across $41.7B volume).
+- Sports comprise **82% of Kalshi's volume** · despite the CFTC-regulated derivatives positioning, it functions as a sports betting platform.
+- Binary payoffs create *clean* basis risk to truth · Jeff Park argues this precision is what differentiates prediction markets from other derivatives, making them ideal for hedging exposure to specific outcomes.
+- Minsky's "vega wedge" · binary hedgers replicating exposure via vanilla options pay a structural overcharge (a structural overcharge (magnitude varies by asset; the specific ~4.8%/7-20% figures are unverified)). Prediction markets can undercut that tax in deep categories (0xturbanurban).
+- The binary structure makes prediction markets the purest testing environment for investment theory · binary resolution eliminates the unobservable noise that obscures strategy quality elsewhere (gemchanger).
+- LMSR's pricing function is mathematically identical to softmax · bridges quant finance and prediction market pricing.
+- Leverage is hard on binaries because binary outcomes resolve *instantly*, skipping the intermediate prices a liquidation engine needs · this is "gap risk." dYdX's TRUMPWIN perp on election night 2024 broke under real conditions despite sophisticated safeguards (Ruzicka).
+- Three current approaches to leverage on binaries: constrain leverage (1x–1.5x), engineer around gap risk (dynamic fees, circuit breakers), or ship and iterate.
+- Berkeley Blockchain primer: order books translate bids/asks into probabilities. Polymarket (offshore, crypto-native, public onchain) vs Kalshi (CFTC-regulated, USD, private activity) is the canonical comparator.
+- Jo Lim (Strait of Hormuz 2026): binary order-book markets hit an architectural ceiling on granular, multi-outcome risk. LMSR/CLMSR offers protocol-native liquidity and coherent pricing for events without underlying assets.
+
+## Notable Quotes
+
+> "Volume follows an extreme Pareto distribution: the top 3 markets capture over 75% of trading activity regardless of event size, leaving a large fraction as untradeable ghost markets."
+> — *functionSPACE, *Binary Events**
+
+> "Kalshi functions more like a poker rake than a sportsbook, charging fees via the formula fee = 0.07 × C × P × (1-P), which incentivizes trading near 50% probability."
+> — *Sam Schneider, *What's Kalshi's Revenue?**
+
+> "Binary outcomes resolve instantly, skipping the intermediate prices that liquidation engines need to function."
+> — *Nick Ruzicka, *Everyone's Promising 20x Leverage on Prediction Markets**
+
+> "Binary payoffs create clean basis risk to truth."
+> — *Jeff Park, *What Most People Get Wrong About Prediction Markets**
+
+## Where It Matters
+
+The binary contract is the dominant primitive of the category · every Polymarket and Kalshi market is a stack of binaries. But the architecture forces continuous questions (price, magnitude, distribution) into a fragmented grid that systematically wastes liquidity in the tails. This is exactly the gap that motivates continuous-outcome markets like Dekant. The binary structure is also what makes leverage and lending hard (gap risk) · every collateralization scheme described in the literature has to engineer around the discontinuity at resolution.
+
+## Related Concepts
+
+- Multi-outcome markets · direct alternative to stacked binaries
+- Liquidity fragmentation · the cost of the binary primitive on continuous questions
+- Gap risk · the binary structure is what creates discontinuous resolution
+- Semantic tick size · the $0.01 increment reads as a percentage point of probability
+- Market scoring rules / LMSR · the AMM mechanism most often paired with binaries
+- Order book · Polymarket's CLOB shipped *because* binary AMMs failed
+- Adverse selection · binaries make insider edges binary too
+- Hedging · binary basis risk is what makes them clean hedging instruments
+
+## Sources
+
+- [Binary Events V2: Does Liquidity Trade The Tails?](https://x.com/functionspaceHQ/status/2048536153662636173) — functionSPACE · X
+- [Polls Are Dead. Long Live Prediction Markets.](https://x.com/CalBlockchain/status/2047460674532790499) — Blockchain at Berkeley · X
+- [What Most People Get Wrong About Prediction Markets](https://x.com/dgt10011/status/2045952603301851173) — Jeff Park · X
+- [The Bane Of Binaries: What Prediction Markets Are Missing](https://x.com/0xturbanurban/status/2044433520806830101) — 0xturbanurban · X
+- [Binary Events: What Happens When You Split One Market Into Twenty](https://x.com/functionspaceHQ/status/2039554933024776516) — functionSPACE · X
+- [The World's Biggest Risk Event Just Exposed Prediction Markets' Biggest Gap](https://x.com/jolimmmm/article/2036119259420807216) — Jo Lim · X
+- [What's Kalshi's Revenue? Analyzing All 203 Million Trades on Kalshi.](https://read.technically.dev/p/whats-a-prediction-market) — Sam Schneider · Technically.dev
+- [Your Hedge Fund's Sharpe Ratio Is a Lie. Prediction Markets Are the Only Place It Can't Hide.](https://x.com/gemchange_ltd/article/2026300422483271733) — gemchanger · X
+- [Everyone's Promising 20x Leverage on Prediction Markets. Here's Why It's Hard.](https://medium.com/@nick.c.ruzicka/everyones-promising-20x-leverage-on-prediction-markets-here-s-why-it-s-hard-8684d9a77e11) — Nick Ruzicka · Medium
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/bonding-trades.md b/.qoder/plans/pm-plan/theory_concepts/bonding-trades.md
new file mode 100644
index 0000000000..0904a91050
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/bonding-trades.md
@@ -0,0 +1,36 @@
+# Bonding Trades
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Bonding Trades](https://www.pmatlas.xyz/concepts/bonding-trades)
+
+---
+
+## Definition
+
+**Quick definition.** A prediction-market strategy of betting on near-certain outcomes (>95% probability) to earn a small but reliable yield, analogous to holding a zero-coupon bond to maturity. The "yield" is the gap between the market price and the $1 payout. Catastrophic tail risk if the improbable outcome occurs.
+
+## Key Insights
+
+- Stephanie Stacey (FT Alphaville): the canonical example is the **Polymarket "Will Jesus Christ Return" market**, where betting NO at 96% odds implies a **4.76% yield to maturity**.
+- Two structural constraints: (1) **illiquidity** · only **$150k executable at one time** on these markets; (2) **catastrophic tail risk** · if the near-impossible outcome occurs, the entire principal is wiped out.
+- The yield equation is straightforward: if NO trades at $0.96, you put $0.96 to win $1 → 4.17% return at resolution. The annualized rate depends on time-to-maturity, which is why long-dated near-certain markets have higher implied yields than short-dated ones (locking $1M for two years vs. two months matters).
+- outpxce (related): the trader self-identifies as a **"bond mule"** · locking capital for small premiums on near-resolution markets. This is essentially the professional version of bonding trades.
+- Connection to position collateralization: the capital lock-up is the binding constraint. If PM positions could be used as DeFi collateral, the implied yield curve on near-certain markets becomes a legitimate fixed-income substitute.
+- Connection to gap risk: bonding is the *short volatility* trade. You earn a tiny yield in exchange for accepting that one in a hundred binaries you bond will resolve against you and wipe out years of premium.
+
+## Where It Matters
+
+Bonding trades are PM's organic fixed-income product. Their existence is what FT Alphaville-style observers point to when arguing PMs are converging on finance, not gambling · but their tail risk and capital-lock-up are also why bonding can't scale into a true bond-replacement until position collateralization is solved. They are the cleanest case study for why "stable yield" in prediction markets is a misleading frame: the yield is real, the stability is conditional on a catastrophic-outcome counterfactual that 99% of the time doesn't fire.
+
+## Related Concepts
+
+- **Gap risk** · bonding is the trade most exposed to it.
+- **Liquidity provision** · bond mules are providing time-locked capital.
+- **Position collateralization** · the key unlock that would scale bonding.
+- **Longshot bias** · the inverse of bonding (paying for longshots vs. selling them).
+- **Temporal arbitrage** · bonding is the opposite end of the time-decay strategy spectrum.
+
+## Sources
+
+- [How Prediction Market Traders Reinvented the Bond](https://www.ft.com/content/3382195d-b417-4fc8-9c2e-17f38027a635) — Stephanie Stacey (FT Alphaville) · Feb 6, 2026
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/brier-score.md b/.qoder/plans/pm-plan/theory_concepts/brier-score.md
new file mode 100644
index 0000000000..80ae17bfc2
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/brier-score.md
@@ -0,0 +1,49 @@
+# Brier Score
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Brier Score](https://www.pmatlas.xyz/concepts/brier-score)
+
+---
+
+## Definition
+
+A proper scoring rule that measures probabilistic forecast accuracy as the mean squared difference between predicted probabilities and binary outcomes (0 or 1). Lower scores indicate better calibration; the universal metric for benchmarking forecasters, models, and markets. Range 0 (perfect) to 1 (maximally wrong on every prediction).
+
+## Key Insights
+
+- Polymarket headline Brier of 0.047 sounds great · for context, a random 50/50 forecaster gets ~0.25, so 0.047 is genuinely strong on the *aggregate*. But the headline is a category average that hides catastrophic per-category Brier scores. Sports markets score 0.325 (worse than a coin flip). 99% of volume concentrates in the last hours before resolution · which inflates aggregate Brier because the closer to resolution, the easier the call. When outlets like CNN and WSJ broadcast illiquid market odds as authoritative signal, whale trades on thin books get *laundered through credible newsrooms* (Vaidik Mandloi).
+- ForecastBench (Forecasting Research Institute, Oct 2025): tracks how well LLMs forecast real-world outcomes against superforecasters and crowd forecasters. Best LLM (GPT-4.5) achieves Brier 0.101 vs superforecasters' 0.081. LLMs improving ~0.016 Brier points per year · projecting parity by late 2026. Notable finding: some models *game* the benchmark by copying prediction market prices rather than reasoning independently. The benchmark itself becomes a calibration problem.
+- ForecastBench full-read methodology: uses a **difficulty-adjusted Brier score** (raw Brier penalty scaled by question difficulty estimated via market Brier scores + two-way fixed-effects model for dataset questions). This solves the comparison problem when forecasters answer different question sets. Two leaderboards: Baseline (no tools, no market access · measures pure model capability) and Tournament (scaffolding/fine-tuning/markets allowed · measures frontier accuracy). The Baseline leaderboard reveals true LLM progress: 0.036 Brier/year on market questions, much faster than the Tournament rate. Models must wait 50 days before appearing on leaderboard (analysis shows that's the minimum needed for reliable performance estimates).
+- ForecastBench example questions from Sept 28, 2025 round: "Will Summer McIntosh still hold the world record for 400m IM by 2025-10-28?" (Wikipedia source), "Will hostilities between Pakistan and India result in 100+ uniformed casualties Jun 2 – Sep 30, 2025?" (RAND FI), "Will a human step foot on Mars by 2030?" (Manifold), "Will the CDC report 10,000+ H5 avian influenza cases by Jan 1, 2026?" (Metaculus). Mix of dataset (250/round, time-series-derived) and market (250/round, from Manifold, Metaculus, Polymarket, RAND).
+- Mandloi (FULL_READ) historical anchor: Glenn Brier was a meteorologist who invented the score in 1950 because weather forecasters were the first profession to take probabilistic predictions seriously. "A Brier score of 0 means you predicted everything perfectly. A score of 0.25 means you did no better than a coin flip. Anything above 0.25 means you would have been better off guessing randomly." Brier.fyi maintains the canonical PM scoreboard.
+
+## Notable Quotes
+
+> "Polymarket's headline Brier score of 0.047 masks category-specific failures like sports markets scoring 0.325 (worse than a coin flip)."
+> — *Mandloi*
+
+> "GPT-4.5 achieves a Brier score of 0.101 versus superforecasters' 0.081, with LLMs improving roughly 0.016 Brier points per year."
+> — *Forecasting Research Institute*
+
+> "Some models game the benchmark by copying prediction market prices rather than reasoning independently."
+> — *Forecasting Research Institute*
+
+## Where It Matters
+
+Brier score is the metric every PM "we're accurate" headline reduces to · but it's an aggregate that can hide huge category-level failures. The only honest reporting is *decomposed* Brier: by category, by horizon, by trade size, by liquidity decile. The LLMs-game-the-benchmark finding is doubly relevant: (1) PMs themselves can be gamed by traders copying other PMs (creating false consensus), and (2) Brier benchmarks used to compare PMs to LLMs/humans are contaminated when one input copies another. For builders, the practical move is to publish per-category Brier curves, and to flag any category where Brier exceeds the random-baseline of 0.25 · that's a market that's actively misinforming.
+
+## Related Concepts
+
+- **Calibration** · Brier decomposes into calibration + refinement
+- **Forecasting accuracy** · Brier is the operational metric
+- **Superforecasting** · Brier is how superforecaster status is awarded
+- **Wisdom of crowds** · Brier is how the crowd is benchmarked
+- **Minimum viable liquidity** · MVL frames how much volume affects Brier
+- **AI agents** · Brier is the LLM benchmark
+- **Noise decomposition** · Brier can be decomposed into signal and noise components
+
+## Sources
+
+- [Polymarket Is Not a Truth Machine](https://www.thetokendispatch.com/p/polymarket-is-not-a-truth-machine) — Vaidik Mandloi · Apr 11, 2026 [FULL_READ]
+- [How Well Can Large Language Models Predict the Future?](https://forecastingresearch.substack.com/p/ai-llm-forecasting-model-forecastbench-benchmark) — Forecasting Research Institute · Oct 8, 2025 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/calibration.md b/.qoder/plans/pm-plan/theory_concepts/calibration.md
new file mode 100644
index 0000000000..0f09658407
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/calibration.md
@@ -0,0 +1,62 @@
+# Calibration
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Calibration](https://www.pmatlas.xyz/concepts/calibration)
+
+---
+
+## Definition
+
+When stated probabilities match empirical frequencies · events given 70% odds happen 70% of the time. The first-order quality metric for any probabilistic forecaster (human, model, or market), and the basis for Brier-score evaluation.
+
+## Key Insights
+
+- Kalshi sports monthly volume grew 80x to $14.4B in March 2026. NCAA March Madness = $3.3B notional. In-game prices correlate 0.99+ with FanDuel, but Kalshi taker fees (up to 3.5% at midpoint) and thin in-game liquidity (76% depth decline vs pre-game) limit institutional execution (Kunal Doshi).
+- Polymarket headline Brier of 0.047 masks category-specific failures: sports markets 0.325 (worse than coin flip); 99% volume in last hours. PMs only "work" on ~2% of contracts. CNN/WSJ broadcasting illiquid odds = whale trades laundered through newsrooms (Mandloi).
+- Raw PM probabilities must be adjusted for contract wording mismatches and economic relevance before use in equity analysis. Polymarket's 51% tariff refund probability → 35% effective for Logitech margin impact; 29.5% FDA approval → $5.4B EV uplift for Eli Lilly (Smallwood).
+- Noisy traders are NOT dumb money. Synthesizes Snowberg/Wolfers/Zitzewitz, INSEAD's BIN model, Wharton's cognitive search framework. Noisy traders *fund the probability space* rather than serve as exit liquidity. Binary CLOBs vs continuous probability markets decompose and harness noise differently (functionSPACE).
+- Tetlock's Superforecasting → Polymarket: Brier scores and calibration are the operational metrics. Foxes vs hedgehogs (Ahnianchykau).
+- "Decomposing Crowd Wisdom" · Bayesian hierarchical model fit to 292M trades across 327k binary contracts on Kalshi/Polymarket. Decomposes calibration into four components · universal horizon effect, domain-specific biases, domain-by-horizon interactions, trade-size scale effect · that together explain 87.3% of calibration variance on Kalshi. Persistent underconfidence in political markets where prices are "chronically compressed toward 50%" (generalises across both exchanges). Large trades amplify this on Kalshi but not Polymarket → platform-specific microstructure. Bayesian model achieves 96.3% posterior predictive coverage (Nam Anh Le).
+- "What if we're capturing the wrong signal?" · binary markets flatten complex beliefs; Vanderbilt: PredictIt 93% accuracy vs 67% on high-volume platforms; more liquidity ≠ better signal (Jo).
+- ForecastBench: GPT-4.5 Brier 0.101 vs superforecasters 0.081. LLMs improving ~0.016 Brier/year, parity by late 2026. Some models *game* the benchmark by copying PM prices rather than reasoning independently (Forecasting Research Institute).
+- Nam Anh Le arXiv full read (abstract): on Kalshi, calibration decomposes into "four components (a universal horizon effect, domain-specific biases, domain-by-horizon interactions and a trade-size scale effect) that together explain 87.3% of calibration variance." The trade-size scale effect: large trades on Kalshi politics have Δ = 0.53 (95% CI [0.29, 0.75]) on underconfidence · but Polymarket only shows Δ = 0.11 (95% CI [-0.15, 0.39]), so the effect is platform-specific. Bayesian hierarchical model confirms with "96.3% posterior predictive coverage." Conclusion: "Consumers of prediction market prices who treat them as face-value probabilities will systematically misinterpret them, and the direction of misinterpretation depends on what is being predicted, when and by whom."
+- Smallwood "Can Polymarket Make You a Better Equity Analyst?" (FULL_READ) provides two case-study calibrations: (1) Logitech tariff exposure · raw Polymarket tariff-refund probability 51% → adjusted to 35% because the contract resolves on a legal refund event while Logitech GM depends on broader economic relief. (2) Retatrutide FDA approval · raw 29.5% → 22.1% effective because the contract resolves on *any* FDA approval (could be a narrow approval that doesn't change the equity thesis). The translation discipline ("mapping haircut") is what makes PM prices useful in actual analyst workflows. Final figure: 0.61% of Lilly market cap as the probability-weighted EV uplift from early 2026 approval.
+- Mandloi full read: Brier.fyi platform analyzed 84,000+ Polymarket/Kalshi/Manifold/Metaculus questions. Cross-platform sports Brier 0.325 explicitly worse than coin flip (0.25). Polymarket's BTC-$100K market resolved Yes but had Brier 0.4909 because it was confidently wrong for months. Kamala Dem nomination market: Brier 0.9098 despite resolving Yes. Polymarket 2024 presidential market: $3.6B volume, 63,000 unique monthly traders. Vanderbilt Bayesian study comparing Polymarket to swing-state polls: Polymarket more accurate in every single swing state.
+
+## Notable Quotes
+
+> "Polymarket's headline Brier score of 0.047 masks category-specific failures like sports markets scoring 0.325 (worse than a coin flip)."
+> — *Mandloi*
+
+> "Persistent underconfidence in political markets, where prices are chronically compressed toward 50%."
+> — *Nam Anh Le*
+
+> "Some models game the benchmark by copying prediction market prices rather than reasoning independently."
+> — *Forecasting Research Institute*
+
+## Where It Matters
+
+Calibration is what every "Brier" headline reduces to, but the 2026 work is decomposing the headline number into more useful diagnostics: domain-specific bias, horizon effects, trade-size effects, platform microstructure. Builders should publish *decomposed* calibration metrics, not aggregate Brier scores, or they'll be accused of laundering bad sports calibration through good political calibration. Underconfidence in political markets (prices compress toward 50%) is a specific actionable mispricing · a curve-drawing market like Dekant could potentially fix this by giving traders a richer way to express bimodal or skewed beliefs than "buy YES at 52¢."
+
+## Related Concepts
+
+- **Brier score** · the metric
+- **Forecasting accuracy** · the umbrella claim
+- **Wisdom of crowds** · the folk theory calibration tests
+- **Superforecasting** · the human-skill benchmark
+- **Noise decomposition** · companion technique for separating signal from microstructure
+- **Longshot bias / Yes bias** · known calibration failures
+- **Adverse selection** · driver of platform-level calibration differences
+- **Distribution markets** · argued as a higher-resolution calibration surface
+
+## Sources
+
+- [From Betting to Trading: How Kalshi Is Reshaping Sports Markets](https://x.com/Kunallegendd/status/2044780671839952949) — Kunal Doshi · Apr 16, 2026 [FULL_READ]
+- [Polymarket Is Not a Truth Machine](https://www.thetokendispatch.com/p/polymarket-is-not-a-truth-machine) — Mandloi · Apr 11, 2026 [FULL_READ]
+- [Can Polymarket Make You a Better Equity Analyst?](https://theagenticanalyst.substack.com/p/can-polymarket-make-you-a-better) — Smallwood · Apr 9, 2026 [FULL_READ]
+- [Noisy Traders Are Not Dumb Money](https://x.com/functionspaceHQ/article/2032397043579462028) — functionSPACE · Mar 13, 2026 [FULL_READ]
+- [The Book That Predicted Polymarket](https://x.com/mikita_crypto/article/2029939210350645729) — Ahnianchykau · Mar 6, 2026 [FULL_READ]
+- [Decomposing Crowd Wisdom: Domain-Specific Calibration Dynamics in Prediction Markets](https://arxiv.org/abs/2602.19520) — Nam Anh Le · Feb 23, 2026 [FULL_READ · abstract]
+- [What If We're Capturing the Wrong Signal?](https://x.com/TideMarkets/article/2016912289941827801) — Jo · Jan 29, 2026 [FULL_READ]
+- [How Well Can Large Language Models Predict the Future?](https://forecastingresearch.substack.com/p/ai-llm-forecasting-model-forecastbench-benchmark) — Forecasting Research Institute · Oct 8, 2025 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/combinatorial-prediction-markets.md b/.qoder/plans/pm-plan/theory_concepts/combinatorial-prediction-markets.md
new file mode 100644
index 0000000000..ca4001a8f6
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/combinatorial-prediction-markets.md
@@ -0,0 +1,54 @@
+# Combinatorial Prediction Markets
+
+**Category:** Mechanism Design
+**Source:** [PM Atlas — Combinatorial Prediction Markets](https://www.pmatlas.xyz/concepts/combinatorial-prediction-markets)
+
+---
+
+## Definition
+
+Prediction markets that allow participants to place bets on *conditional events* and *Boolean combinations* of base events, not just individual outcomes. By enabling richer betting vocabularies, they elicit joint probability distributions over related events rather than only marginal probabilities.
+
+## Key Insights
+
+- **The canonical reference:** Powell, Hanson, Laskey & Twardy (2013), *Combinatorial Prediction Markets: An Experimental Study* · the only article cited on the on-page corpus for this concept, but it is dense and foundational.
+- The paper describes the **DAGGRE** combinatorial prediction market · an actual platform built for the IARPA forecasting tournament, not just a theoretical proposal. The experiment used a Bayesian network to generate the "ground truth" scenario and provide "gold standard" probabilities to compare against.
+- The experimental setup: participants were "challenged to solve a 'whodunit' murder mystery by using a prediction market to arrive at group consensus probabilities for characteristics of the murderer, and to update these consensus probabilities as clues were revealed." This is a controlled environment where joint probabilities can be compared against the Bayes-net ground truth.
+- Direct from the abstract: "Economic theory suggests that the greater expressivity of combinatorial prediction markets should improve accuracy by capturing dependencies among related questions" · and the paper provides empirical support comparing combinatorial vs flat market structures.
+- **Co-authored by Robin Hanson**, whose LMSR underpins most automated prediction markets · LMSR is what makes combinatorial markets tractable because the log cost function decomposes naturally over a combinatorial outcome space.
+- The combinatorial state space problem: with N base events, there are 2^N possible Boolean combinations, so a naive combinatorial market becomes intractable beyond ~20–30 base events. Tractable subclasses include tournament structures, ordering questions, and conditional-only markets.
+- Combinatorial markets are particularly useful for **conditional decision markets**: "What is P(GDP growth | tariff policy)?" requires a joint distribution, which a flat market on each policy separately cannot deliver.
+- Conditional prediction markets are the formal grounding of **futarchy** · Hanson's governance proposal that policy decisions be made based on which option prediction markets forecast will produce the best outcomes (covered separately under Governance and Decisions cluster, but the math lives here).
+- For *decision-relevant* questions, combinatorial markets are strictly more informative than flat markets · you can answer "is X causal for Y?" by comparing P(Y|X) to P(Y|not-X) directly.
+- Practical implementations are rare: MIT's "Yoopick," several SAIC / Consensus Point experiments, the IARPA ACE program (which DAGGRE itself was built for). Most modern prediction-market platforms (Polymarket, Kalshi) do not support combinatorial bets · they list each outcome as an independent binary.
+- The on-page corpus is thin for this concept (only 1 article), but the related-concepts graph hints that combinatorial is positioned as a frontier mechanism: connected to market scoring rules, LMSR, proper scoring rules, and forecasting accuracy.
+
+## Notable Quotes
+
+> "Prediction markets produce crowdsourced probabilistic forecasts through a market mechanism in which forecasters buy and sell securities that pay off when events occur. Prices in a prediction market can be interpreted as consensus probabilities for the corresponding events."
+> — *Powell, Hanson, Laskey & Twardy*
+
+> "Combinatorial prediction markets allow forecasts not only on base events, but also on conditional events (e.g., 'A if B') and/or Boolean combinations of events. Economic theory suggests that the greater expressivity of combinatorial prediction markets should improve accuracy by capturing dependencies among related questions."
+> — **ibid.**
+
+> "The experiment challenged participants to solve a 'whodunit' murder mystery by using a prediction market to arrive at group consensus probabilities for characteristics of the murderer, and to update these consensus probabilities as clues were revealed. A Bayesian network was used to generate the 'ground truth' scenario and to provide 'gold standard' [probabilities]."
+> — **ibid.**
+
+## Where It Matters
+
+Combinatorial prediction markets are the formal foundation for *decision-relevant* forecasting · the mechanism that lets markets answer "if we do X, what happens to Y?" rather than just "what will Y be?" This is the missing piece for futarchy and for any prediction-market application where the question is causal/conditional rather than purely descriptive. The category is structurally underbuilt: most modern platforms list flat binaries because combinatorial UI and pricing are hard. For Dekant, the question is whether continuous-outcome markets can express the *conditional* dimension natively · that's an open design problem.
+
+## Related Concepts
+
+- LMSR · makes combinatorial markets computationally tractable via cost-function decomposition
+- Market scoring rules · the AMM class combinatorial markets sit inside
+- Proper scoring rules · combinatorial markets inherit IC from log-rule decomposability
+- Forecasting accuracy · combinatorial markets empirically improve calibration
+- Decision markets / futarchy · combinatorial conditional markets are the mathematical foundation
+- Multi-outcome markets · combinatorial extends multi-outcome with conditional and Boolean structure
+- Information aggregation · combinatorial markets aggregate joint, not just marginal, information
+
+## Sources
+
+- [Combinatorial Prediction Markets: An Experimental Study](https://link.springer.com/chapter/10.1007/978-3-642-40381-1_22) — Powell, Hanson, Laskey & Twardy · Springer
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/conditional-tokens.md b/.qoder/plans/pm-plan/theory_concepts/conditional-tokens.md
new file mode 100644
index 0000000000..48c44ca0af
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/conditional-tokens.md
@@ -0,0 +1,75 @@
+# Conditional Tokens
+
+**Category:** Governance & Decisions
+**Source:** [PM Atlas — Conditional Tokens](https://www.pmatlas.xyz/concepts/conditional-tokens)
+
+---
+
+## Definition
+
+**Quick definition.** Tokens whose redemption value depends on a specific future outcome. They enable composable prediction-market positions: split collateral into YES/NO (or multi-outcome) tokens, trade each independently, then merge or redeem at resolution. Pioneered as a standard by Gnosis' Conditional Token Framework (CTF), which underpins Polymarket and most onchain prediction markets.
+
+## Key Insights
+
+- The Gnosis CTF is the canonical implementation: any user can **split** one unit of collateral (typically USDC) into one of each outcome token, or **merge** a complete set of outcome tokens back into one unit of collateral. In a correctly structured marketplace, outcome-token prices stay between 0 and 1 and sum to 1 (binary case).
+- The split/merge invariant is what lets liquidity expand whenever matched counterparties exist: if Jack wants 10 YES at $0.35 and Jill wants 10 NO at $0.65, the protocol mints 10 full conditional sets from $10 of collateral and distributes them · no external maker needed. Ranger Global's microstructure series identifies this as the structural reason CLOBs beat constant-product AMMs for binary events.
+- Conditional tokens compose into **multi-outcome markets** by stacking conditions: each candidate gets its own YES/NO market priced independently. The price sum is enforced only loosely (by arbitrage), so the "sum of YES prices ≠ 100%" gap creates a "soft link" arbitrage opportunity · buy all underpriced YES sides to lock in a small profit when at least one must resolve true.
+- Polymarket alone hit roughly 35x weekly active user growth from May to September 2024 (Shoal), with 99.2% of trading volume concentrated in political markets and two-thirds of cumulative volume occurring in the last six months before the 2024 US election (ASXN). The conditional-token primitive is the operational substrate underneath this.
+- **Hyperliquid's HIP-4** (Pink Brains) reframes outcome contracts as an onchain **options layer**: unified margin engine, fee structure, and token value capture targeted at credit default swaps, parametric insurance, and futarchy rather than just binary betting. Conditional tokens are the building block for the options stack.
+- **Conditional decision tokens** (futarchy implementation): pABC/fABC and pUSD/fUSD ("p" = pass, "f" = fail). If the proposal passes, pABC redeems for regular ABC and fABC becomes worthless; the opposite if it fails. Two markets trade simultaneously (pABC/pUSD and fABC/fUSD); the proposal passes if pABC/pUSD's TWAP exceeds fABC/fUSD's. Used by MetaDAO.
+- The conditional-tokens architecture is what makes **trustless joint ownership** possible (Heavey/Umbra): an attempt to raid a DAO treasury through governance becomes self-defeating because the attacker must buy worthless pABC above spot AND sell fABC below fair value. The mechanism makes the rational economic outcome of the attack a net loss for the attacker.
+- Conditional tokens enable **composable hedging**: a BTC holder worried about an election can lock in a conditional BTC price for the specific scenario by trading directly on an Impact Market token (BTC | election outcome), rather than separately holding BTC and betting against probability · collapses basis risk between event view and asset exposure (Galaxy/Pokorny).
+- **DeFi composability**: conditional tokens are SPL or ERC-20 tokens, so they can be used as collateral elsewhere. Gondor lets users lend against PM positions; DFlow tokenizes Kalshi contracts as SPL tokens for cross-chain DeFi use. Mike's "How Prediction Markets Turn Into Risk Instruments" frames this as PMs developing the same derivative second-layer that stocks did.
+- **Resolution-aware Meta-Pools** (Jeff Opinion): proposes infrastructure for swapping semantically similar conditional tokens issued by different oracles. The architecture introduces CredibilityTokens (trading on oracle trustworthiness) and ConvergenceTokens (hedging the divergence risk between two oracle interpretations of "the same" event). Estimates $3.4–8.5M of annual efficiency loss from current fragmentation.
+- **Cross-platform arbitrage on conditional tokens** is a major activity: identical events trade at different prices on Polymarket vs. Kalshi vs. Hyperliquid. Latency under 100ms captures 73% of arbitrage profits (Ranger Global). The conditional-token primitive makes positions transferable and hedgeable across venues.
+- **Probability-scaled dynamic fees** (Ranger Global) are designed to address the structural challenge that bid-ask spread should shrink as price approaches 0 or 1 (where most of the mass concentrates). Conditional tokens at the extremes need fee curves that adjust to capital efficiency rather than being flat percentages.
+- The Polymarket US government shutdown market (Jan 2025) is the canonical conditional-token resolution failure case (michaellwy): tokenholder-voters with retroactive rule changes and a corruption cost below the value at stake · the conditional token resolved YES on a shutdown that did not actually happen. The fragility of conditional tokens is downstream of oracle design, not token mechanics.
+- **Why 99.2% Polymarket volume sits in politics**: conditional tokens need both schelling-point salience and credible resolution. Politics has both; obscure questions have neither, which is why Polymarket's category expansion has lagged its election cycle peaks.
+- **The "no mechanical link" arbitrage** in multi-outcome markets is a feature, not a bug: it preserves the ability to trade each outcome as a continuous variable in a unified order book, while letting arbitrageurs enforce the sum-to-1 constraint through capital, not protocol logic. Inefficiencies live in spread, not in mispricing.
+
+## Notable Quotes
+
+> "All outcomes on Polymarket are tokenized and exchanged both non-custodially and atomically on the Polygon Network… Polymarket outcome shares are binary outcomes, Yes/No, and are represented using Gnosis' Conditional Token Framework."
+> — *Alex Nardi, Shoal*
+
+> "The matching calculation for this example is simple, $3.50 will be transferred from Jack to Jill in exchange for her 10 shares… An amount of $3.50 from Jack and $6.50 from Jill ($10 in total) is used to mint 10 full conditional token sets (10 YES and 10 No), then distribute 10 Yes to Jack and 10 No to Jill."
+> — *ASXN, *Polymarket: An Election-Driven Success Story**
+
+> "Impact markets transfer the burden of correlation estimation from every individual end user to the market's price discovery process… analogous to the difference between estimating implied volatility yourself versus reading it off an options chain."
+> — *Zack Pokorny, Galaxy Research*
+
+> "For Bob's proposal to pass, pABC/pUSD needs to trade above fABC/fUSD… From both Alice and Bob's perspectives, 1 fABC is worth 1 ABC because the DAO carries on as normal if the proposal fails, and 1 pABC is worth 0 because as soon as the proposal passes, the DAO won't possess anything anymore."
+> — *Kevin Heavey, *Futarchy as Trustless Joint Ownership**
+
+> "PM traders systematically underreact to spot moves by 10-20%, and latency under 100ms now captures 73% of arbitrage profits."
+> — *Ranger Global, *Anatomy Of A New Asset Class I**
+
+## Where It Matters
+
+Conditional tokens are the load-bearing primitive: every onchain prediction market of any size today uses them, every futarchy implementation uses them in their pass/fail form, every Impact Market design extends them with asset-denominated payouts, and every cross-platform arbitrage strategy is fundamentally trading them. The fragility points are oracle design (how outcomes get reported) and liquidity fragmentation across venues · both of which are solvable in principle and the active frontier.
+
+## Related Concepts
+
+- **Decision markets** · Pass/fail conditional token pairs are the standard implementation of decision markets.
+- **Futarchy** · Conditional tokens make asset-futarchy decision markets work; the trustless-joint-ownership argument runs through conditional-token mechanics.
+- **Impact markets** · Conditional tokens denominated in real assets (BTC, equity, etc.) rather than $0/$1 are the natural generalization.
+- **Oracle design / UMA / dispute resolution** · Conditional-token redemption is downstream of oracle output; bad oracles produce bad redemptions regardless of how clean the token model is.
+- **Cross-platform arbitrage** · Identical conditional tokens at different platforms create the most reliable arbitrage opportunities in the category.
+- **Order book / market making / execution quality** · CLOBs work better than CFAMMs for binary events specifically because the split/merge invariant lets order books absorb directional flow without LP losses.
+- **Attention markets** · Trendle is conceptually a conditional-token market where the underlying is a normalized attention index instead of a binary outcome.
+- **Network effects / platform competition** · Conditional-token standards (Gnosis CTF, HIP-4) become a coordination point for liquidity; switching costs flow through whichever standard captures the largest split/merge pool.
+
+## Sources
+
+- [HIP-4 Is Not a Prediction Market · It's the Options Layer: A Full Guide](https://x.com/PinkBrains_io/status/2051312783074226606) — Pink Brains · May 4, 2026
+- [Anatomy Of A New Asset Class I: How Markets Turn Capital Into Probability](https://x.com/Ranger_Global/status/2046547375649427572) — Ranger Global · Apr 21, 2026 ·
+- [How Prediction Markets Turn Into Risk Instruments](https://x.com/mikhryc0x/article/2020802941091741972) — Mike · Feb 9, 2026 ·
+- [Prediction Markets' Next Frontier: Impact and Decision Markets](https://www.galaxy.com/insights/research/prediction-markets-impact-markets-decision-markets-futarchy) — Zack Pokorny, Galaxy · Jan 12, 2026 ·
+- [Meta Pool: A Unified Infrastructure for Liquidity and Resolution Trust in Prediction Markets](https://x.com/jeff__opinion/article/1957611486991487486) — Jeff Opinion · Aug 19, 2025 ·
+- [10 Predictions About Prediction Markets](https://x.com/michael_lwy/status/1884570344184578081) — michaellwy · Jan 29, 2025 ·
+- [Why Prediction Markets Are Broken (And How to Fix Them)](https://x.com/michael_lwy/status/1877211555496079683) — michaellwy · Jan 9, 2025 ·
+- [Futarchy as Trustless Joint Ownership](https://www.umbraresearch.xyz/writings/futarchy) — Kevin Heavey · Oct 28, 2024 ·
+- [Polymarket and the Proliferation of Prediction Markets](https://www.shoal.gg/p/prediction-markets) — Alex Nardi, Shoal · Oct 8, 2024 ·
+- [Mechanisms for Prediction Markets](https://paragraph.com/@mechanism.institute/prediction-markets) — Raye Hadi, Sofia Cossar, Ori Shimony · Aug 22, 2024 ·
+- [Polymarket: An Election-Driven Success Story](https://newsletter.asxn.xyz/p/polymarket-an-election-driven-success) — ASXN · Aug 10, 2024 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/continuous-double-auction.md b/.qoder/plans/pm-plan/theory_concepts/continuous-double-auction.md
new file mode 100644
index 0000000000..b04235085a
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/continuous-double-auction.md
@@ -0,0 +1,55 @@
+# Continuous Double Auction
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Continuous Double Auction](https://www.pmatlas.xyz/concepts/continuous-double-auction)
+
+---
+
+## Definition
+
+**Quick definition.** A trading mechanism where buy and sell orders are matched immediately as they arrive, with price-time priority (FCFS) determining execution order. The CDA is the standard matching rule for CLOBs on Polymarket and Kalshi · and the principal target of every "alternative ordering mechanism" proposal in the PM literature.
+
+## Key Insights
+
+- Human Invariant inventories the 2025 CDA incumbents explicitly: **Polymarket, Kalshi, Opinion, and Limitless** all run offchain CDA matching with price-time priority. "Onchain orderbooks still remain unviable."
+- Trust assumption: FCFS in PMs is "very likely" but not enforceable · "the public must trust that the companies running the infrastructure are operating honestly and not re-ordering transactions". The regulatory backstops that ensure FCFS in equities don't yet exist for PMs.
+- Failure mode 1 · **latency wars**: FCFS creates a large incentive to co-locate with the matching engine. This is the same dynamic seen in equities but amplified because PM contracts can jump 1%→100% on a single news item.
+- Failure mode 2 · **defensive spread widening**: MMs widen quotes when they can't trust they'll cancel in time. Human Invariant's anchor stat: on Nasdaq, MMs successfully update quotes only **~13% of the time** (87% of the time they get picked off). Stable in equities; catastrophic in PMs.
+- Failure mode 3 · **taker dominance**: Human Invariant: "the most profitable traders all run taker strategies, where they scan various markets to pick off mispriced quotes from market makers". The large LP-incentive programs from Polymarket and Kalshi exist *because* of this · they're paying MMs to keep quoting despite the structural disadvantage.
+- Human Invariant's live-sports case study: spreads visibly widen during in-game play, with the "$0.04 spread" misleading because the resting liquidity is only **$3,000** · negligible. Compare with NBA regular-season pregame markets, which maintain $0.01 spreads with substantially deeper liquidity.
+- The proposed replacement is **priority batch auctions** that process cancellations → maker orders → taker orders inside each batch. This rule structure lets MMs cancel stale quotes safely before takers can sweep them.
+- Human Invariant's bear case on migration: "Existing prediction market players benefit from maximizing raw transaction counts and headline volume, including wash trading, to juice their vanity metrics." Migration is costly, the trader base is anchored, and airdrops/rebates already reward "sheer activity rather than quality liquidity". The likely route is new entrants, not incumbent reform.
+- Implicit comparison set: sybilpm (continuous order books "structurally broken for binary assets") and Will Howard ("no practical social benefit from sub-second reaction times") reach the same conclusion from different angles.
+- The CDA's saving grace is that it's the matching mechanism every trader already understands · it inherits the equity-market mental model. The cost is that everything that makes CDAs ugly in equities (HFT arms races, latency rents, last-look games) is amplified in PMs by gap risk and binary payoffs.
+- The PM-specific failure mode: a CDA matches the first-arriving order at the prevailing price; when prices can jump 0.10 → 0.99 between two orders, the CDA's price-time priority *encodes the sniper's advantage* into the matching rule.
+
+## Notable Quotes
+
+> "First-Come-First-Served order matching in prediction markets creates perverse incentives: latency wars between traders and defensive spread widening by market makers."
+> — *Human Invariant*
+
+> "On Nasdaq, market makers are only able to update their quotes in time approximately 13% of the time. This means that 87% of the time they are picked off by takers."
+> — *Human Invariant*
+
+> "A single second can provide new information to cause the price to jump from 1% to 100% (pope announcement, game winning shot, etc.). As a market maker, you co-locate as close to the matching engine as possible but you are still very likely to be picked off in a FCFS environment."
+> — *Human Invariant*
+
+> "The prediction market that creates the best market structure will host the most liquid markets with the best prices for users."
+> — *Human Invariant*
+
+## Where It Matters
+
+The CDA is the dominant microstructure for prediction markets in 2026, and it's also the most-critiqued. Every meaningful alternative proposal (batched auctions, parimutuel, LMSR, market scoring rules, covariance markets) defines itself against the CDA's pathologies. Whether incumbents migrate or new entrants arrive built around batching is the open product question of the year.
+
+## Related Concepts
+
+- **Batched auctions** · the leading alternative.
+- **Order book / market making / bid-ask spread** · the CDA's operating environment.
+- **Adverse selection / toxic flow / gap risk** · the failure modes the CDA amplifies.
+- **Execution quality** · what CDAs are graded on.
+- **Time arbitrage** · what FCFS rewards.
+
+## Sources
+
+- [The Case For Alternative Ordering Mechanisms in Prediction Markets](https://www.humaninvariant.com/blog/pm-ordering) — Human Invariant · Nov 12, 2025
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/corruption-value-multiple.md b/.qoder/plans/pm-plan/theory_concepts/corruption-value-multiple.md
new file mode 100644
index 0000000000..0bbcefff31
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/corruption-value-multiple.md
@@ -0,0 +1,73 @@
+# Corruption Value Multiple (CVM)
+
+**Category:** Oracle & Resolution
+**Source:** [PM Atlas — Corruption Value Multiple (CVM)](https://www.pmatlas.xyz/concepts/corruption-value-multiple)
+
+---
+
+## Definition
+
+**Quick definition.** The Corruption Value Multiple is a quantitative diagnostic: the ratio of *value-at-stake outside a market* (Polymarket open interest, derivatives downstream, social/political leverage from a flipped resolution) to the *cost of corrupting the oracle* (half of the oracle token float + bonds + slashing risk). A CVM > 1 means honest resolution is economically irrational for a marginal voter. The phrase was coined by Luca Prosperi (Dirt Roads #64) and is now standard vocabulary on the topic.
+
+## Key Insights
+
+- **The original Prosperi formula** (Nov 2024): for Polymarket-on-UMA, CVM = (Open Interest / 2) / (UMA float / 2). At the time of writing, OI ≈ $300M, UMA market cap ≈ $220M, majority-stake capture ≈ $110M. Resulting CVM = **$150M / $110M ≈ 1.36x.**
+- **The 1.36x figure is a *lower bound*.** It assumes: (a) all UMA tokens always vote (in reality ~20% do), (b) resolutions are unambiguous (in reality ambiguous ones are easier to swing), (c) attacker has no leverage from outside-the-market value (in reality propaganda, derivative positions, and political outcomes amplify the prize). True CVM is significantly higher.
+- **A CVM above 1.0 means oracle honesty is economically irrational.** The whole optimistic-oracle design rests on the assumption that voters lose more from slashing than they could gain from corruption. When CVM crosses 1, that assumption inverts and the protocol becomes a transparent attack target.
+- **CVM is the unifying lens for every documented Polymarket failure case:**
+- Venezuela 2024 election: market open interest ≈ $6M+, UMA capture cost vastly higher · but external-value (regime-legitimacy narrative, derivative bets) is what drove the lobbying intensity.
+- Government shutdown 2025: technical OPM-website resolution, low CVM, but the optical/political value of a Polymarket-says-shutdown-ended on a specific day created soft pressure.
+- Zelensky suit $240M / TikTok ban $120M / Cardi B halftime $47M: each had value-at-stake comparable to UMA float, making them direct CVM stress tests.
+- **The CVM denominator is wrong if you only count tokens.** Real corruption costs also include:
+- Price-impact of accumulating tokens (Prosperi: thin float, slippage on a $110M buy)
+- Reputation/value-destruction of the token itself (perceived manipulation tanks UMA price)
+- Coordination overhead of organizing voters
+- Risk of dissenting voters exposing the attack
+- Derivative positions downstream
+- Information-arbitrage opportunities elsewhere
+- Propaganda value (Gunitsky: "headline value is extracted immediately")
+- State-actor information-warfare budgets
+- **Polymarket scaling produces an unbounded CVM increase.** Polymarket's volume grew ~35x from May to Sep 2024 (Nardi). UMA's market cap is bounded by token demand. The asymmetry is structural: market value scales with adoption, oracle security scales with token speculation.
+- **The CVM diagnostic generalizes beyond UMA.** Any token-voted oracle, AI-judge mechanism with corrupt-the-input attacks, or human-committee oracle has a CVM. michaellwy applies it to AI judges: corrupting the model is harder than corrupting tokens but not impossible (training-data poisoning, prompt injection in inputs).
+- **The fix is not bigger bonds · it's smaller markets per oracle.** XO Labs layered approach: route each market to the resolution tier where its CVM is < 1. Low-stakes meme markets get cheap AI resolution; high-stakes geopolitical markets get expensive deeply-decentralized adjudication; on-chain price feeds get parametric resolvers with CVM ≈ 0.
+- **Sortition/recusal mechanisms compress CVM.** If voters cannot also trade the market, the numerator collapses to "external value only" · usually much smaller than full open interest. UMA does not currently enforce this.
+- **The CVM is a *worst-case bound*, not an average-case prediction.** Most markets resolve honestly because most votes are routine and the attack-effort is unwarranted. CVM matters most for *contested edge cases* where someone bothers to organize. Empirically, those are exactly the cases that have failed.
+- **CVM-aware market design suggests asymmetric bonds.** Make dispute bonds scale with market value, not be flat. Polymarket's flat $100k bond is rounding error on $240M markets.
+- **The OPM-website class of failures has CVM ≈ 0 but still fails.** Because no token-corruption is involved · the website just got out of sync with reality, and the UMA voters honestly reported what they saw. This is a *different failure class* from CVM-driven capture; it's a data-source failure. (Self-resolving markets and AI-with-multi-source resolution are the proposed mitigations.)
+- **The CVM ratio explains why Polymarket-style markets struggle to scale further.** Each additional dollar of open interest *increases* CVM linearly; UMA market cap does not scale linearly with adoption. The "wall" is the moment large counterparties (sportsbooks, hedgers, institutional traders) refuse to enter because the oracle isn't economically secure for their position size.
+- **The corollary: AI judges have a different CVM topology.** Corruption cost is roughly constant (cost of training-data manipulation or prompt injection) regardless of market size · so AI-judge CVM *decreases* per-dollar as market size grows. This is the strongest pro-AI-judge argument in the corpus.
+- **Repeated UMA capture in small ways may have already happened.** Prosperi flags that the perceived integrity of UMA is part of its market cap. If observable failures accelerate, UMA price falls, CVM rises further, and the protocol enters a death spiral.
+
+## Notable Quotes
+
+> "The Corruption Value Multiple (CVM) is a metric that compares the cost of manipulating a market to the potential gains from such manipulation. In this context, with Polymarket's open interest at c. $300m and UMA's market capitalisation around c. $220m, the CVM would be ($300 / 2) / $110 = 1.36x, indicating that for every $1 invested in manipulation there's a potential gain of $1.36."
+> — *Luca Prosperi*
+
+> "The 1.36x number might well understate CVM since it assumes that (a) every $UMA always participate in voting, while that number seems to be actually c. 20%, and (b) resolutions are trivial, in reality ambiguous wording, delays in result verification, and the potential for social manipulation make things more complicated. My gut feeling says that the $$$ required to control the oracle are much, much less."
+> — *Luca Prosperi*
+
+> "[The UMA system has] a corruption cost lower than the value at stake."
+> — *michaellwy (paraphrasing Polymarket shutdown post-mortem)*
+
+## Where It Matters
+
+CVM is the most useful single number for evaluating an oracle. It's the prediction-market equivalent of a casino's house-edge · except inverted: when CVM > 1, the *house* is the trader being extracted from, and the attacker is anyone with capital. Every new oracle proposal (XO Oracle layered, AI judges committed on-chain, Meta Pool credibility tokens) can be benchmarked by what its CVM looks like across the market-size distribution.
+
+## Related Concepts
+
+- **oracle design** · CVM is the most cited oracle-quality metric in the corpus.
+- **UMA protocol** · the empirical case study where CVM first became binding.
+- **dispute resolution** · the layer CVM measures.
+- **market manipulation** · when CVM > 1, manipulation is the rational strategy.
+- **incentive compatibility** · CVM < 1 is approximately the condition for incentive-compatible truth-telling.
+- **election markets** · the highest-stakes markets and therefore the highest-CVM tests.
+
+## Sources
+
+- [Resolving Prediction Markets: An AI-Driven Layered Approach](https://x.com/xolabs_/status/2055266974259961917) — XO Labs · May 15 2026 ·
+- [Semantics for $10 Million: When Is an Invasion an Invasion?](https://www.oddchain.com/p/semantics-for-10-million-when-is-an-invasion-an-invasion-polymarket) — OddChain · Jan 23 2026 ·
+- [Why Prediction Markets Are Broken (And How to Fix Them)](https://x.com/michael_lwy/status/1877211555496079683) — michaellwy · Jan 9 2025 ·
+- [Prediction Markets (II): Spoiling the Election Love Story](https://dirtroads.substack.com/p/64-prediction-markets-ii-spoiling) — Luca Prosperi · Nov 2 2024
+- [My Polymarket Conspiracy Theory](https://medium.com/quantum-economics/my-polymarket-conspiracy-theory) — Lou Kerner · Oct 20 2024
+- [Polymarket Settles Bet Against Its Own Rules](https://frankmuci.substack.com/p/polymarket-settles-bet-against-its) — Frank Muci · Aug 8 2024 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/covariance-markets.md b/.qoder/plans/pm-plan/theory_concepts/covariance-markets.md
new file mode 100644
index 0000000000..506e604e50
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/covariance-markets.md
@@ -0,0 +1,41 @@
+# Covariance Markets
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Covariance Markets](https://www.pmatlas.xyz/concepts/covariance-markets)
+
+---
+
+## Definition
+
+**Quick definition.** Markets that trade on the *correlation* between two events rather than each event individually. The original proposal (Chicken / Voliti) solves the parlay-fragmentation problem: instead of creating separate markets for every AND/OR combination, a single covariance market between two base markets enables all 8 joint combinations while maintaining concentrated liquidity.
+
+## Key Insights
+
+- Chicken's design proposal: parlays normally fragment liquidity because every AND/OR/conditional combination needs its own market. A **covariance market between two base markets** unlocks all 8 joint combinations on one shared liquidity pool.
+- The mechanism: trade correlation directly. If two events A and B trade at probabilities p_A and p_B, the covariance market expresses the difference between actual joint probability and p_A × p_B. Going long covariance = betting the events move together more than independence implies.
+- This is the PM-native version of variance/correlation swaps in traditional derivatives.
+- Compatible with the **distribution-markets / continuous markets** thesis: instead of fragmenting a question across many binaries, a covariance overlay enables compositional bets without multiplying contracts.
+- Connection to Jon Turek's implied-correlation work: the *reason* covariance markets would exist is to absorb the relative-value trades that arise from inconsistent cross-market correlation pricing.
+- Daedalus Research's "Black-Scholes for PMs" paper proposes correlation swaps as one of three core derivative instruments (alongside variance swaps and first-passage notes) · covariance markets are the spot product underneath those derivatives.
+
+## Notable Quotes
+
+> "A single covariance market between two base markets enables all 8 joint combinations while maintaining concentrated liquidity."
+> — *Chicken*
+
+## Where It Matters
+
+Covariance markets are one of the more elegant design proposals for solving liquidity fragmentation without abandoning the binary-contract substrate. Rather than splitting questions into dozens of correlated binaries, traders express the joint structure once. The catch is that they require traders to think in covariances, which is a much bigger conceptual lift than yes/no · meaning these are infrastructure for AI agents and quant desks more than for retail.
+
+## Related Concepts
+
+- **Parlays** · what covariance markets are designed to replace.
+- **Liquidity provision** · covariance markets concentrate it.
+- **Liquidity fragmentation** · what covariance markets undo.
+- **Implied correlation / relative value trading** · the trading strategies that consume covariance pricing.
+- **Distribution markets / multi-outcome markets** · sibling proposals for non-binary primitives.
+
+## Sources
+
+- [How to Offer Prediction Market Parlays Without Fragmenting Liquidity](https://x.com/voliti_co/status/1946232456459255869) — Chicken · Jul 18, 2025
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/credibility-markets.md b/.qoder/plans/pm-plan/theory_concepts/credibility-markets.md
new file mode 100644
index 0000000000..c87b39d105
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/credibility-markets.md
@@ -0,0 +1,45 @@
+# Credibility Markets
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Credibility Markets](https://www.pmatlas.xyz/concepts/credibility-markets)
+
+---
+
+## Definition
+
+Markets where participants stake on the accuracy of claims or sources, building continuously updated trust scores rather than static reputations. Treats trustworthiness as a tradeable probability over time. A subset of the broader "probability infrastructure" thesis.
+
+## Key Insights
+
+- Single-source concept article. Probability layers thesis (Aggie): PMs are a proof-of-concept for a broader shift · probability as infrastructure. Three layers beyond trading: 1. **Attention markets** · price content virality forward 2. **Credibility markets** · turn trust into a continuously updated score 3. **Demand markets** · capture consumer intent before production
+- Credibility markets specifically treat *trustworthiness as a probability over time*. Instead of static "verified" badges or one-shot reputation systems, every claim or source has a running probability score that traders update by staking. A news outlet, a researcher, an AI-generated artifact · each gets a continuously-priced credibility curve.
+- Endgame framing: probability signals embedded invisibly into every decision surface on the internet. Credibility markets are the layer most likely to be adopted by social platforms, news aggregators, and AI safety pipelines because trust is the single most-needed primitive in an AI-saturated information environment.
+
+## Notable Quotes
+
+> "Markets where participants stake on the accuracy of claims or sources, building continuously updated trust scores rather than static reputations."
+> — *onprediction.xyz definition*
+
+> "Treats trustworthiness as a tradeable probability over time."
+> — *onprediction.xyz definition*
+
+> "Probability signals embedded invisibly into every decision surface on the internet."
+> — *Aggie*
+
+## Where It Matters
+
+Credibility markets are speculative · there's only one article cataloged at onprediction · but the *mechanism design* is the most natural next step after the PM-as-info-finance thesis. If PMs can price the probability of an event resolving YES/NO, they can price the probability that a *claim* will resolve as true after future verification. Two practical applications: (1) AI-generated content authentication (every claim has a market-priced trust score), (2) news source rating (a continuously-priced replacement for static media-bias rankings). For PM platforms, this is a structural expansion of TAM beyond event-betting into the much larger category of "things people want a probability on." For Dekant, the curve-drawing primitive is *more* suited to credibility than binary · credibility isn't really YES/NO, it's a distribution over "how likely is this claim true given different verifiers."
+
+## Related Concepts
+
+- **Probability infrastructure** · credibility markets are one of three named layers
+- **Info finance** · Vitalik's umbrella that includes credibility pricing
+- **Demand markets / Attention markets** · sister concepts
+- **Information aggregation** · credibility markets aggregate trust signals
+- **Oracle design** · credibility markets need verifiable claim resolution
+- **Self-resolving markets** · credibility markets may need cross-prediction mechanisms for unverifiable claims
+
+## Sources
+
+- [The Probability Layers Are Coming](https://x.com/BlondiePredicts/status/2038595927225622569) — Aggie · Mar 30, 2026 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/cross-platform-arbitrage.md b/.qoder/plans/pm-plan/theory_concepts/cross-platform-arbitrage.md
new file mode 100644
index 0000000000..742eeb3917
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/cross-platform-arbitrage.md
@@ -0,0 +1,63 @@
+# Cross-Platform Arbitrage
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Cross-Platform Arbitrage](https://www.pmatlas.xyz/concepts/cross-platform-arbitrage)
+
+---
+
+## Definition
+
+**Quick definition.** Trading the same event across different prediction-market platforms to profit from price discrepancies. In practice it's rarely risk-free because different platforms resolve the "same" event under different rules and oracles, leaving residual basis risk.
+
+## Key Insights
+
+- Lotus: same contract priced at **58–67 cents on different platforms simultaneously**. Cross-venue fragmentation is its own asset, and conventional MMing fails partly because of it.
+- Kunal Doshi (Kalshi vs. FanDuel): in-game prices correlate at **0.99+** with FanDuel; Kalshi monthly volume grew **80× to $14.4B in March 2026** (NCAA March Madness alone = $3.3B). But Kalshi taker fees up to **3.5% at midpoint** and **76% in-game depth decline vs. pre-game** currently limit institutional execution. Valuation comp: Kalshi $20B as exchange vs. sportsbooks at 2–4× revenue.
+- Pantera/Sui's NFL dataset (282 games, Sep 2025–Jan 2026) gave concrete numbers to cross-platform arb potential: Kalshi $1.3B notional vs. Polymarket $359M for the same games; Kalshi leads in 80% of large moves with median **7-second lead**; but Polymarket has **3-4× more concentrated liquidity** by Kyle-λ. The arb opportunity is in the latency gap *and* the depth differential.
+- st1ne identifies five MEV-style edges, one of which is cross-platform conditional probability arbitrage across correlated markets. Polymarket is "a hidden MEV playground."
+- Saguillo et al.: **~$40M extracted from Polymarket via single-market and cross-related-market arbs**, identified via blockchain tx analysis. Two distinct types: Market Rebalancing Arbitrage (within a market when YES+NO < $1) and Combinatorial Arbitrage (across topically related markets). Their analysis required heuristic reductions to bring the O(2^(n+m)) comparison down to feasible.
+- Hall & Paschal's overlap data: only **70 of 131 (53%) resolved US elections appeared on both Kalshi and Polymarket**, and overlap drops to **18%** among 1,826 active electoral events. Cross-platform arb is structurally constrained because the same event simply isn't listed on both venues most of the time. They explicitly call for an ISDA-equivalent for PM contract definitions.
+- Sethi ("Information Contagion"): the 15-minute spike in oil/stock futures preceding Trump's March 24, 2026 Iran-negotiations tweet shows arbitrage *across* PM and traditional venues · quant funds extracted the signal from Polymarket and acted in regulated derivatives, profiting without breaking any law. This is the most consequential form of cross-platform arb because it routes around KYC.
+- Mike's risk-instruments piece: cross-platform composability is live via Gondor (lending against PM positions) and DFlow (tokenizing Kalshi contracts as SPL tokens). Liquidity fragmentation, execution risk, UX are barriers.
+- Nekt0 lists cross-platform arb as one of eight strategies, with dollar examples and specific risk factors.
+- Cryptonomads: most PM terminals fail at execution; the cross-platform layer is exactly where institutional API rails win.
+- The implicit thesis across these pieces: **the same event being available on Kalshi (regulated, KYC) and Polymarket (offshore, pseudonymous) is the largest single source of structural arbitrage in the asset class** · and information contagion across this boundary (Sethi) is what regulators are now watching.
+- michaellwy: each Polymarket↔Kalshi pair is fundamentally non-fungible because of different referee systems. "Identical" contracts on Polymarket (UMA oracle) and Kalshi (CFTC-designated contract market) can resolve differently · eliminating the "risk-free" element of arbitrage in many cases.
+
+## Notable Quotes
+
+> "The same contract at 58-67 cents on different platforms."
+> — *Lotus*
+
+> "In-game prices correlate at 0.99+ with FanDuel."
+> — *Kunal Doshi*
+
+> "Information flows freely across platforms even when regulatory frameworks differ."
+> — *Rajiv Sethi*
+
+> "Only 70 of 131 resolved elections appeared on both platforms; among 1,826 active electoral events, overlap drops to 18%."
+> — *Hall & Paschal*
+
+## Where It Matters
+
+Cross-platform arbitrage is what makes the multi-venue prediction-market world *behave* like a single market · without it, Polymarket and Kalshi quotes could drift indefinitely. But the arbitrage is bounded by capital lock-up (positions don't settle until resolution), oracle divergence (Kalshi's CFTC settlement vs. UMA on Polymarket can differ), and KYC frictions (the same trader cannot always access both venues legally). Every "meta-pool" or "unified PM liquidity" proposal in 2026 is effectively an attempt to internalize cross-platform arbitrage on behalf of the network.
+
+## Related Concepts
+
+- **Arbitrage** · the parent concept.
+- **Liquidity fragmentation** · what creates the cross-platform spread.
+- **Resolution criteria / oracle design** · what limits "risk-free."
+- **Time arbitrage / orderflow arbitrage** · co-located strategies.
+- **Execution quality** · what arbitrageurs sell as a service to slower flow.
+- **Platform competition** · the meta-game.
+
+## Sources
+
+- [Why Every Prediction-Market Terminal Will Fail (and the Two That Won't)](https://x.com/0xCryptoNomads/status/2048664550225170605) — cryptonomads · Apr 27, 2026
+- [Market Making In PMs Sucks](https://x.com/uselotusai/status/2046639095531614455) — Lotus · Apr 21, 2026
+- [From Betting to Trading: How Kalshi Is Reshaping Sports Markets](https://x.com/Kunallegendd/status/2044780671839952949) — Kunal Doshi · Apr 16, 2026
+- [Polymarket Is a Hidden MEV Playground. Most Traders Have No Idea.](https://x.com/SolSt1ne/article/2032008002094366899) — st1ne · Mar 13, 2026
+- [How Prediction Markets Turn Into Risk Instruments](https://x.com/mikhryc0x/article/2020802941091741972) — Mike · Feb 9, 2026
+- [All Types of Arbitrage on Prediction Markets](https://x.com/Nekt_0/article/2019107985079816395) — Nekt0 · Feb 5, 2026
+- [Unravelling the Probabilistic Forest: Arbitrage in Prediction Markets](https://arxiv.org/abs/2508.03474) — Saguillo, Ghafouri, Kiffer, Suarez-Tangil · Aug 5, 2025
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/cross-subsidization.md b/.qoder/plans/pm-plan/theory_concepts/cross-subsidization.md
new file mode 100644
index 0000000000..7ea8d1fc8c
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/cross-subsidization.md
@@ -0,0 +1,49 @@
+# Cross-subsidization
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — Cross-subsidization](https://www.pmatlas.xyz/concepts/cross-subsidization)
+
+---
+
+## Definition
+
+**Quick definition.** Using revenue from commercially profitable PMs to fund liquidity incentives on socially valuable but not-yet-self-sustaining markets · mirroring how newspaper ads subsidized investigative journalism.
+
+## Key Insights
+
+- Alan Wu's central frame: PM prices are public goods. The benefits (informational signal, hedging tool, accountability) are non-excludable, but the liquidity costs fall on a narrow trader base. Without cross-subsidization, only commercially profitable markets survive · and the most informationally valuable markets (long-tail policy, scientific outcomes, accountability) are often the least profitable.
+- The newspaper analogy: classifieds and display ads funded the investigative-journalism beat that society needed but no individual reader would have paid for directly. PMs can do the same · sports and crypto profits subsidize political, scientific, and policy markets.
+- Wu's secondary argument: accuracy isn't the only axis of value. Markets serve risk transfer (hedging hurricane or policy exposure) and information accountability functions even when prices drift from pure probability. The cross-subsidy can be justified by these auxiliary functions even where the market isn't perfectly calibrated.
+- This connects directly to Hall & Paschal's "Building the Truth Machine" prescription: cross-subsidize political liquidity from sports profits. They find only 1.3% of political markets are liquid enough to be manipulation-resistant · fixing this requires money to flow from where it's earned (sports) to where it's needed (politics).
+- The cross-subsidization concept's related concepts (per the on-prediction page) include adverse selection · informed traders systematically extract from MMs in low-volume political markets, and the cross-subsidy is precisely the mechanism for compensating MMs for taking that loss.
+- Hedging-as-justification: Wu argues some socially valuable markets (climate, geopolitical risk) exist primarily as risk-transfer mechanisms, not pure forecasting. Cross-subsidies are justified because the social value of available hedges exceeds the narrow trading profits the market generates.
+- Implicit critique of pure profit-maximization: platforms that only run sports/crypto markets are not contributing to the public-good case for PMs; they're free-riding on the legitimacy that information-valuable markets provide.
+- The mechanism could take multiple forms: explicit subsidies (MM rebates funded from sports rake), platform fee redistribution, or governance-level allocation. Andy Hall's a16z piece implicitly assumes the platform itself does the allocation.
+- Cross-subsidization is essentially "Tobin tax on entertainment markets to fund information markets" · a redistributive design choice that platforms can make voluntarily or that regulators could impose.
+- Without cross-subsidies, the ghost-market problem (functionSPACE) gets worse over time: as more questions are atomized into thin binary contracts, more markets fall below minimum viable liquidity, and the informational-value half of the PM thesis hollows out.
+- Cross-subsidies don't address the manipulation problem (Prosperi documented 23% open-interest concentration); they only address the funding problem. A subsidized market that's still thin can still be manipulated.
+
+## Notable Quotes
+
+> "Prediction market prices are public goods whose benefits are non-excludable but whose liquidity costs fall on a narrow trader base."
+> — *paraphrased from Alan Wu, *How Prediction Markets Can Ascend**
+
+> "Profitable markets fund socially valuable ones that can't sustain themselves, the same way newspaper ads funded investigative journalism."
+> — *paraphrased from Alan Wu*
+
+## Where It Matters
+
+Cross-subsidization is the missing economic mechanism for making PMs work as information infrastructure rather than pure entertainment. Without it, the platform incentive is to keep adding sports/crypto markets indefinitely; with it, the platform's own sports profits underwrite the long-tail political markets that justify the "truth machine" narrative. For Dekant: continuous-curve markets have a structural cross-subsidy property · a single liquidity pool over a continuous outcome covers the whole curve, so liquidity earned on a popular region's trading volume effectively underwrites the tails of the same distribution without an explicit subsidy mechanism.
+
+## Related Concepts
+
+- Liquidity provision · the cost that needs subsidizing
+- Adverse selection · the cost MMs face in thin markets
+- Long-tail markets · the obvious subsidy target
+- Hedging · the auxiliary use case justifying socially valuable markets
+- Minimum viable liquidity · the threshold subsidies aim to push markets above
+
+## Sources
+
+- [How Prediction Markets Can Ascend](https://x.com/alanwu/article/2036438579824496906) — Alan Wu · Mar 25 2026 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/decision-markets.md b/.qoder/plans/pm-plan/theory_concepts/decision-markets.md
new file mode 100644
index 0000000000..47ac1ac9fa
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/decision-markets.md
@@ -0,0 +1,82 @@
+# Decision Markets
+
+**Category:** Governance & Decisions
+**Source:** [PM Atlas — Decision Markets](https://www.pmatlas.xyz/concepts/decision-markets)
+
+---
+
+## Definition
+
+**Quick definition.** Prediction markets explicitly designed to inform specific decisions by forecasting outcomes of each option. The mechanism: agree on a metric of success, then run conditional markets to determine which policy or action is most likely to improve that metric.
+
+## Key Insights
+
+- The intellectual lineage runs back to Robin Hanson's futarchy proposal (Hanson, 2000) · decision markets are the operational mechanism that makes "vote on values, bet on beliefs" tractable. They extend prediction markets from forecasting events to selecting actions.
+- The current state-of-the-art implementations are MetaDAO (futarchy for startups, using token price as the KPI) and Combinator (decision-market infrastructure protocol). Both use a conditional-futures architecture: "what will the KPI be if we take action A?" vs. "what will the KPI be if we don't?".
+- Alex Janiak's diagnosis: current implementations of decision markets fail outside a few narrow market types because of two structural problems · (1) thin markets / lack of informed traders, and (2) overly complicated conditional-futures architecture.
+- The trader problem maps onto Minimum Viable Liquidity (MVL): markets only price accurately above a capital threshold that makes it worth informed traders' time to participate. Polymarket and Kalshi clear MVL for major events because audiences are huge; decision markets typically can't because the pool of informed traders is tiny (employees, advisors, a few competitors).
+- Decision markets are hardest to use exactly where they would be most valuable: at the company or individual level, where decisions are idiosyncratic and the relevant context (financials, competitive landscape, team capabilities) is private. Markets need traders to be "open book," which is impractical.
+- Two cases work in practice despite the theoretical limits: (1) when you want **aggregated opinions** (which landing page converts better, which feature to prioritize) · i.e., a beauty-contest task rather than informational price discovery, and (2) when you want to **distribute trust** rather than optimize a decision (commitment device: a decision can only execute if the market endorses it).
+- The architecture problem with token-price-as-KPI: a token's price reflects everything the market believes about the protocol · macro sentiment, unrelated news, general market inefficiency. You can correctly predict that a decision will improve fundamentals and still lose money because the token moves for unrelated reasons. This heavily disincentivizes rational informed trading, exactly what the mechanism depends on.
+- Subtler structural issue: if you price tokens against a KPI in a conditional vault and the KPI improves, token holders redeem at higher value · the protocol is effectively shorting its own success metric.
+- Galaxy's framing (Pokorny): Decision Markets extend Impact Markets from information revelation to governance automation. Rather than merely surfacing conditional valuations to inform individual decision-making, they directly and bindingly determine whether an organization takes an action based on which outcome the market prices higher.
+- For Decision Markets to produce meaningful signals, the traded asset must be **causally connected** to the outcomes the decision aims to optimize; ideally, holders have material ownership claims over the economic value generated. Most crypto governance tokens fail this test · a chain's governance token cannot effectively guide ecosystem grant allocations when its value is structurally detached from the growth of applications built on the chain.
+- Retrofitting decision markets onto existing DAOs is hard because established governance, social norms, informal power structures, and token/ownership structures may not align token value with organizational outcomes. New organizations purpose-built for decision-market governance face fewer such constraints · they can architect token economics from inception so success necessarily flows to token value.
+- Decision markets are **reductive by design** · they collapse complex tradeoffs into a single dimension (economic value). This is a feature for capital allocation, resource deployment, and economically dominant strategic choices, but a limitation for decisions where alignment, community capital, or other qualitative factors matter.
+- LessWrong "Prediction Markets Are Mediocre" critique: conditional prediction markets that ask "Will X lead to consequence Y?" don't actually convert probability assessments into useful policy guidance. The Polymarket "Trump imposes tariffs in first year" market sat at 56% on election day · neither low enough to dissuade Trump-leaning voters nor high enough to function as a deal-breaker. Adding more liquidity only marginally reduces fluctuation; layering meta-markets on meta-markets adds indirection without adding information.
+- Hanson's "distilled human judgement" idea (via Vitalik): you have a trusted but expensive judgement mechanism (e.g., a court, expert panel). Set up a prediction market on what that mechanism would decide. 99.99% of the time, you never actually invoke the costly mechanism · you just use the market's average price. 0.01% of the time you invoke it, and compensate participants based on the verdict. This gives you a fast, cheap, credibly neutral "distilled copy" of the expensive process · and is a natural fit for DAOs where most votes shouldn't actually happen.
+- Two genuinely promising directions from Janiak: (1) use AI forecasters as automated market makers to drop the MVL floor · synthesizing information and providing initial price discovery so the minimum threshold to attract informed humans drops; (2) abandon conditional futures for noisy KPIs in favor of combinatorial markets directly on the question ("Will this KPI increase if we take this decision?") · less information-dense but legible and free of the architecture problems.
+- A subtle but important critique from commenter Elias Kunnas: futarchy outputs a **price instead of a model**. Traders model privately, but the institution doesn't see causal assumptions, hidden-variable audits, or any falsifiable claim to revise later. This is a separate failure mode from thin liquidity and KPI noise · and arguably explains why the working cases (aggregated opinions, commitment devices) are exactly the ones where model legibility doesn't matter.
+- Mohamed Elrashid's argument extends this: forecasting accuracy has outpaced product design. The CTO of Cultivate Labs, after a decade running prediction markets inside the US intelligence community, says he "can't remember a single time that someone told us they had issues with the forecasts not being accurate enough." The bottleneck for decision markets isn't accuracy · it's that probability outputs don't slot into decision-makers' workflows. Simulation startups (Aaru, Simile) raised nine-figure rounds in 15 months because they delivered artifacts shaped like the consulting deliverables institutions already buy.
+
+## Notable Quotes
+
+> "Decision markets are hardest to use exactly where they'd be most valuable: at the company or individual level, where decisions are idiosyncratic and private."
+> — *alexjaniak, *Where Are All the Decision Markets?**
+
+> "A token's price reflects everything the market believes about the protocol: macro sentiment, unrelated news, and general market inefficiency. You might correctly predict that a specific decision will improve fundamentals and still lose money because the token moves against you for reasons entirely unrelated to that decision."
+> — *alexjaniak*
+
+> "In 10+ years, I can't remember a single time that someone told us they had issues with the forecasts not being accurate enough."
+> — *Cultivate Labs CTO (quoted in Mohamed Elrashid, *Good Forecasts, Bad Products*)*
+
+> "Even past your two implementation problems, futarchy outputs a price instead of a model. Traders model privately but the institution doesn't see causal assumptions, hidden-variable audits, or any falsifiable claim to revise later."
+> — *Elias Kunnas (comment on Janiak)*
+
+> "Decision Markets extend the Impact Markets mechanism from information revelation to governance automation… directly and bindingly determine whether an organization takes an action based on which outcome the market prices higher."
+> — *Zack Pokorny, Galaxy Research*
+
+> "Decision Markets are powerful precisely because they're reductive: they collapse complex tradeoffs into a single dimension of economic value."
+> — *Zack Pokorny, Galaxy Research*
+
+## Where It Matters
+
+Decision markets are the bridge between prediction markets as "news at the chart level" and prediction markets as governance infrastructure. They are the canonical answer to "what is this category good for beyond elections and crypto prices?" · and the answer so far is murkier than the theory suggests. The current generation of implementations clarifies that information aggregation is necessary but not sufficient: decision markets also need a causally-connected asset, legible interfaces for traders, and ideally AI-driven market makers to drop the MVL floor for idiosyncratic decisions.
+
+## Related Concepts
+
+- **Futarchy** · Decision markets are the operational mechanism inside Hanson's futarchy proposal.
+- **Conditional tokens** · The canonical implementation pattern: pABC/fABC (pass/fail) conditional tokens settle in opposite states depending on whether the decision is taken.
+- **Impact markets** · Decision markets are Impact markets with binding governance hooks; the same conditional pricing machinery, but the highest-priced state determines organizational action.
+- **Minimum viable liquidity** · The MVL framework explains why decision markets struggle: idiosyncratic decisions can't recruit enough informed traders to clear the liquidity threshold.
+- **Reflexivity** · Decision markets are a controlled reflexive system: the market's price doesn't just predict the outcome, it determines the action that produces the outcome.
+- **Info finance** · Vitalik's broader framing places decision markets as one application within a "correct-by-construction" info-finance discipline.
+- **Hyperstition markets** · Decision markets are the rationalist cousin of hyperstition markets; both involve markets choosing outcomes, but hyperstition markets are explicit about reflexivity as a feature.
+- **Wisdom of crowds / forecasting accuracy** · Decision markets inherit the wisdom-of-crowds advantage but face a deployment gap: accurate forecasts aren't decisions.
+
+## Sources
+
+- [Where Are All the Decision Markets?](https://www.lesswrong.com/posts/bDxqMn3GJEhPeqZey/where-are-all-the-decision-markets) — alexjaniak, LessWrong · May 12, 2026 ·
+- [Good Forecasts, Bad Products](https://mo-el.xyz/blog/good-forecasts-bad-products/) — Mohamed Elrashid · Apr 24, 2026 ·
+- [Polls Are Dead. Long Live Prediction Markets.](https://x.com/CalBlockchain/status/2047460674532790499) — Blockchain at Berkeley · Apr 23, 2026
+- [On War Markets](https://x.com/probaaron/article/2027765452689014822) — aaron · Feb 28, 2026 ·
+- [The Shape of Prediction Markets to Come](https://www.galaxy.com/insights/research/prediction-markets-leverage-ai-agents-defi) — Will Owens, Galaxy · Jan 19, 2026
+- [Prediction Markets' Next Frontier: Impact and Decision Markets](https://www.galaxy.com/insights/research/prediction-markets-impact-markets-decision-markets-futarchy) — Zack Pokorny, Galaxy · Jan 12, 2026 ·
+- [Stop Predicting. Start Manipulating.](https://x.com/hyperstiti0ns/article/1994422160790536299) — HYPERSTITIONS · Nov 28, 2025 ·
+- [A Small Prediction Market Design Taxonomy](https://x.com/aaronjmars/article/1991975420669915328) — aaronjmars · Nov 22, 2025 ·
+- [Opportunity Markets](https://www.paradigm.xyz/2025/08/opportunity-markets) — Dave White, Matt Liston, Paradigm · Aug 18, 2025 ·
+- [Prediction Markets Are Mediocre](https://www.lesswrong.com/posts/opzJP7QHgyHzMth5o/prediction-markets-are-mediocre) — Ape in the coat, LessWrong · Apr 5, 2025 ·
+- [10 Predictions About Prediction Markets](https://x.com/michael_lwy/status/1884570344184578081) — michaellwy · Jan 29, 2025 ·
+- [From Prediction Markets to Info Finance](https://vitalik.eth.limo/general/2024/11/09/infofinance.html) — Vitalik Buterin · Nov 9, 2024 ·
+- [Futarchy as Trustless Joint Ownership](https://www.umbraresearch.xyz/writings/futarchy) — Kevin Heavey · Oct 28, 2024 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/demand-markets.md b/.qoder/plans/pm-plan/theory_concepts/demand-markets.md
new file mode 100644
index 0000000000..1722d72312
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/demand-markets.md
@@ -0,0 +1,46 @@
+# Demand markets
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — Demand markets](https://www.pmatlas.xyz/concepts/demand-markets)
+
+---
+
+## Definition
+
+**Quick definition.** Markets that capture consumer purchase intent as a probability signal before production, replacing surveys and focus groups with skin-in-the-game demand forecasting.
+
+## Key Insights
+
+- Aggie (BlondiePredicts) frames demand markets as one of three "probability layers" beyond trading, alongside attention markets and credibility markets. The endgame: probability signals embedded invisibly into every decision surface on the internet.
+- The core concept: a market on whether N consumers will pre-commit to buying product X at price P before launch. The market price aggregates demand intent without requiring a survey or focus-group budget. Skin-in-the-game replaces stated-preference noise.
+- This is a cleaner version of crowdfunding (Kickstarter/Indiegogo): crowdfunding requires the consumer to actually fund the product; demand markets only require them to put down a small probability-weighted stake on whether the product hits a demand threshold.
+- The infrastructure layer: as the cost of producing real-time probability estimates collapses (functionSPACE), demand markets become viable for ever-smaller decisions. Eventually every product launch could have one.
+- Strategic implications for product teams: instead of running ten user-research interviews and three surveys, a demand market lets the team observe a continuously updating probability of meeting their sales target. The signal is noisy at first (low liquidity) but improves as more participants stake.
+- Connection to attention markets: a product launch's demand signal correlates with its viral attention signal. A combined demand+attention market could outperform either alone.
+- Connection to credibility markets: if the company making a product has a low credibility score, the demand market should price in execution risk · does the company actually ship?
+- The financialization-of-social-networks thesis (Joel John): demand markets are the consumer-product manifestation of linking financial stakes to social signals. Polymarket's brief #1 app-store moment was partly a demand-market-like signal · users staked on social-political outcomes.
+- The functionSPACE supply-side framing applies: as forecasting cost approaches zero, the addressable market for demand-related PMs extends from product launches to every B2C and even B2B decision (will customer X renew their contract? will the partnership close?).
+- Privacy concerns: a demand market reveals individual purchase intent if not designed carefully. Stealth Pay-like privacy primitives or pool-based aggregation are likely required for B2B use cases where intent leakage is a competitive risk.
+- The mechanism design constraint: demand markets need to resolve on observable outcomes (sales hit target Y by date Z) or self-resolve via on-chain signals (NFT mints sold, tokens claimed). The resolution challenge is the same as for any niche PM · but the upside is they can plausibly self-resolve from commerce data.
+
+## Notable Quotes
+
+> "Probability signals embedded invisibly into every decision surface on the internet."
+> — *paraphrased from Aggie, *The Probability Layers Are Coming**
+
+## Where It Matters
+
+Demand markets push the PM thesis past pure forecasting into operational decision-making. They're the most direct example of "info-finance for product teams" · a market that doesn't just predict but actively shapes a launch decision. For Dekant: continuous-curve markets over unit-sales distributions or price-elasticity curves are the natural fit. Drawing a curve for "how many units will the new product sell" expresses a richer belief than a binary "more than X / less than X."
+
+## Related Concepts
+
+- Probability infrastructure · the umbrella concept
+- Credibility markets · the trust score that prices in execution risk
+- Attention markets · the demand correlate
+- Info finance · the macro frame
+- Information aggregation · the underlying mechanism
+
+## Sources
+
+- [The Probability Layers Are Coming](https://x.com/BlondiePredicts/status/2038595927225622569) — Aggie · Mar 30 2026 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/dispute-resolution.md b/.qoder/plans/pm-plan/theory_concepts/dispute-resolution.md
new file mode 100644
index 0000000000..b0f94ef003
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/dispute-resolution.md
@@ -0,0 +1,76 @@
+# Dispute Resolution
+
+**Category:** Oracle & Resolution
+**Source:** [PM Atlas — Dispute Resolution](https://www.pmatlas.xyz/concepts/dispute-resolution)
+
+---
+
+## Definition
+
+**Quick definition.** Dispute resolution is the *appeal layer* on top of an oracle · the mechanism by which a participant can challenge a proposed market outcome and force adjudication by an independent body (UMA token-holders, a centralized committee, an AI judge, or some combination). In current production systems it is the single most-stressed surface in the entire prediction-market stack: 14 documented failures over 18 months at Polymarket and Kalshi affecting over $500M in volume.
+
+## Key Insights
+
+- **The Polymarket/UMA "decentralized courtroom" has four phases**: assertion (someone posts the proposed outcome with a bond), liveness period (window for any party to challenge by posting a counter-bond), dispute (the dispute is routed to UMA's Data Verification Mechanism), DVM vote (UMA token-holders vote on-chain, weighted by staked tokens). Aligned voters earn rewards; dissenters get slashed.
+- **The DVM is gameable when value-at-stake exceeds the cost of swinging the vote.** UMA's market cap in 2024 was ~$160-220M; a $500M presidential market would have made buying-and-voting economically rational. Frank Muci walks through the explicit scenario: hyper-polarized voters + ability to bet on the very market you're voting on = trivially extractable.
+- **UMA voters have admitted conflicts of interest.** They can hold the market position *and* vote on its resolution. There is no recusal mechanism. The Venezuela case (Muci) is the canonical demonstration: voters lobbied each other in Discord, then voted to declare Gonzalez the winner despite the Polymarket primary resolution source (Venezuelan electoral authority) declaring Maduro.
+- **Polymarket has been forced to overrule its own published rules at least twice.** Venezuela 2024: "primary source = official information from Venezuela" was overridden in favor of "consensus of credible reporting" (a secondary clause). Government shutdown 2025: market resolved YES to a shutdown that never happened, on a website-update technicality.
+- **14 named resolution failures, totaling $500M+** (XO Market catalog):
+- **$242M** Zelensky-suit market (semantic ambiguity)
+- **$120M** TikTok ban market (criteria interpretation)
+- **$47M** Cardi B halftime market (resolved YES on Polymarket, NO on Kalshi · same event, two referees)
+- plus 11 more cases grouped into four patterns: vague criteria, decentralized oracle capture, centralized operator discretion, cross-platform divergence.
+- **Cross-platform divergence is a new category of dispute.** When two markets price the same event and resolve differently, neither traders nor arbitrageurs have a forum to appeal to. The "dispute" is between platforms, not within one.
+- **Pre-resolution clarifications are a back-door rule change.** OddChain on the Venezuela invasion market: Polymarket issued a "clarification" on Jan 4 · *after* the Jan 3 raid and *after* Trump's "we will run the country" statement · that retroactively narrowed what counted as an invasion. From the trader's perspective, this is changing the rules mid-game. "Polymarket has descended into sheer arbitrariness."
+- **The five-step appeal anatomy on Polymarket** (Lou Kerner walkthrough): (1) market resolves under rule, (2) UMIP-107 specifies 4 possible oracle responses (p1, p2, p3, p4), (3) dispute initiated with $100k bond, (4) UMA holders vote with off-chain Discord/forum lobbying, (5) result is irreversible on-chain · automatic payout cannot be unwound.
+- **AI judges as dispute resolvers are the leading proposed fix.** Hall (a16z): commit a specific LLM model and prompt to chain at market creation; at dispute, the committed AI runs the committed prompt with committed sources, output is binding. Tradeoff: AI can hallucinate, but cannot be bribed in real time.
+- **Modular dispute resolution is being discussed as a 2025 trend.** michaellwy 10 Predictions: AI as arbiters, Kleros-style multi-round dispute systems, dispute pools dedicated to specific market categories. The general direction is *specialization* · different markets need different dispute substrates.
+- **The arbitrage opportunity across dispute systems is itself a risk.** michaellwy: apparent arbitrage between Polymarket and Kalshi on identical-looking markets is *not* risk-free, because the resolution criteria and dispute mechanisms differ. A trader long-Polymarket-YES / short-Kalshi-NO can lose both legs if the dispute systems disagree.
+- **Dispute sniping is a documented MEV strategy.** st1ne lists "dispute sniping · gaming the UMA dispute process" as one of five Polymarket MEV edges: front-run the bond posting, time the dispute trigger around the liveness window, exit positions before the DVM vote concludes.
+- **Bond requirements are a load-bearing parameter.** A $100k dispute bond on a $240M market is rounding error · the bond should scale with value-at-stake to deter spurious disputes *and* deter capture. Current parameters were calibrated for synthetic-asset use cases, not prediction markets.
+- **Off-chain lobbying is functionally part of the protocol.** Discord and forum activity during the liveness window has more impact on outcomes than the bond mechanics. The "decentralized" piece is the vote tally; the deliberation is fully social. Smaliy frames this as the "courtroom" metaphor · and like courtrooms, it's susceptible to who shows up and who has the louder voice.
+- **Polymarket's PolyLend creates compounding dispute risk.** Borrowing against conditional tokens means a disputed resolution can cascade into liquidations on lending positions. Nardi flags this as a structural weakness of the Polymarket/UMA stack.
+- **Repugnance markets force dispute systems to make moral judgments.** michaellwy CFTC comment: markets on individual deaths, terrorism, or assassination create dispute scenarios where the *existence* of the disputed contract is the problem, not the specifics of resolution. The Charlie Kirk assassination + Kalshi market-void is the case study (Guillory & Zimmermann).
+- **The XO Labs framework names this the "resolver trilemma."** Efficiency (resolve fast), decentralization (no single point of trust), security (manipulation-resistant) · pick at most two. Optimistic oracles trade security for efficiency; centralized resolvers trade decentralization for both; layered systems try to route different markets to different tradeoffs.
+
+## Notable Quotes
+
+> "Like Venezuela's captured institutions, UMA's decision simply ignored the rules, and Polymarket users who correctly predicted the future and hedged against Maduro winning or falsely claiming victory had their money stolen."
+> — *Frank Muci, *Polymarket Settles Bet Against Its Own Rules**
+
+> "Words are redefined at will, detached from any recognized meaning, and facts are simply ignored. That a military incursion, the kidnapping of a head of state, and the takeover of a country are not classified as an invasion is plainly absurd."
+> — *Polymarket user, in *Semantics for $10 Million**
+
+> "The fundamental problem is the same: When large sums of money depend on determining what happened in an ambiguous situation, every resolution mechanism becomes a target for being gamed, and every ambiguity becomes a potential flash point."
+> — *Andy Hall*
+
+> "Read the resolution criteria, not the title… Model the resolver, not reality."
+> — *OddChain*
+
+## Where It Matters
+
+Dispute resolution is the second-order failure mode: even when the oracle works correctly, the dispute layer becomes the new attack surface for adversaries with capital. A platform's dispute system is effectively its constitution · when it works, it builds trust faster than any UX investment; when it fails, the market becomes a venue for political capture. As stakes scale past UMA's market cap, the dispute layer has to either decentralize further (harder), centralize back (politically unpopular), or be replaced by deterministic AI-judge commitments.
+
+## Related Concepts
+
+- **oracle design** · dispute resolution is the appeals layer; oracle is the trial court.
+- **UMA protocol** · the implementation that 90% of these case studies stress-test.
+- **corruption value multiple** · the diagnostic for whether the dispute layer is economically secure.
+- **resolution criteria** · vague criteria *generate* disputes; precise criteria prevent them.
+- **market manipulation** · disputes are themselves a manipulation surface (dispute sniping, vote-buying).
+- **AI agents** · proposed dispute substrate (LLM judges).
+
+## Sources
+
+- [Resolving Prediction Markets: An AI-Driven Layered Approach](https://x.com/xolabs_/status/2055266974259961917) — XO Labs · May 15 2026 ·
+- [The Hidden Risk Of Prediction Markets: 14 Resolution Failures That Cost $500M](https://x.com/xomarket/status/2046924302952640934) — XO Market · Apr 22 2026 ·
+- [The Economy of Truth: Inside the Decentralized Courtroom of Polymarket & UMA](https://x.com/smaaaaliy/article/2033242239498076190) — Smaliy · Mar 16 2026 ·
+- [What to Do When Prediction Markets Fail](https://a16zcrypto.substack.com/p/how-ai-judges-can-scale-prediction) — Andy Hall · Jan 24 2026 ·
+- [Semantics for $10 Million: When Is an Invasion an Invasion?](https://www.oddchain.com/p/semantics-for-10-million-when-is-an-invasion-an-invasion-polymarket) — OddChain · Jan 23 2026 ·
+- [The Risk Behind Arbitrage in Prediction Markets](https://x.com/michael_lwy/status/1925890768654254571) — michaellwy · May 23 2025 ·
+- [Why Prediction Markets Are Broken (And How to Fix Them)](https://x.com/michael_lwy/status/1877211555496079683) — michaellwy · Jan 9 2025 ·
+- [My Polymarket Conspiracy Theory](https://medium.com/quantum-economics/my-polymarket-conspiracy-theory) — Lou Kerner · Oct 20 2024
+- [Polymarket and the Proliferation of Prediction Markets](https://www.shoal.gg/p/prediction-markets) — Alex Nardi · Oct 8 2024 ·
+- [Mechanisms for Prediction Markets](https://paragraph.com/@mechanism.institute/prediction-markets) — Hadi, Cossar, Shimony · Aug 22 2024 ·
+- [Polymarket Settles Bet Against Its Own Rules](https://frankmuci.substack.com/p/polymarket-settles-bet-against-its) — Frank Muci · Aug 8 2024 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/distribution-markets.md b/.qoder/plans/pm-plan/theory_concepts/distribution-markets.md
new file mode 100644
index 0000000000..10a17991d3
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/distribution-markets.md
@@ -0,0 +1,48 @@
+# Distribution Markets
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Distribution Markets](https://www.pmatlas.xyz/concepts/distribution-markets)
+
+---
+
+## Definition
+
+Markets that trade on full probability distributions rather than single binary yes/no outcomes. Traders express a *shape* of belief (mean, variance, skew, multi-modal structure) and are paid by how close the realized outcome falls to the shape they staked on. Paradigm's Dave White paper (Dec 2024) formalized the modern version; Dekant is the first onchain production implementation.
+
+## Key Insights
+
+- Binary yes/no PMs are incomplete: they flatten nuanced beliefs into coin flips and pay the same whether you were barely right or sharply right. Distribution-native markets reward precision · pay more for being closer to the actual outcome. 130x volume growth from early 2024 to late 2025 = category's credibility moment (Tide manifesto, "Make Precision Pay").
+- "What if we're capturing the wrong signal?" · binary markets flatten complex beliefs, losing precision that separates superforecasters from average predictors. 2024 French trader whale ($30M moving election odds) and Vanderbilt study (PredictIt 93% vs 67% on high-volume platforms) · more liquidity ≠ better signal (Jo).
+- Information Vectors thesis: binary event contracts fragment liquidity and flatten beliefs into 1-bit structures. *Achieving 8-bit resolution requires 256 separate markets.* Proposes treating beliefs as vectors over probability distributions on a shared liquidity surface. Traders express full distributions; rewarded for *variance compression* (reducing entropy), not just final outcome correctness (functionSPACE).
+
+## Notable Quotes
+
+> "Achieving 8-bit resolution requires 256 separate markets."
+> — *functionSPACE*
+
+> "Don't pick a side. Draw a curve."
+> — *paraphrased thesis across all three sources*
+
+> "Pay more for being closer to the actual outcome."
+> — *Tide*
+
+## Where It Matters
+
+This is the entire premise of Dekant. The three articles all converge on the same diagnosis (binary contracts under-resolve belief, fragment liquidity, and underpay precision) and propose essentially the same solution architecture (distribution-native / vector-of-beliefs / continuous-outcome markets). The volume growth cited (130x) plus the academic underconfidence-in-political-markets finding (prices compressing to 50%) form the empirical case that this category has tailwinds. For builders, the open implementation questions are: (1) what's the AMM invariant that supports a distribution surface? (L2-norm CFAMM, partition-of-unity smooth kernels), (2) what's the UX so retail can express a curve without a math degree? (sliders, drag-curve, presets), (3) how do you settle when realized outcome is continuous? (binned payouts, kernel-weighted partition-of-unity).
+
+## Related Concepts
+
+- **Information aggregation** · distribution markets aggregate at higher dimension
+- **Price discovery** · argued to be higher-bandwidth in continuous space
+- **Forecasting accuracy / Calibration** · the metric distribution markets claim to improve
+- **Wisdom of crowds** · distribution markets aggregate richer crowd signals
+- **Liquidity provision** · single shared liquidity surface across the whole distribution
+- **Superforecasting** · distribution markets reward the precision skill that separates superforecasters from amateurs
+- **LMSR / market scoring rules** · the mechanism family naturally extended to multi-outcome
+
+## Sources
+
+- [What If We're Capturing the Wrong Signal?](https://x.com/TideMarkets/article/2016912289941827801) — Jo · Jan 29, 2026 [FULL_READ]
+- [Information Vectors: An Intro to Composable Beliefs](https://x.com/functionspaceHQ/article/2014809461647671570) — functionSPACE · Jan 24, 2026 [FULL_READ]
+- [Manifesto: Make Precision Pay](https://x.com/TideMarkets/article/2008508916616098086) — Tide · Jan 6, 2026 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/distribution-moat.md b/.qoder/plans/pm-plan/theory_concepts/distribution-moat.md
new file mode 100644
index 0000000000..39212f8e79
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/distribution-moat.md
@@ -0,0 +1,50 @@
+# Distribution moat
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — Distribution moat](https://www.pmatlas.xyz/concepts/distribution-moat)
+
+---
+
+## Definition
+
+**Quick definition.** The competitive advantage a multi-asset platform (Robinhood, Coinbase) holds over specialized PM venues (Kalshi, Polymarket) by offering event contracts alongside stocks, options, crypto, and futures · cross-selling PMs to an existing large user base and undercutting standalone platforms on fees.
+
+## Key Insights
+
+- DCo's central claim: Robinhood's 27M funded users + cross-selling across stocks, options, crypto, and futures gives it an existential edge over standalone PM platforms. Standalone venues need to win each user from scratch; Robinhood imports them pre-funded with KYC, payment rails, and existing engagement.
+- The Rothera JV is the structural move: Robinhood bought the exchange infrastructure rather than partnering for it. Vertical integration captures the venue economics · taker fees, maker rebates, listing rights · that would otherwise leak to Kalshi.
+- The asymmetric regulatory exposure: regulatory threats to sports contracts hurt specialists far more than multi-asset brokers. If sports event contracts get banned in a state, Kalshi's revenue collapses; Robinhood loses one product line out of dozens.
+- Blocmates' stack thesis fits this neatly: "Execution wrappers like Coinbase and Robinhood are turning event contracts into a mainstream instrument shelf, while TradFi incumbents like Nasdaq and Cboe are pushing standardized, hedging-grade binaries that fit neatly into existing market structure and compliance regimes."
+- The "stack war" framing: rails (Polymarket crypto-native, Kalshi regulated) compete on settlement-layer credibility, while wrappers compete on user attention. Wrappers can run any rails · they're closer to the user, hold the spending wallet, and own the screen.
+- Implied corollary: Kalshi only wins long-term if it becomes the dominant rails layer beneath wrappers, not the consumer destination. Polymarket has the same fork · does it stay a consumer brand or commoditize as crypto-native settlement?
+- Kaviish quantifies the value flow: distribution platforms (Robinhood, Coinbase) will capture most value as they vertically integrate into exchange infrastructure. Sports drive 83% of Kalshi revenue today, but the valuations price an information-infrastructure future that wrappers may capture instead.
+- The DFS parallel (Gouker): DraftKings and FanDuel survived state regulatory pushback partly through casino partnerships and brand recognition. PM specialists without distribution face the same pressure DFS pure-plays did · most of which got acquired by sportsbook incumbents.
+- Coinbase already has its own PM offering and the crypto-wallet-as-distribution playbook. Their version of the moat is: every Coinbase user already has a USDC balance and a Polygon address; event-contract integration is a few-click product extension.
+- Robinhood's fee posture: as a wrapper, it can undercut Kalshi's taker fees because it's not capturing exchange revenue · it's monetizing through payment-for-order-flow, premium subscriptions, and cross-product engagement. Standalone PMs that need exchange-revenue margins to fund operations can't compete on fees.
+- The market structure concept page lists distribution moat as one of the four structural elements of PM stack stratification · alongside rails, wrappers, and product archetypes.
+
+## Notable Quotes
+
+> "Robinhood's distribution advantage, 27 million funded users and cross-selling across stocks, options, crypto, and futures, gives it an existential edge over standalone prediction market platforms like Kalshi and Polymarket."
+> — *paraphrased from DCo, *Why Robinhood Will Eat Kalshi's Lunch**
+
+> "Execution wrappers like Coinbase and Robinhood are turning event contracts into a mainstream instrument shelf."
+> — *blocmates, *The State of Prediction Markets**
+
+## Where It Matters
+
+Distribution moats are how the PM endgame becomes a stack war rather than a venue war. The winners may not be the platforms with the best matching engines but the ones with pre-existing user bases. For Dekant: this is the most strategically uncomfortable concept in the cluster · competing for venue attention is a losing game against Robinhood/Coinbase. The implied strategy is either to be a rails layer that distributes through wrappers, or to find a vertical where wrappers haven't bothered (continuous markets are arguably this · they require dedicated UI that wrappers won't build for a niche format).
+
+## Related Concepts
+
+- Platform competition · distribution moat is the dominant competitive lever
+- Network effects · wrappers import a pre-built network
+- Market structure · stack stratification (rails / wrappers / archetypes)
+- Event contracts · the inventory the wrappers shelf
+- Regulatory classification · favorable classification opens wrapper distribution
+
+## Sources
+
+- [Why Robinhood Will Eat Kalshi's Lunch](https://x.com/i/article/2052782036688228352) — DCo · May 11 2026 ·
+- [The State of Prediction Markets](https://stateofpredictionmarkets.blocmates.com/) — blocmates · Apr 1 2026 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/election-markets.md b/.qoder/plans/pm-plan/theory_concepts/election-markets.md
new file mode 100644
index 0000000000..2ba051d2ad
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/election-markets.md
@@ -0,0 +1,60 @@
+# Election markets
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — Election markets](https://www.pmatlas.xyz/concepts/election-markets)
+
+---
+
+## Definition
+
+**Quick definition.** PMs focused on political elections · historically the highest-volume and most visible category, but plagued by accuracy disputes and structural manipulation problems.
+
+## Key Insights
+
+- The 2024 calibration scoreboard (Clinton & Huang via John Sides): across "more than 2,500 political prediction markets" on IEM, Kalshi, PredictIt, and Polymarket in the final five weeks of the 2024 campaign (>$2B transactions), PredictIt 93%, Kalshi 78%, Polymarket 67% correctly predicted outcomes better than chance. Prices for identical contracts diverged across exchanges, daily price changes were weakly correlated or negatively autocorrelated, and arbitrage opportunities peaked in the final two weeks · undermining the "wisdom of crowds" story.
+- Prosperi documents structural distortions in 2024 Polymarket: four coordinated accounts controlled 23% of open interest, ~41% of volume appeared to be wash trading. Current platforms lack the conditions for reliable forecasting in his analysis.
+- Building the Truth Machine (Hall & Paschal): six-month empirical study finds only 1.3% of political markets liquid enough to be manipulation-resistant; bid-ask spreads exceed 20% on most contracts; only 53% of resolved US elections appeared on both Kalshi and Polymarket. Proposes stocking relevant questions, cross-subsidizing political liquidity from sports profits, deploying AI MMs where human interest is thin, and standardizing contract definitions across platforms.
+- Lou Kerner's Polymarket conspiracy: Fox News chosen as a settlement oracle despite unlikely calling the election for a non-Trump candidate; UMA token holders could sway disputed resolution votes given UMA's small market cap. Frames oracle design as the manipulation surface.
+- Sides surfaces a media-incentives concern: Kalshi's CNN and CNBC partnerships create incentives for sensational coverage of market movements and potential manipulation of thin markets.
+- The history (Eniola): from 1419 Vatican papal elections through Iowa Electronic Markets to Polymarket era. Surveys insider trading scandals (Musk tweets, French elections), moral hazard (assassination markets), and new entrants (Robinhood, DraftKings, Crypto.com, FanDuel) signaling mainstream adoption. Concludes the sector is evolving, not decaying.
+- Lauris's institutional-benchmark thesis: election ETFs (Bitwise's PredictionShares) are one of the three benchmark-building races (alongside corporate event notes and AI capability markets) that determine which categories win institutional capital.
+- Park's regulatory arc culminates in Bitwise PredictionShares ETF as the mainstream tipping point for political event contracts · same product that 1988 IEM no-action letter cracked the door for, finally with a retail-product wrapper.
+- ASXN: 99.2% of Polymarket trading volume concentrated in political markets and two-thirds of cumulative volume came in the six months before the 2024 election, raising sustainability questions for the platform's election-driven business model.
+- fil (Polymarket data analyst): comparison shows Polymarket priced in Biden's withdrawal probability while polls measured only head-to-head support · a demonstration of how PMs aggregate forward-looking signals polls miss.
+- Lydia Wu describes Polymarket as a creator-economy / crypto-media platform during election cycles, with the 166:1 visit/MAU ratio suggesting it functions as informational infrastructure beyond the trader population.
+- Nam Anh Le's calibration decomposition (frequentist + Bayesian, 96.3% posterior predictive coverage): 292M trades across 327K binary contracts on Kalshi and Polymarket; four-component model explains 87.3% of calibration variance; persistent underconfidence in political markets (prices chronically compressed toward 50%). Large trades amplify the underconfidence in Kalshi politics (Δ = 0.53, 95% CI [0.29, 0.75]) but the effect does not replicate on Polymarket (Δ = 0.11, [-0.15, 0.39]), indicating platform-specific microstructure.
+- Election markets are where the regulatory question gets sharpest. Park: Kalshi's appeals court win establishing that "gaming" does not cover financial contracts on election outcomes is the doctrinal pillar holding up the entire industry.
+
+## Notable Quotes
+
+> "Only 53% of resolved US elections appeared on both platforms... bid-ask spreads exceed 20% on most contracts."
+> — *paraphrased from Andy Hall & Elliot Paschal, *Building the Truth Machine**
+
+> "Four coordinated accounts controlled 23% of Polymarket's open interest, 41% of volume appeared to be wash trading."
+> — *paraphrased from Luca Prosperi, *Prediction Markets (II): Spoiling the Election Love Story**
+
+## Where It Matters
+
+Election markets are the category that put PMs on the cultural map and the one regulators have moved hardest against. They're also the canary for whether the broader info-finance thesis holds: if elections can't be made liquid and manipulation-resistant, the case for "markets as truth machines" deflates. For Dekant: continuous markets fit electoral questions (vote share by candidate, turnout, margin) naturally and could improve calibration where binary state-by-state contracts compress to 50%.
+
+## Related Concepts
+
+- Forecasting accuracy / calibration · election markets are the canonical test case
+- Market manipulation / wash trading · heavily documented in 2024 PMs
+- Oracle design · Polymarket's UMA + Fox News oracle controversy
+- Regulatory classification · the doctrinal hinge for political event contracts
+- Federal preemption · state AG challenges target election markets specifically
+
+## Sources
+
+- [Benchmarks Are Key to Scale Prediction Markets Institutionally. Question: Which Ones Can Deliver?](https://x.com/lzminsky/status/2051741765682508073) — Lauris · May 5 2026 ·
+- [Are Prediction Markets Decaying or Evolving?](https://x.com/_drNeyo/article/2029205512391172364) — Eniola · Mar 4 2026 ·
+- [Decomposing Crowd Wisdom: Domain-Specific Calibration Dynamics in Prediction Markets](https://arxiv.org/abs/2602.19520) — Nam Anh Le · Feb 23 2026 ·
+- [The Truth Machine Era Is Here](https://x.com/dgt10011/status/2024228182178595247) — Jeff Park · Feb 19 2026 ·
+- [The Perils of Election Prediction Markets](https://goodauthority.org/news/the-perils-of-election-prediction-markets/) — John Sides · Dec 18 2025 ·
+- [Prediction Markets (II): Spoiling the Election Love Story](https://dirtroads.substack.com/p/64-prediction-markets-ii-spoiling) — Luca Prosperi · Nov 2 2024 ·
+- [My Polymarket Conspiracy Theory](https://medium.com/quantum-economics/my-polymarket-conspiracy-theory) — Lou Kerner · Oct 20 2024 ·
+- [Unveiling Polymarket: The Positioning, Expansion, and Shadows of Crypto Prediction Markets](https://research.mintventures.fund/2024/10/09/Unveiling-Polymarket-The-Positioning-Expansion-and-Shadows-of-Crypto-Prediction-Markets/) — Lydia Wu · Oct 9 2024 ·
+- [The Art of Forecasting](https://paragraph.com/@filarm/the-art-of-forecasting) — fil · Sep 30 2024 ·
+- [Polymarket: An Election-Driven Success Story](https://newsletter.asxn.xyz/p/polymarket-an-election-driven-success) — ASXN · Aug 10 2024 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/endogeneity.md b/.qoder/plans/pm-plan/theory_concepts/endogeneity.md
new file mode 100644
index 0000000000..e4364a10e3
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/endogeneity.md
@@ -0,0 +1,50 @@
+# Endogeneity
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Endogeneity](https://www.pmatlas.xyz/concepts/endogeneity)
+
+---
+
+## Definition
+
+When the existence or visibility of a prediction market influences the very outcome it aims to forecast, undermining its reliability as a neutral signal. Particularly relevant in political and social markets where public price movements can shift the behavior of voters, candidates, or media. The "thermometer that becomes a thermostat" problem.
+
+## Key Insights
+
+- Single-source concept page citing one Bhattacharjee article. The framing is about *which PM categories are reliable enough to use for nowcasting* · and endogeneity is the core reason political/social markets are *lower-tier* than financialized economic indicators. The Bhattacharjee tiered framework explicitly ranks: 1. **Financialized economic indicators** (CPI, employment, Fed action) · *highest reliability*, because the underlying outcome is set by independent institutional processes and shouldn't react to PM prices. 2. **Verifiable hard outcomes** (sports, scheduled events) · moderate, low endogeneity risk. 3. **Speculative prop bets and political/social predictions** · *lowest tier*, because PM prices can leak into candidate behavior, media coverage, donor allocation, and voter perception.
+- Cited evidence: Federal Reserve paper validating Kalshi data quality (specifically on financial-econ contracts, where endogeneity is minimal). The implication is that endogeneity is *category-specific*: a CPI market is a clean thermometer because the BLS doesn't read PMs; a presidential election market is a thermostat because campaigns, media, and donors do.
+- Three practical use cases the article proposes, ranked by endogeneity risk: triangulating against polls (low risk · uses PMs as one of many signals), nowcasting delayed econ data (very low risk · the underlying process is exogenous), hedging event risk (depends on the event · exogenous events like CPI are fine, endogenous events like elections are dangerous).
+- Bhattacharjee (FULL_READ) defines endogeneity directly: "Endogeneity is when variables that are supposed to be independent actually influence each other. It's the bane of econometricians (stats nerds who focus on economics) because it starts to break down the assumptions of our models. If the price of a prediction market (or even its existence) impacts the outcome of the prediction, it starts to lead to funny results."
+- Bhattacharjee's canonical example: Brian Armstrong, CEO of Coinbase, was on an earnings call. He became aware Polymarket was running a contract on whether he would mention specific phrases. "He modified the words he was going to use. Here, the market was supposed to be predicting what he would say. Instead, his knowledge of the market's bets changed what he said." This is endogeneity at the contract level.
+- Bhattacharjee on the joint failure mode: "Ultimately both insider trading and endogeneity risks prevent sophisticated participants from using these markets, which in turn reduces their usefulness. These risks are both clearest in category 3 and 4 markets [sports, prop bets, mention markets] and frankly that's why I think they will never mature into efficient, useful sources of information like categories 1 and 2 [econ data, political/macro outcomes]."
+
+## Notable Quotes
+
+> "Public price movements can shift behavior."
+> — *onprediction.xyz definition*
+
+> "Particularly relevant in political and social markets where public price movements can shift behavior."
+> — *onprediction.xyz definition*
+
+> "Financialized economic indicators rank highest, speculative prop bets lowest."
+> — *Isar Bhattacharjee*
+
+## Where It Matters
+
+Endogeneity is the most under-discussed structural risk in the PM bull case. Every "PMs as truth machines" narrative implicitly assumes the market price doesn't *change* the outcome it predicts · but for elections, social movements, AI safety announcements, M&A rumors, and product launches, that assumption is wrong. The Kyla Scanlon NYT op-ed (responded to by Adhi Rajaprabhakaran in the price-discovery cluster) argued exactly this for PMs broadly; Rajaprabhakaran's pushback was that PMs lack the *causal mechanism* stocks have, which makes endogeneity smaller · but not zero. For builders, the regulatory and ethical implication is: don't list contracts where the PM is itself a campaign tool (e.g., "Will X be assassinated by Y date?" creates a literal hit-market). For category strategy, lean into financialized-econ markets (low endogeneity, Fed-cited credibility) and away from political markets when accuracy claims matter.
+
+## Related Concepts
+
+- **Nowcasting** · endogeneity determines which categories can be nowcast reliably
+- **Reflexivity** · the broader concept that PM endogeneity is one form of
+- **Information aggregation** · endogeneity is what corrupts aggregation
+- **Wisdom of crowds** · endogeneity is one failure mode that breaks the WoC claim
+- **Market manipulation** · deliberate exploitation of endogeneity
+- **Insider trading** · different but related failure mode of price signal
+- **Decision markets** · by design *use* endogeneity (the market should influence the decision)
+- **Election markets** · the canonical endogenous category
+
+## Sources
+
+- [How to Use Prediction Markets as a High Quality Info Source](https://uncover.substack.com/p/how-to-use-prediction-markets-as) — Isar Bhattacharjee · Mar 30, 2026 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/event-contracts.md b/.qoder/plans/pm-plan/theory_concepts/event-contracts.md
new file mode 100644
index 0000000000..e3b71cc3d6
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/event-contracts.md
@@ -0,0 +1,75 @@
+# Event contracts
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — Event contracts](https://www.pmatlas.xyz/concepts/event-contracts)
+
+---
+
+## Definition
+
+**Quick definition.** Standardized binary contracts on specific real-world events · the core trading unit of prediction markets. Their legal status (swap vs gambling) is the defining regulatory question of the era.
+
+## Key Insights
+
+- Event contracts are the regulatory and commercial unit of account. Hariharan (Dopamine) argues the industry's fate rests on whether sports event contracts count as "swaps" under the Commodity Exchange Act · if yes, federal preemption applies in all 50 states; if no, the path collapses into state-by-state gambling licenses.
+- Third Circuit became the first appeals court to side with Kalshi, holding sports event contracts are swaps. Courts elsewhere are split (Kalshi won NJ appeals, TN district, N. Cal district; states won OH district, MA state, NV state). 19 federal lawsuits pending. CFTC is suing Arizona, Illinois, Connecticut on Kalshi's behalf. Supreme Court resolution likely within two years.
+- Rob Schwartz (Morgan Lewis) argues "industry consensus is what matters" under the CEA: DCMs, FCMs, brokers, and DCOs all treat sports event contracts as swaps, so they are swaps and CFTC jurisdiction is exclusive. He counters the court characterization that the products "are sports wagers and everyone who sees them knows it" · the derivatives industry's own terminology disproves it.
+- HIP-4 reframes event contracts. Dean Eigenmann positions them as parametric cover for crypto-native exposures (Kelp DAO rsETH bridge exploit, $292M, as motivating case): when outcome contracts share margin with underlying exposure on the same execution layer, the addressable market is two orders of magnitude larger than current DeFi insurance.
+- Pink Brains: HIP-4 is the on-chain options layer, not "another PM." Unified margin engine + fee structure + HYPE token value capture differentiates it. Design space includes CDS, parametric insurance, futarchy.
+- Astaria: prediction market perps are structurally different from crypto perps because event contracts lack a tradeable underlying. Most "perps on events" implementations devolve into liquidation arcades. The winning path is institutional hedging infrastructure for continuous event-risk management.
+- DWF Ventures argues PMs are evolving into a financial derivatives asset class. Structural barriers: jump risk, binary valuation, regulatory uncertainty. Emerging solutions: epoch-based fee models, perpetual futures on outcomes, tokenized positions on Solana.
+- 0xturbanurban introduces the "vega wedge" · the structural overcharge binary hedgers pay when they replicate via vanilla options. The argument: PMs can undercut this tax in deep-volume categories but still lack a Black-Scholes-equivalent shared pricing language and risk infrastructure. (Specific bp figures cited in the original tweet are not in our fetched digest body.)
+- ARK Invest sizes the medium-term opportunity at up to ~$5 trillion, arguing prediction markets' "real potential will come from their instantiation as the future of financial infrastructure" · not sports betting disruption. They explicitly argue PMs will *not* disrupt sports betting but will become a foundational layer of financial services.
+- Levine's "Truth Machines Go to War" (Bloomberg, paywalled in our fetch) argues Kalshi's "mentions markets" · paying off if a specific word is said · show how reality rarely provides clean binary boundaries, forcing platforms into interpretive disputes.
+- Stefan von Imhof debunks the canonical misconception that market prices equal probabilities. Risk-free rates, opportunity costs, and bid-ask spreads create systematic price deviations from true beliefs · material when calibrating PM data for serious use.
+- The Measles Market on Kalshi (OddChain): real-world ethical limits on the binary-everything frontier. Turning a public health crisis into a tradeable instrument is the cost of a category that converts any future into a contract.
+- Lauris argues (via his variance-risk-premium framing) that high-VRP categories · Bitcoin and elections in particular · already cross the displacement threshold where event contracts beat options replication, with FOMC markets having compressed their cost gap dramatically since 2024 (specific figures cited in the original tweet are not in our fetched digest body).
+- Michael Li's four-dimensional classification (information structure, manipulation economics, social utility, repugnance) makes the case that lumping every event contract under one regulatory regime is either too restrictive (suppressing election/macro forecasting) or too permissive (allowing perverse contracts with no countervailing benefit).
+- Park's history: Iowa Electronic Markets' 1988 no-action letter -> Intrade's 2012 collapse -> Kalshi's court win establishing "gaming" doesn't cover financial contracts on uncertain outcomes -> Bitwise PredictionShares ETF launch as the mainstream tipping point.
+- Cassar (Oxford): EU regulators face a parallel classification crisis · gambling vs MiFID II derivative vs something else. Proposes a Malta-style "Prediction Test" to systematically categorize through exclusion.
+
+## Notable Quotes
+
+> "All of these entities refer to and treat the contracts as 'swaps,' subject to regulation under the exclusive jurisdiction of the Commodity Futures Trading Commission … under the Commodity Exchange Act, industry consensus is what matters."
+> — *Rob Schwartz, *Federal Preemption in Sports Prediction Market Litigation**
+
+> "[Sports event contracts] are sports wagers and everyone who sees them knows it."
+> — *court opinion quoted in Rob Schwartz, *Federal Preemption in Sports Prediction Market Litigation**
+
+> "Prediction markets … real potential will come from their instantiation as the future of financial infrastructure, presenting a potential medium-term opportunity up to ~$5 trillion."
+> — *ARK Invest, *Prediction Markets: The Potential Multi-Trillion Dollar Asset Class**
+
+## Where It Matters
+
+Event contracts are the fundamental unit; everything else (oracles, liquidity, distribution) is downstream of whether you can offer them legally and how cleanly they map to user intent. For Dekant: continuous-curve markets are themselves a kind of unbundled event contract · instead of one binary, a partition-of-unity over the outcome space. The regulatory tailwind for swap-classified contracts under the CEA could be relevant if a continuous market is structured as a basket of "commonly known to the trade" swap legs.
+
+## Related Concepts
+
+- Regulatory classification · how the contract is legally categorized
+- Federal preemption · whether CFTC's swap jurisdiction overrides state gaming law
+- Binary contracts · the dominant subtype
+- Distribution moat · multi-asset wrappers cross-sell event contracts
+- Market structure · event contracts as the lego brick across rails/wrappers
+
+## Sources
+
+- [Outcome Markets as a Cover Venue: HIP-4 and Its Traditional Comparables](https://x.com/DeanEigenmann/status/2052734335971713347) — Dean Eigenmann · May 8 2026 ·
+- [Benchmarks Are Key to Scale Prediction Markets Institutionally. Question: Which Ones Can Deliver?](https://x.com/lzminsky/status/2051741765682508073) — Lauris · May 5 2026 ·
+- [Prediction Market Perps - the 1% Winning Product](https://x.com/AstariaTrade/status/2051363114881568852) — Astaria · May 4 2026 ·
+- [HIP-4 Is Not a Prediction Market - It's the Options Layer: A Full Guide](https://x.com/PinkBrains_io/status/2051312783074226606) — Pink Brains · May 4 2026 ·
+- [A Second Identity: Prediction Markets as Financial Derivatives](https://x.com/i/article/2049414664946323456) — DWF Ventures · Apr 30 2026 ·
+- [Why Prediction Markets Are Hard to Regulate](https://michaellwy.substack.com/p/my-comment-to-cftcs-prediction-markets) — Michael Li · Apr 30 2026 ·
+- [You Don't Hate Prediction Markets. You Hate Capitalism.](https://x.com/noahlitvin/status/2048527516587966660) — Noah Litvin · Apr 27 2026 ·
+- [Prediction Markets: The Potential Multi-Trillion Dollar Asset Class Hiding In Plain Sight](https://www.ark-invest.com/articles/analyst-research/prediction-markets-potential-multi-trillion-dollar-asset-class) — Nicholas Grous, Varshika Prasanna, Raye Hadi (ARK) · Apr 22 2026 ·
+- [The Bane Of Binaries: What Prediction Markets Are Missing](https://x.com/0xturbanurban/status/2044433520806830101) — 0xturbanurban · Apr 15 2026 ·
+- [States vs. Prediction Markets: The Fight Over the Meaning of 'Swap'](https://www.dopaminemarkets.com/p/prediction-markets-vs-states-the) — Shreyas Hariharan · Apr 6 2026 ·
+- [Truth Machines Go to War](https://www.bloomberg.com/opinion/newsletters/2026-04-06/truth-machines-go-to-war) — Matt Levine · Apr 6 2026 ·
+- [The State of Prediction Markets](https://stateofpredictionmarkets.blocmates.com/) — blocmates · Apr 1 2026 ·
+- [Regulating Prediction Markets in Europe Requires a 'Prediction Test'](https://blogs.law.ox.ac.uk/oblb/blog-post/2026/03/regulating-prediction-markets-europe-requires-prediction-test) — Terence Cassar · Mar 31 2026
+- [Prediction Markets and Insider Trading Law](https://www.congress.gov/crs-product/LSB11406) — Jay B. Sykes (CRS) · Mar 18 2026 ·
+- [Federal Preemption in Sports Prediction Market Litigation: This Shouldn't Be a Jump Ball](https://www.morganlewis.com/-/media/files/publication/outside-publication/article/2026/federal-preemption-in-sports-prediction-market-litigation-this-shouldnt-be-a-jump-ball-futures-and-derivatives-law-report.pdf) — Rob Schwartz · Mar 1 2026 ·
+- [Seeing Like a Market: Event Contracts and Market Topology](https://www.slampaper.xyz/) — Lauris Marinson · Mar 1 2026 ·
+- [The Truth Machine Era Is Here](https://x.com/dgt10011/status/2024228182178595247) — Jeff Park · Feb 19 2026 ·
+- [The Measles Market on Kalshi Is One of the Dumbest and Most Tragic Markets We've Seen This Year](https://www.oddchain.com/p/the-measles-market-on-kalshi-for-2025-is-one-of-the-dumbest-and-most-tragic-markets-we-ve-seen-this) — OddChain · Dec 30 2025 ·
+- [Prediction Markets Explained](https://alternativeassets.substack.com/p/prediction-markets-explained) — Stefan von Imhof · Feb 4 2024 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/execution-quality.md b/.qoder/plans/pm-plan/theory_concepts/execution-quality.md
new file mode 100644
index 0000000000..6c158adcf5
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/execution-quality.md
@@ -0,0 +1,65 @@
+# Execution Quality
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Execution Quality](https://www.pmatlas.xyz/concepts/execution-quality)
+
+---
+
+## Definition
+
+**Quick definition.** How favorably a trader transacts relative to the prevailing market price. The defining empirical finding of 2026 prediction-market research is that **execution quality outweighs directional forecasting skill** as a determinant of returns.
+
+## Key Insights
+
+- Della Vedova (222M Polymarket trades): forecasting accuracy does *not* predict profitability. Traders who pick the right side still lose because they arrive late at unfavorable prices; **automated traders profit by paying 2.52 cents less per contract** despite near-random directional skill.
+- Kunal Doshi: 19 algo addresses on Polymarket fast crypto extract consistent profit through paired trading and maker execution; 69% of retail lose. Execution edge dominates the P&L.
+- Isaac Rose-Berman: a fair coin-flip becomes a **3.4% expected loss after fees** on Kalshi · execution costs convert otherwise neutral expected value into a structural loss for retail. The mechanism: retail almost always crosses the maker spread as the taker; Kalshi takes 0.07 × C × P × (1-P) which peaks at 50/50 from both sides. "Maker dominance is structural, not stylistic · only the people with the fastest systems and best models can profitably maintain quotes."
+- Cryptonomads (10,000 automated trades): most PM terminals are "execution mirages." Only institutional API rails and trader-native terminals survive · execution infrastructure becomes the moat.
+- Ranger Global: **latency <100 ms captures 73% of arbitrage profits**. The execution-quality battle is at the millisecond level. PM traders systematically underreact to spot moves by 10–20%, leaving a renewable execution edge for fast actors.
+- Daedalus Research: academic conversation must move beyond forecast accuracy to **execution quality, market structure, and trader welfare**.
+- Kunal Doshi on Kalshi sports: in-game depth declines **76%** vs. pre-game; taker fees up to 3.5% at midpoint · institutional execution is currently bottlenecked by these microstructure properties.
+- Pantera/Sui Kyle-λ measurement: **Polymarket's median liquidity depth is 10⁶·⁹⁶ vs. Kalshi's 10⁶·⁴²** · 3-4× more notional volume needed to move Polymarket prices over a 60-second horizon. Execution-quality tradeoff is structural: Kalshi prices respond faster to information (median 7s lead in 80% of large moves) but Polymarket fills with less slippage.
+- Pantera/Sui design hypothesis: Polymarket's **~3-second delay on marketable orders in live sports** reduces LP adverse selection, allowing tighter quotes · this is an explicit execution-quality intervention at the matching-engine level.
+- Becker on execution-vs-information: at 1-cent contracts takers win only 0.43% of the time against an implied 1% probability · a **−57% mispricing** entirely captured by makers. Pure execution edge from controlling who's the LP.
+- sybilpm: in batch auctions "the money that used to go to 'I was 50ms faster' now goes to 'I was right about the price'" · the design space for fixing PM execution quality is microstructure-level, not infrastructure-level.
+
+## Notable Quotes
+
+> "Forecasting accuracy does not predict profitability."
+> — *Della Vedova*
+
+> "Automated traders profit by paying 2.52 cents less per contract."
+> — *Della Vedova*
+
+> "Most prediction market terminals fail through execution mirages."
+> — *cryptonomads*
+
+> "Takers exhibit negative excess returns at 80 of 99 price levels. Makers exhibit positive excess returns at the same 80 levels."
+> — *Becker*
+
+> "Polymarket requires roughly 3–4× more notional volume to generate a comparable price move over a 60-second horizon."
+> — *Pantera Research Lab*
+
+## Where It Matters
+
+This is the single most important finding for builders: **the product that captures the asset class is the one that delivers superior execution, not superior research**. Every existing terminal that displays charts and odds without first solving order routing, fill quality, slippage, and latency is fighting on the wrong axis. The corollary for platforms is that maker-rebate fee structures and matching-engine design (latency, batched vs. continuous) determine which traders become profitable · which determines who comes back.
+
+## Related Concepts
+
+- **Market making** · execution quality from the LP side.
+- **Adverse selection / toxic flow** · what execution edge guards against.
+- **Retail flow** · the cohort that systematically gets worse execution.
+- **Cross-platform arbitrage** · execution edge across venues.
+- **Bid-ask spread** · the unit of execution quality.
+- **Temporal arbitrage** · execution edge over time.
+
+## Sources
+
+- [A Game of Volatility](https://x.com/Kunallegendd/status/2054191944944038084) — Kunal Doshi · May 12, 2026
+- [Kalshi's Favorite Lie](https://howgamblingworks.substack.com/p/kalshis-favorite-lie) — Isaac Rose-Berman · Apr 29, 2026
+- [Why Every Prediction-Market Terminal Will Fail (and the Two That Won't)](https://x.com/0xCryptoNomads/status/2048664550225170605) — cryptonomads · Apr 27, 2026
+- [Anatomy Of A New Asset Class I: How Markets Turn Capital Into Probability](https://x.com/Ranger_Global/status/2046547375649427572) — Ranger Global · Apr 21, 2026
+- [What Happens When Institutional Liquidity Enters Prediction Markets](https://x.com/DaedalusRsch/status/2046219948452979151) — Daedalus Research · Apr 20, 2026
+- [From Betting to Trading: How Kalshi Is Reshaping Sports Markets](https://x.com/Kunallegendd/status/2044780671839952949) — Kunal Doshi · Apr 16, 2026
+- [Who Profits from Prediction Markets? Execution, Not Information](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6191618) — Joshua Della Vedova · Feb 7, 2026
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/federal-preemption.md b/.qoder/plans/pm-plan/theory_concepts/federal-preemption.md
new file mode 100644
index 0000000000..864a1228c2
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/federal-preemption.md
@@ -0,0 +1,60 @@
+# Federal preemption
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — Federal preemption](https://www.pmatlas.xyz/concepts/federal-preemption)
+
+---
+
+## Definition
+
+**Quick definition.** The legal doctrine under which federal law displaces state regulation. In PMs: whether CFTC's exclusive jurisdiction over swaps preempts state gaming law from regulating exchange-traded event contracts.
+
+## Key Insights
+
+- The whole industry hinges on whether sports event contracts are "swaps" under section 1a of the Commodity Exchange Act. If yes, CFTC preempts state law and platforms operate in all 50 states. If no, platforms need 50-state gambling licenses (commercially fatal). · Hariharan.
+- The Third Circuit became the first appeals court to rule that sports event contracts are swaps, siding with Kalshi. But courts are split: Kalshi won in NJ (appeals), TN and N. Cal (district); states won in OH (district), MA and NV (state). The CFTC is suing Arizona, Illinois, and Connecticut on Kalshi's behalf. Washington state sued Kalshi; Robinhood (which uses Kalshi to power its PMs) counter-sued Washington.
+- 19 federal lawsuits pending. Supreme Court resolution likely within two years.
+- The Dodd-Frank swap definition (section 1a) is: "Any agreement, contract, or transaction ... that provides for any purchase, sale, payment, or delivery (other than a dividend on an equity security) that is dependent on the occurrence, nonoccurrence, or the extent of the occurrence of an event or contingency associated with a potential financial, economic, or commercial consequence." The fight is over two phrases inside this definition.
+- Rob Schwartz (Morgan Lewis): under the CEA, "industry consensus is what matters." DCMs, FCMs, brokers, and DCOs all treat sports event contracts as swaps, so they qualify as swaps subject to CFTC's exclusive jurisdiction. He rebuts the trial-court line that the contracts "are sports wagers and everyone who sees them knows it" · the derivatives industry's own terminology disproves the "everyone knows" claim.
+- Park's timeline: the Feb 2026 jurisdictional standoff between CFTC Chairman Mike Selig and Utah Governor Cox is the canonical preemption moment. Park argues federal preemption under the CEA will hold against state AGs, citing Iowa Electronic Markets' 1988 no-action letter as the doctrinal anchor and Bitwise's PredictionShares ETF launch as the practical conclusion.
+- Jay Sykes (CRS): even if preemption holds, an insider-trading enforcement gap remains. SEC Rule 10b-5 and CFTC Rule 180.1 both require a breach of duty · many PM insiders (e.g., a candidate betting on his own race) don't owe one. Four pending congressional bills would close this gap.
+- The DFS parallel (Gouker): daily fantasy sports went through an identical state-by-state legal battle 2013-2015 before settling into a mostly-legal patchwork. The PM script may already be written, with federal-state compromise as the equilibrium.
+- Sykes also covers the CFTC's February 2026 advisory on two Kalshi enforcement actions · signaling that even with preemption, the CFTC intends to police certain conduct (insider trading) within the federal regime.
+- States retain authority over off-exchange sports betting (the actual sportsbooks like DraftKings/FanDuel). Preemption only covers exchange-traded event contracts. This split is crucial · it means Kalshi/Polymarket get federal cover while traditional sportsbooks stay state-regulated.
+- The DraftKings/FanDuel reaction (referenced in Eniola): the entry of Robinhood, DraftKings, Crypto.com, FanDuel into PMs is partly motivated by the preemption arbitrage · if event contracts are federally cleared, they can offer products their state licenses don't otherwise allow.
+- Tribal-gaming compacts add a third layer: some state opposition is driven by tribal exclusivity arrangements that PMs would erode.
+
+## Notable Quotes
+
+> "The fate of the industry rests on a binary question of its own: do sports event contracts like 'who will win the Super Bowl' count as 'swaps'?"
+> — *Shreyas Hariharan, *States vs. Prediction Markets**
+
+> "Good luck building a venture-backed company if you need to get a gambling license in fifty states."
+> — *Shreyas Hariharan, *States vs. Prediction Markets**
+
+> "All of these entities refer to and treat the contracts as 'swaps' … under the Commodity Exchange Act, industry consensus is what matters."
+> — *Rob Schwartz, *Federal Preemption in Sports Prediction Market Litigation**
+
+> "[Sports event contracts] are sports wagers and everyone who sees them knows it."
+> — *trial-court characterization quoted (and rebutted) in Rob Schwartz, *Federal Preemption in Sports Prediction Market Litigation**
+
+## Where It Matters
+
+Federal preemption is the binary the industry needs answered to size its TAM. Without it, every venture-backed US PM is on an existential timer. For Dekant: building on Solana devnet (not yet mainnet, not yet US-regulated) keeps optionality · if preemption holds and Solana-based event contracts can be classified as swaps via a US-regulated wrapper (Kalshi-like clearing), the moat could open quickly.
+
+## Related Concepts
+
+- Event contracts · the unit under jurisdictional dispute
+- Regulatory classification · the categorical decision preceding preemption
+- Regulatory arbitrage · preemption closes intra-US arbitrage
+- Election markets · the specific category that triggered state AG challenges
+- Insider trading · the conduct the federal regime still has to police
+- Market surveillance · the CFTC's tool inside the preempted regime
+
+## Sources
+
+- [States vs. Prediction Markets: The Fight Over the Meaning of 'Swap'](https://www.dopaminemarkets.com/p/prediction-markets-vs-states-the) — Shreyas Hariharan · Apr 6 2026 ·
+- [Prediction Markets and Insider Trading Law](https://www.congress.gov/crs-product/LSB11406) — Jay B. Sykes (CRS) · Mar 18 2026 ·
+- [Federal Preemption in Sports Prediction Market Litigation: This Shouldn't Be a Jump Ball](https://www.morganlewis.com/-/media/files/publication/outside-publication/article/2026/federal-preemption-in-sports-prediction-market-litigation-this-shouldnt-be-a-jump-ball-futures-and-derivatives-law-report.pdf) — Rob Schwartz · Mar 1 2026 ·
+- [The Truth Machine Era Is Here](https://x.com/dgt10011/status/2024228182178595247) — Jeff Park · Feb 19 2026 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/forecasting-accuracy.md b/.qoder/plans/pm-plan/theory_concepts/forecasting-accuracy.md
new file mode 100644
index 0000000000..05beff09cc
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/forecasting-accuracy.md
@@ -0,0 +1,86 @@
+# Forecasting Accuracy
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Forecasting Accuracy](https://www.pmatlas.xyz/concepts/forecasting-accuracy)
+
+---
+
+## Definition
+
+Measuring how well predicted probabilities match actual outcome frequencies over many events. Operationalized in PMs through Brier scores, calibration curves, and head-to-head comparisons against ARIMA, FluSight ensembles, polls, expert panels, and LLMs.
+
+## Key Insights
+
+- Prediction markets *underperform* simple baselines on infectious disease forecasting: Polymarket was "dominated by the FluSight ensemble" for flu, and "outperformed by simple statistical baselines" for measles. Two diagnosed failure modes: probability mass placed on impossible outcomes (e.g., decreasing values in cumulative forecasts) and low trading volume. The best ensemble combination "puts zero weight on the markets" (Dudley & Magdaleno, May 2026).
+- "Polymarket is not a truth machine": headline Brier score of 0.047 masks category-specific failures · sports markets score 0.325 (worse than a coin flip). 99% of volume concentrates in the final hours before resolution. PMs only "work" on ~2% of listed contracts (binary, high-profile, short-term, millions at stake). CNN/WSJ broadcasting illiquid market odds as authoritative signal launders whale trades through credible newsrooms (Vaidik Mandloi).
+- ~13,500 Polymarket contracts analyzed: >80% of volume goes to sports/crypto/elections. Accuracy on "useful" markets hasn't improved since early 2025. AI chatbots may supersede PMs as the primary forecasting interface, leaving markets to serve an epistemic role as common-knowledge infrastructure (Dan Schwarz, Asterisk).
+- ForecastBench: best LLM (GPT-4.5) achieves Brier 0.101 vs superforecasters' 0.081, with LLMs improving ~0.016 Brier/year, projecting parity by late 2026. Some models *game* the benchmark by copying PM prices rather than reasoning (Forecasting Research Institute).
+- LLM-as-updater framing: distinguish cold prediction from updating; AI tools deployed alongside humans, not replacing them (OddChain).
+- Tetlock's Superforecasting framework operationalized at scale by Polymarket. Foxes vs hedgehogs, Good Judgment Project, Brier scores, calibration · the book "predicted Polymarket" (Mikita Ahnianchykau).
+- 2024 election platform-level accuracy: PredictIt 93%, Kalshi 78%, Polymarket 67% (Clinton & Huang via Sides). Cross-platform divergence near Election Day was significant; suggests "more liquidity" doesn't mean "better signal."
+- Minimum viable liquidity finding: trading volume explains <1% of variance in forecast accuracy on 149 Kalshi CPI markets (2021–26). MVL = Cost of Expertise / Price Gap (Adhi Rajaprabhakaran).
+- Political prediction market quality (6-month empirical analysis): only 1.3% of political markets are liquid enough to be manipulation-resistant; bid-ask spreads exceed 20% on most contracts; only 53% of resolved US elections appeared on both Kalshi and Polymarket. Four-part blueprint: stock relevant questions, cross-subsidize political liquidity from sports profits, deploy AI MMs where human interest insufficient, standardize contract definitions (Hall & Paschal).
+- Five-point diagnostic for distinguishing gambling from systematic trading. Three trader archetypes by profitability. Polymarket's CLOB creates renewable structural arbitrage by design. Kelly position sizing, adverse-selection measurement via fill quality, probability term structure (Roan).
+- AI underperforms humans because edge is *embodied/local* · five of six AI models in Prediction Arena underwater after three weeks (Mehmet Avci).
+- "Nielsen moment": coordination value > accuracy. Polymarket/Golden Globes, Polymarket/WSJ, Kalshi/CNN · once PMs become shared reference points, displacement is hard regardless of methodological superiority (Avci).
+- "Make Precision Pay" · binary markets flatten beliefs into coin flips and pay the same whether you were barely right or sharply right. Distribution-native markets reward precision (Tide).
+- "What if we're capturing the wrong signal?" · binary markets flatten complex beliefs; Vanderbilt study: PredictIt 93% accuracy vs 67% on high-volume platforms · more liquidity ≠ better signal (Jo).
+- Conditional PMs fail to translate probability assessments into meaningful policy guidance despite being their central theoretical appeal · "prediction markets are mediocre" (LessWrong).
+- 2024 election: Polymarket priced in Biden's withdrawal probability while polls measured only head-to-head support (fil) · illustrates the speed advantage on multi-branch questions.
+- PMs aren't unpopular due to regulation but due to demand-side issues · they need savers, gamblers, or sharps; PMs attract none (zero-sum so no savers, long resolution so no gamblers, too small so no sharps) (Whitaker & Mazlish).
+- Combinatorial PMs (Powell, Hanson, Laskey, Twardy 2013) · extending to conditional events and Boolean combos improves accuracy by eliciting joint distributions rather than only marginals.
+- Mandloi specifics from full read: Polymarket BTC-to-$100K market had Brier 0.4909 (worse than coin flip) despite *correctly resolving Yes* · was confidently wrong for months. Kamala Harris Democratic nomination market: Brier 0.9098 · "so bad that it is hard to overstate." "A single correct call tells you almost nothing." Brier.fyi analyzed 84,000+ questions; Polymarket science/economics A, politics B+, culture/tech worse, sports D (0.325). 80% of all volume in elections + sports. Median Polymarket question resolves in 4 days; average 19 days.
+- Schwarz/Asterisk specifics: 5 categories of value (risk monitoring, news interpretation, policy outcomes, accountability, novel info). Only risk monitoring has working supply+demand balance (2,821 markets, $3.8B volume, median $82K). 90% of Kalshi volume is sports. 85% of "news interpretation" volume is just US Fed rate markets. 66% of accountability market volume is Epstein speculation. 196 tariff-policy markets ($144M) are the rare bright spot for genuinely novel info. Markets >90 days do show volume → accuracy correlation; markets <90 days don't.
+- Schwarz on AI substitution: "Try searching Polymarket for probabilities, versus asking Claude about it. I wager you'll prefer Claude's take, even if it is less accurate." Claims AI chatbots will become the primary distribution layer for forecasts, with PMs serving as common-knowledge infrastructure.
+- ForecastBench full-read additions: GPT-4 (Mar 2023) Brier 0.131 → GPT-4.5 (Feb 2025) Brier 0.101 (one-generation gap to superforecasters). The benchmark uses a "difficulty-adjusted Brier score" to compare across non-overlapping question sets. Most LLMs *copy market prices when given them* · GPT-4.5 outputs correlated 0.994 with market forecasts, exact matches on 26/122 questions. Baseline (no market access) leaderboard shows true LLM improvement at 0.036 Brier/year · faster than Tournament rate.
+- Hall/Paschal "Building the Truth Machine" (FULL_READ): 98.7% of political markets are "ghost towns" with wide spreads and almost no counterparty. But "less liquid markets are not necessarily less accurate. Sometimes markets stay illiquid precisely because they're already accurate, and there's no incentive for new money to enter."
+- John Sides / Vanderbilt (Clinton & Huang) full-read: 2,500+ political prediction markets across IEM, Kalshi, PredictIt and Polymarket, with $2B+ transacted over the final 5 weeks of 2024. PredictIt 93% > Kalshi 78% > Polymarket 67% on accuracy. "Prices for identical contracts diverged across exchanges, daily price changes were weakly correlated or negatively autocorrelated, and arbitrage opportunities peaked in the final two weeks before Election Day." The conclusion challenges the assumption that PMs "necessarily efficiently and accurately aggregate information about political outcomes."
+- LessWrong "Prediction Markets Are Mediocre" (FULL_READ): the conditional-market case for policy guidance fails because the market gives a number that comes too late and too vaguely to change decisions. 56% chance of tariffs in Trump's first year was technically correct, but useless on election day. Adding more liquidity helps marginally; adding a second-order market about the first-order market doesn't help at all because "both of them are trying to capture the same core fact about reality."
+
+## Notable Quotes
+
+> "Polymarket's headline Brier score of 0.047 masks category-specific failures like sports markets scoring 0.325 (worse than a coin flip), and 99% of volume concentrates in the final hours before resolution."
+> — *Vaidik Mandloi*
+
+> "More liquidity doesn't mean better signal."
+> — *Jo, "What If We're Capturing the Wrong Signal?"*
+
+> "Trading volume explains less than 1% of variance in forecast accuracy."
+> — *Adhi Rajaprabhakaran*
+
+## Where It Matters
+
+Forecasting accuracy is the *advertised* value prop of every PM platform, but the 2026 empirical research catalog tells a much more conditional story. PMs are accurate on a narrow band of contracts: binary, high-profile, short-term, with millions in volume. Outside that band, they often lose to ARIMA, FluSight, or expert panels. For builders this implies: don't sell "PMs are smarter than experts" · sell "PMs are the shared coordination layer for a narrow set of high-stakes binary questions, plus a UI for the broader info-finance vision." For Dekant: distribution markets attack the binary flattening problem head-on; if Brier on binary sports is 0.325, a continuous outcome representation paid by distance-from-truth could plausibly tighten that.
+
+## Related Concepts
+
+- **Calibration** · the diagnostic
+- **Brier score** · the metric
+- **Wisdom of crowds** · the folk theory; partly debunked by 3%-of-accounts finding
+- **Superforecasting** · Tetlock's framework that the markets operationalize
+- **Minimum viable liquidity** · the counterintuitive limit
+- **Distribution markets** · proposed higher-resolution form
+- **AI agents** · emerging competitor / participant
+- **Endogeneity / Legibility** · reasons PM prices can diverge from "truth"
+
+## Sources
+
+- [Prediction Markets Underperform Simple Baselines For Infectious Disease Forecasting](https://arxiv.org/abs/2605.11220) — Dudley & Magdaleno · May 11, 2026 [FULL_READ · abstract]
+- [Polymarket Is Not a Truth Machine](https://www.thetokendispatch.com/p/polymarket-is-not-a-truth-machine) — Vaidik Mandloi · Apr 11, 2026 [FULL_READ]
+- [Are Prediction Markets Good for Anything?](https://asteriskmag.com/issues/14/are-prediction-markets-good-for-anything) — Dan Schwarz · Apr 1, 2026 [FULL_READ]
+- [Can LLMs Beat the Market?](https://www.oddchain.com/p/can-llms-beat-the-market) — OddChain · Mar 19, 2026 [FULL_READ]
+- [The Book That Predicted Polymarket](https://x.com/mikita_crypto/article/2029939210350645729) — Ahnianchykau · Mar 6, 2026 [FULL_READ]
+- [Ahead of the Headlines: Prediction Markets and the Collective Mind](https://jprz1321.substack.com/p/ahead-of-the-headlines-prediction) — JP · Feb 25, 2026 [FULL_READ]
+- [Minimum Viable Liquidity](https://fiftycentdollars.substack.com/p/minimum-viable-liquidity) — Adhi Rajaprabhakaran · Feb 24, 2026 [FULL_READ]
+- [Building the Truth Machine](https://freesystems.substack.com/p/building-the-truth-machine) — Andy Hall, Elliot Paschal · Feb 13, 2026 [FULL_READ]
+- [Why Prediction Markets Aren't Gambling? (The Math)](https://x.com/RohOnChain/article/2020565633453412751) — Roan · Feb 9, 2026 [FULL_READ]
+- [Is AI Any Good at Predicting?](https://reachavci.substack.com/p/is-ai-any-good-at-predicting) — Avci · Feb 2, 2026 [FULL_READ]
+- [The Nielsen Moment for Prediction Markets](https://reachavci.substack.com/p/the-nielsen-moment-for-prediction) — Avci · Jan 12, 2026 [FULL_READ]
+- [Manifesto: Make Precision Pay](https://x.com/TideMarkets/article/2008508916616098086) — Tide · Jan 6, 2026 [FULL_READ]
+- [The Perils of Election Prediction Markets](https://goodauthority.org/news/the-perils-of-election-prediction-markets/) — John Sides · Dec 18, 2025 [FULL_READ]
+- [How Well Can Large Language Models Predict the Future?](https://forecastingresearch.substack.com/p/ai-llm-forecasting-model-forecastbench-benchmark) — Forecasting Research Institute · Oct 8, 2025 [FULL_READ]
+- [Prediction Markets Are Mediocre](https://www.lesswrong.com/posts/opzJP7QHgyHzMth5o/prediction-markets-are-mediocre) — LessWrong · Apr 5, 2025 [FULL_READ]
+- [The Art of Forecasting](https://paragraph.com/@filarm/the-art-of-forecasting) — fil · Sep 30, 2024 [FULL_READ]
+- [Why Prediction Markets Aren't Popular](https://worksinprogress.co/issue/why-prediction-markets-arent-popular/) — Whitaker & Mazlish · May 17, 2024 [FULL_READ]
+- [Combinatorial Prediction Markets: An Experimental Study](https://link.springer.com/chapter/10.1007/978-3-642-40381-1_22) — Powell, Hanson, Laskey, Twardy · Sep 16, 2013 [FULL_READ · Springer abstract; full chapter paywalled]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/futarchy.md b/.qoder/plans/pm-plan/theory_concepts/futarchy.md
new file mode 100644
index 0000000000..a81b760726
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/futarchy.md
@@ -0,0 +1,79 @@
+# Futarchy
+
+**Category:** Governance & Decisions
+**Source:** [PM Atlas — Futarchy](https://www.pmatlas.xyz/concepts/futarchy)
+
+---
+
+## Definition
+
+**Quick definition.** A governance system, proposed by Robin Hanson in 2000, where decisions are made by prediction markets: a community votes on what it values (the objective metric), and conditional markets determine which policy is most likely to maximize that metric. "Vote on values, bet on beliefs."
+
+## Key Insights
+
+- Origin: Robin Hanson's "Shall We Vote on Values, But Bet on Beliefs?" (2000), part of his broader information-aggregation research at George Mason University. Hanson had already argued markets aggregate dispersed information more efficiently than polls, forecasts, or expert committees; futarchy is the proposal to use that information for governance.
+- Modern asset-futarchy variant (Heavey/Umbra): people own shares in a valuable asset (DAO, company, treasury), and proposals are decided by conditional markets that bet on the asset's value if the proposal passes vs. fails. The MetaDAO is the canonical implementation; Proposal 6 (Ben Hawkins) is the canonical raid-prevention case study.
+- The deeper innovation Heavey identifies: futarchy doesn't just produce better decisions · it solves **trustless joint ownership**. Companies enforce minority shareholder rights through laws (oppression doctrines, fiduciary duty); DAOs historically have no equivalent. Asset futarchy makes majority raids economically irrational by construction, replacing legal enforcement with mechanism design.
+- The economic argument: for Bob (majority) to drain Alice's (minority) treasury via a malicious proposal, the conditional-market arithmetic forces him to buy pABC above its true (post-raid) value AND sell fABC below the pre-proposal spot price. The expected outcome of "almost succeeding" is Bob buying Alice's tokens at a premium · the attack is self-defeating.
+- This works regardless of Bob's stake · there is no special threshold at 50%. Asset futarchy decouples decision power from majority-token ownership and ties it to willingness-to-back-with-capital.
+- **Coin price is the only acceptable objective function for asset futarchy** (Heavey's argument): it is the fairest, most elegant, and least manipulable. Vitalik's 2021 concern that "it's not just coin price that people want" is, on Heavey's reading, mostly wrong · alternative metrics introduce manipulation and ambiguity that destroy the mechanism.
+- **The MetaDAO and Combinator** both run conditional-futures implementations of futarchy for crypto-native organizations. MetaDAO built futarchy.guide as a dedicated onboarding/explainer site because the conditional-futures interface is genuinely novel and confusing · a real friction point for trader recruitment.
+- Galaxy Pokorny: Decision Markets (the active mechanism inside futarchy) are reductive by design · they collapse complex tradeoffs into a single dimension (economic value). This is exactly what makes them powerful for capital allocation and resource deployment, and exactly what makes them poorly suited for governance whose stated goals include alignment, community capital, or social legitimacy.
+- **Newly-formed organizations vs. retrofits**: Galaxy argues retrofitting futarchy onto existing DAOs is hard because token economics, governance norms, and ownership structures may not align token value with organizational outcomes. New "market-native" orgs purpose-built around futarchy face fewer constraints.
+- Janiak's critique applies: the conditional-futures architecture is genuinely confusing, MVL is hard to clear for idiosyncratic decisions, and token price is a noisy KPI. Even where futarchy theoretically works, in practice it has worked for two narrow use cases · aggregated opinions ("which option is most popular?") and commitment devices ("we will only execute if the market endorses").
+- Soft rug pulls: futarchy doesn't prevent a founder from raising funds, taking a salary, and then walking away. The Parrot DAO incident saw the team distribute treasury pro-rata across token holders · but because the team minted themselves most of the tokens, the founders effectively pocketed ~$47m of investor funds while the mechanism behaved "fairly" on paper.
+- **Settlement price calculation is non-trivial**: MetaDAO's TWAP implementation requires constant trader monitoring; if a trader doesn't engage on the final day, they can find the TWAP diverges from their fair-value view too late to react. Auction-style closing settlements are an active design frontier.
+- **Insider trading regulation** is a real obstacle: in a regulated company context, executives may be restricted from participating in decision markets on company assets even when they don't have material non-public information about the specific proposal. The same lockup rules that prevent stock manipulation also constrain the people best positioned to price decision markets.
+- **Custodial-asset futarchy fails**: if a DAO controls $100m of user deposits but has only $10m of fair-value market cap, an attacker can rationally buy DAO tokens above fair value to pass a deposit-draining proposal. Futarchy works as joint-ownership protection only when the DAO's controlled assets approximate its own fair value.
+- Hanson's "distilled human judgment" framing (via Vitalik): futarchy can be used as a cheap proxy for expensive trusted mechanisms (courts, expert panels). Set up a prediction market on what the expensive mechanism would decide; only invoke the expensive mechanism rarely. This dramatically lowers cost while preserving legitimacy.
+- Abhitej's framing: futarchy is part of the broader argument that **markets are the next stage in expression**. From print to radio to social media, each medium widened who could speak. Markets are unique in demanding that speakers bear consequences for being wrong · Hayek's price-as-coordination, Taleb's skin-in-the-game, Hanson's futarchy. In an AI-saturated information environment, "staked speech" may out-trust "cheap talk."
+- a16z crypto podcast (Tabarrok / Kominers): prediction markets and futarchy are part of a broader category of information-aggregation mechanisms (alongside peer prediction, etc.). Applications span corporate decision-making, scientific reproducibility, DeSci, and governance · but each application needs custom design, and the most promising near-term use cases may be corporate rather than democratic.
+
+## Notable Quotes
+
+> "Since its inception, futarchy has been presented as a way to make smarter decisions by harnessing the predictive power of markets. This is accurate, but exposure to the lawless wasteland of blockchain governance has revealed a more fundamental innovation: futarchy solves the problem of trustless joint ownership."
+> — *Kevin Heavey, *Futarchy as Trustless Joint Ownership**
+
+> "Joint ownership without minority protection is an illusion. And since majoritarian DAOs typically do not operate as actual companies (and if they did it would be difficult to describe them as decentralized), any semblance of minority ownership they project only lasts as long as the majority wants it to."
+> — *Kevin Heavey*
+
+> "Attempting to manipulate the market created a scenario where the potential gains from the proposal's passage were outweighed by the sheer cost of acquiring the necessary META."
+> — *Ben Hawkins, attempting Proposal 6 raid on MetaDAO (quoted by Heavey)*
+
+> "Pure futarchy has proven difficult to introduce, because in practice objective functions are very difficult to define (it's not just coin price that people want!)"
+> — *Vitalik Buterin (quoted by Heavey, who pushes back: coin price *is* the right objective for asset futarchy)*
+
+> "MetaDAO even had to build a dedicated site, futarchy.guide, just to walk people through the basics. Another hurdle for attracting traders."
+> — *alexjaniak*
+
+> "Each medium widened who could speak, but only markets demand that speakers bear consequence for being wrong."
+> — *Abhitej, *Predictions Are The New Expression**
+
+## Where It Matters
+
+Futarchy is the most ambitious governance application of prediction markets and the longest-standing "killer use case" in the rationalist canon. It is also the most contested: the elegance of the theory has not been matched by deployment success outside MetaDAO. The two open frontiers are (1) **whether new-form market-native organizations can avoid the retrofitting problem** and (2) whether **AI-driven market making** can lower MVL enough to make idiosyncratic futarchic decisions tractable. Practical futarchy is the most direct competitor to traditional DAO governance and a clarifying lens on what prediction markets are *for* beyond "betting on news."
+
+## Related Concepts
+
+- **Decision markets** · Decision markets are the operational mechanism inside futarchy.
+- **Conditional tokens** · pABC/fABC + pUSD/fUSD is the canonical onchain implementation pattern.
+- **Information aggregation / price discovery** · Futarchy is the application that proves (or disproves) that price discovery can be redirected into governance.
+- **Impact markets** · Asset-futarchy is a special case of impact markets where the asset-conditional price is bound to a governance trigger.
+- **Hyperstition markets** · Hyperstition is futarchy with execution: HYPERSTITIONS frames it as "futarchy with execution built in · betting YES means coordinating action toward manifestation."
+- **No-loss prediction markets** · A potential lower-stakes onramp to futarchic governance for organizations that can't justify principal-at-risk markets.
+- **Reflexivity** · Futarchy is a controlled reflexive system: the price determines the action that determines the outcome.
+- **Minimum viable liquidity** · MVL is the dominant practical obstacle to futarchic governance for non-major decisions.
+- **Wisdom of crowds** · Futarchy is the most-discussed wisdom-of-crowds-applied-to-governance mechanism; both share the assumption that distributed information is better than centralized expertise.
+
+## Sources
+
+- [Where Are All the Decision Markets?](https://www.lesswrong.com/posts/bDxqMn3GJEhPeqZey/where-are-all-the-decision-markets) — alexjaniak, LessWrong · May 12, 2026 ·
+- [Predictions Are The New Expression](https://x.com/abhitejxyz/status/2047353485700825546) — Abhitej · Apr 24, 2026
+- [Prediction Markets' Next Frontier: Impact and Decision Markets](https://www.galaxy.com/insights/research/prediction-markets-impact-markets-decision-markets-futarchy) — Zack Pokorny, Galaxy · Jan 12, 2026 ·
+- [Stop Predicting. Start Manipulating.](https://x.com/hyperstiti0ns/article/1994422160790536299) — HYPERSTITIONS · Nov 28, 2025 ·
+- [A Small Prediction Market Design Taxonomy](https://x.com/aaronjmars/article/1991975420669915328) — aaronjmars · Nov 22, 2025 ·
+- [Next Gen Prediction Markets](https://x.com/socialgraphvc/status/1952808508396552534) — Social Graph Ventures · Aug 6, 2025 ·
+- [Prediction Markets and Beyond](https://a16zcrypto.com/posts/podcast/prediction-markets-information-aggregation-mechanisms/) — Tabarrok, Kominers, Chokshi (a16z crypto) · Nov 21, 2024 ·
+- [From Prediction Markets to Info Finance](https://vitalik.eth.limo/general/2024/11/09/infofinance.html) — Vitalik Buterin · Nov 9, 2024 ·
+- [Futarchy as Trustless Joint Ownership](https://www.umbraresearch.xyz/writings/futarchy) — Kevin Heavey · Oct 28, 2024 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/gap-risk.md b/.qoder/plans/pm-plan/theory_concepts/gap-risk.md
new file mode 100644
index 0000000000..da9e13748a
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/gap-risk.md
@@ -0,0 +1,70 @@
+# Gap Risk
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Gap Risk](https://www.pmatlas.xyz/concepts/gap-risk)
+
+---
+
+## Definition
+
+**Quick definition.** Exposure to sudden large price jumps in binary markets when new information arrives between trades. Gap risk is what makes prediction markets uniquely hostile to leverage, to passive market making, and to traditional risk-management toolkits · outcomes resolve instantly, skipping the intermediate prices that liquidation engines need to function.
+
+## Key Insights
+
+- semaji.eth: "Gap risk is effectively worse than any other asset class because informed counterparties can have near-perfect information and take out entire order books." Indian options easy → crypto medium → PMs *legendary*.
+- Nick Ruzicka surveys leverage attempts and finds nearly everyone converges to **1×–1.5×** rather than the 10×–20× they advertise. **dYdX's TRUMPWIN perp on election night 2024** is the case study: sophisticated safeguards still broke under real conditions. Three camps: constrain, engineer around (dynamic fees, circuit breakers), or ship and iterate.
+- Darren on the four leverage models · lending pools (Gondor / Morpho-style), prime brokers (Ultramarkets), synthetic desks (CFD counterparties), perpetual futures (dYdX-style) · all share **structural dependency on CLOB venue architecture that degrades during jump events**. Fee revenue opportunity sized at $15M base / $50.7M bull, **87% from financing on OI**.
+- sybilpm's "Sniper's Tax": sniping geopolitical strike markets at 10c costs **80 cents on the dollar** when 0.10 → 0.99 on a single tweet. Concrete case: "dudukos" cleared the entire order book of "Will Israel strike Gaza on January 3, 2026?" in one trade, buying from $0.10 up to $0.80, then repeated the pattern Jan 10/11/12 across dozens of Israel-strike markets. Proposes frequent batch auctions (Budish, Cramton, Shim 2015) to neutralize gap risk by shifting competition from speed to accuracy.
+- sybilpm on the unhedgeable problem: in equities an MM holding Apple can sell Nasdaq futures; in PMs there's no correlated instrument to a "Will Israel strike Lebanon today?" position · the MM is just exposed. Quote: "You weren't providing liquidity to a forecaster. You were exit liquidity for a bot."
+- sybilpm MM profit equation: **E[π] = (s/2 · V(s)) − P_news · L_snipe**. When P_news is high (geopolitical, breaking-news markets) the sniping term dominates and MMs only have two options · widen s or pull quotes. Both make the market worse.
+- sybilpm's "liquidity mirage": at 3am Tuesday a reasonable order book is visible, but it'll vanish the instant news drops; once real liquidity is gone, markets become trivially manipulable with a few thousand dollars. Polymarket/Kalshi LP-incentive programs route retail LPs into being "exit liquidity for snipers" · rewards never sized to cover 80-cents-per-contract gap risk.
+- njokuScript ("Leverage Fixes PMs"): the gap-risk workaround is **temporal arbitrage** · leveraged markets close *before* event resolution, so liquidation engines never face the discontinuity. Vault-based yield layer captures trading-activity returns rather than directional outcome exposure.
+- Sakshi Mishra: trade-expressiveness is one of five infrastructure pillars on the path to $1T · leverage faces a gap-risk constraint unique to binary markets.
+- DWF Ventures: jump risk, binary valuation, and regulatory uncertainty are the three structural barriers to leverage and collateral lending. Emerging solutions: epoch-based fees, perp futures on outcomes, tokenized positions on Solana.
+- Stephanie Stacey (FT) on bonding trades: ~$150k executable at one time on near-certain PMs; small yield, **catastrophic tail risk** if the near-impossible outcome occurs. Gap risk is the tail of bonding.
+- njokuScript ("A New Kind of Asset Class"): PM positions have **time-bounded decay and binary convergence to truth** · properties that create temporal arbitrage but also make platform-native margin dangerous.
+- Will Howard's adjacent framing: continuous-time markets *reward speed over accuracy*. In a batch auction Trader A and Trader B both land in the same window; "the winner is whoever valued the contract higher, not whoever had the shorter wire." The "rents flow toward accuracy instead of infrastructure" · Budish-Cramton-Shim showed this formally in 2015 for equities; the case in PMs is even stronger because prices can jump 80 points in a tick.
+
+## Notable Quotes
+
+> "In traditional markets, sniping costs basis points; in prediction markets, it costs 80 cents on the dollar."
+> — *sybilpm*
+
+> "The resolution is binary and often instant. There's no gradual price discovery."
+> — *Nick Ruzicka*
+
+> "Gap risk is effectively worse than any other asset class."
+> — *semaji.eth*
+
+> "The markets with the highest social value and information content"
+> — *those with extreme or high news sensitivity · are precisely the markets in which passive liquidity is most difficult to sustain." · sybilpm*
+
+> "Your cancel button was slower than their react button."
+> — *sybilpm*
+
+## Where It Matters
+
+Gap risk is the binary-specific amplifier on every other microstructure problem. It's why MMs widen spreads defensively, why leverage products keep converging to 1.5×, why dYdX's election perp was an instructive failure, and why "bonding" trades carry tail risk that wipes out years of small yield in a single resolution. Every serious design proposal in 2026 · batched auctions, epoch-based fees, temporal-arbitrage leverage windows, tokenized positions, dynamic fees scaled by P×(1-P) · is a reaction to gap risk specifically.
+
+## Related Concepts
+
+- **Adverse selection** · gap risk is adverse selection in extremis.
+- **Toxic flow** · the realized form of gap risk.
+- **Market making** · gap risk is the binding constraint on profitable MMing.
+- **Batched auctions** · leading mitigation.
+- **Position collateralization / temporal arbitrage** · capital-efficiency and leverage workarounds that depend on closing positions before resolution.
+- **Binary contracts** · the structural cause.
+- **Oracle design / resolution criteria** · control the moment when gap risk fires.
+
+## Sources
+
+- [A Second Identity: Prediction Markets as Financial Derivatives](https://x.com/i/article/2049414664946323456) — DWF Ventures · Apr 30, 2026
+- [Leverage in Prediction Markets](https://x.com/0xMims/status/2041498106181627918) — Darren · Apr 7, 2026
+- [The Sniper's Tax](https://sybilpm.substack.com/p/the-snipers-tax) — sybilpm · Mar 8, 2026
+- [Prediction Markets: The Path to $1 Trillion and What Needs to Happen Next](https://x.com/a1research__/article/2022340314770375024) — Sakshi Mishra · Feb 14, 2026
+- [Leverage Fixes Prediction Markets: The Case for Why 10x Is Safer Than 1x](https://x.com/njokuScript/article/2022351299547689325) — njokuScript · Feb 14, 2026
+- [How Prediction Market Traders Reinvented the Bond](https://www.ft.com/content/3382195d-b417-4fc8-9c2e-17f38027a635) — Stephanie Stacey (FT) · Feb 6, 2026
+- [Everyone's Promising 20x Leverage on Prediction Markets. Here's Why It's Hard.](https://medium.com/@nick.c.ruzicka/everyones-promising-20x-leverage-on-prediction-markets-here-s-why-it-s-hard-8684d9a77e11) — Nick Ruzicka · Jan 27, 2026
+- [Prediction Markets: A New Kind of Asset Class](https://x.com/njokuScript/article/2010080717729046613) — njokuScript · Jan 11, 2026
+- [The Liquidity Problem in Prediction Markets, Part II: Adverse Selection in Prediction Markets](https://x.com/semajeth/status/1975211193418563697) — semaji.eth · Oct 6, 2025
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/hedging.md b/.qoder/plans/pm-plan/theory_concepts/hedging.md
new file mode 100644
index 0000000000..27efedb994
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/hedging.md
@@ -0,0 +1,85 @@
+# Hedging
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Hedging](https://www.pmatlas.xyz/concepts/hedging)
+
+---
+
+## Definition
+
+**Quick definition.** Taking offsetting positions to reduce exposure to adverse price movements or uncertain outcomes. In prediction markets, hedging is both an end-user use case (corporate, institutional event-risk) and a market-maker primitive (offsetting inventory) · but binary structure and fragmented venues make most hedges imperfect.
+
+## Key Insights
+
+- Astaria: prediction-market perps are structurally different from crypto perps because event contracts lack a tradeable underlying. Most implementations become "liquidation arcades"; the winning path is **institutional hedging infrastructure for continuous event-risk management**.
+- a16z (Immerman & Rodriguez): three-stage institutional adoption · using markets as data → integrating into compliance → actively hedging risk. Bottleneck for institutions is **full notional collateral requirements**, which Kalshi is addressing via margin trading licenses.
+- 0xturbanurban's "Bane of Binaries": Minsky's **vega wedge** · binary hedgers using vanilla options pay ~4.8% overcharge for BTC binaries, **7–20% for gold**. PMs can undercut this tax in deep-volume categories. What still keeps institutions out: no shared Black-Scholes equivalent, missing risk infrastructure.
+- Marinson ("Seeing Like a Market") empirical pipeline: 87 event contracts across 11 categories, 2.89M trade-row dataset, Jan 2024 to Feb 2026. Core framework: PMs win when **structural wedge (W) > execution cost (C)**. The wedge is paid every time an institution uses derivatives; execution cost compresses as PM liquidity arrives.
+- Marinson category scorecard: BTC (median VRP 4.10%) = 12 PM wins / 2 threshold / 6 losses; Elections = 12/0/5; FOMC = 3/9/0 (zero structural losses, just liquidity-bound); Equity = 1/1/11+1; Gold = 1/0/2 (high VRP but illiquid PM markets); Silver = 0/0/3 (negative VRP, derivatives structurally cheaper). **Overall 30/12/45 · 42 of 87 contracts already favor PMs at $3M reference depth.**
+- Marinson's FOMC convergence: **cost gap fell from 12pp (March 2024) to <2pp (Feb 2026) at $3M** · driven by liquidity compression, not vol expansion. The "apparatus" of dealer infrastructure is being unbundled; the cost differential is "apparatus rent."
+- Marinson on BTC's January 2026 natural experiment: 5 BTC contracts at identical 4.08% VRP but different strikes/volumes · PM wins at $100k ($13.3M vol), $105k ($7.1M), $110k ($4.9M), $150k ($32.8M); PM loses only at $125k ($2.2M vol). **Liquidity is the only variable**.
+- Marinson on the smile: PM-derived IV across BTC strike contracts shows the **same skew, fat tails, and regime dynamics** as options markets. "No dealers. No SABR. No apparatus. The smile emerged from aggregation alone."
+- Marinson on Fed Funds options unobservability: Databento returns zero rows, CME omits them from liquidity reviews, CFTC excludes them from weekly data, pit trading closed in May 2021 · the supposed benchmark for FOMC hedging cannot even be priced from public data. Required a 7-step replication pipeline via SOFR options and Black-76.
+- Smallwood case studies in detail · Logitech: raw Polymarket tariff probability 51%, mapped down to **35% effective probability** for Logitech's gross-margin exposure because the contract resolved on legal refund event vs. economic-relief reality; weighted on a status-quo / relief / escalation scenario tree rather than binary thinking. Eli Lilly: raw retatrutide approval probability 29.5%, haircut to **22.1% effective**, EV uplift $24.2B from pulling approval into 2026; probability-weighted EV uplift ≈ $5.4B = 0.61% of LLY's market cap. "Useful does not mean decisive."
+- Jeff Park: "the flip side of speculation is always insurance." PM payoffs are precise (binary → clean basis risk to truth) with finite expiry · structurally different from other derivatives.
+- Alan Wu: PMs serve risk transfer (hurricane, policy exposure) and information accountability functions *even when prices drift from pure probability*.
+- Nic Carter detailed: corporate hedging is impractical due to **market fragmentation and basis risk**. "The more useful the market is to the hedger, the less liquid it is likely to be." A vaccine manufacturer wanting to hedge an FDA shutdown is forced into buying "TrumpWin" · high correlation, but with non-trivial basis risk. The natural counterparty problem: a biotech firm is naturally long approval, so the firm + employees + management + shareholders all want to be short "approval"; no one wants the long side.
+- Carter on why PM insurance economics fail: "Insurance markets are written on losses that are objectively measurable and tightly specified." A homeowner insures a defined asset for a defined value against a defined risk; CDS references a specific bond with a legal credit event. A PM contract on a Supreme Court decision is "a coarse, binary contract written on a political event whose economic consequences are heterogeneous and path dependent."
+- Andy Hall & Elliot Paschal: **98.7% of political prediction markets qualify as "ghost towns"** with wide spreads and no counterparty; the truth-machine vision requires turning political contracts into political-risk insurance. Hedgers are the "liquidity traders" of Glosten-Milgrom · their presence is what makes it profitable for informed traders to participate at all.
+- Hall & Paschal blueprint for institutional hedging: (1) shared contract definitions across platforms (ISDA-equivalent for PMs), (2) cross-subsidy from sports markets, (3) AI agents to trade where humans won't, (4) listings driven by user/partner proposals · CNN with Kalshi, WSJ with Polymarket. The OPM-website resolution debacle for a Polymarket government-shutdown contract is cited as why ISDA-style standardization is needed.
+- Mike's "How Prediction Markets Turn Into Risk Instruments": three hedging modes already live · crypto-price binaries; attention markets (Trendle) as sentiment hedges; cross-platform composability (Gondor lending against PM positions, DFlow tokenizing Kalshi contracts as SPL tokens).
+- Dean Eigenmann (HIP-4 paper): when outcome contracts share margin with underlying exposure on the same execution layer, parametric cover unlocks a market two orders of magnitude larger than current DeFi insurance. Compares to CDS, cat bonds, reinsurance sidecars, weather derivatives.
+- Niakris: hedging use cases are part of the case PMs are finance not gambling · peer-to-peer architecture, skin-in-the-game pricing.
+- Lebron: PMs lack heterogeneous risk preferences for efficiency; rely instead on continuous retail losses · limits how robust they can be as hedging infrastructure.
+
+## Notable Quotes
+
+> "The flip side of speculation is always insurance."
+> — *Jeff Park*
+
+> "Corporate hedging is impractical due to market fragmentation and basis risk."
+> — *Nic Carter*
+
+> "Binary hedgers pay around a structural overcharge (magnitude varies by asset; specific figures unverified)."
+> — *0xturbanurban, citing Minsky's vega wedge*
+
+> "The cost differential is not a quality discount. It is apparatus rent. Same distributional content. Different transmission cost."
+> — *Lauris Marinson*
+
+> "The more useful the market is to the hedger, the less liquid it is likely to be."
+> — *Nic Carter*
+
+> "Insurance markets are written on losses that are objectively measurable and tightly specified. A prediction market on a Supreme Court decision or regulatory change is a coarse, binary contract written on a political event whose economic consequences are heterogeneous and path dependent."
+> — *Nic Carter*
+
+## Where It Matters
+
+Hedging is the asset class's path to institutional scale · if a corporate treasurer can use a PM contract to hedge tariff risk, policy risk, climate risk, or supply-chain risk, the addressable market goes from "retail betting" to a slice of OTC derivatives. The data shows isolated categories (BTC binaries, FOMC) are already cheaper than the derivative alternative, but most categories still have basis risk, illiquidity, and resolution-criteria mismatches that prevent serious corporate use. Fixing those · through standardized contracts, better collateral, and parametric-cover infrastructure like HIP-4 · is the explicit thesis behind teams like Astaria, Daedalus, and the Hyperliquid event-contract program.
+
+## Related Concepts
+
+- **Liquidity provision** · hedging only works at sufficient depth.
+- **Event contracts** · the unit being hedged with.
+- **Cross-platform arbitrage** · fragmentation creates both basis risk and arbitrage.
+- **Position collateralization** · capital efficiency unlocks more hedging volume.
+- **Binary contracts / multi-outcome markets / distribution markets** · granularity changes what you can hedge.
+- **Resolution criteria / oracle design** · basis risk lives here.
+- **Adverse selection** · hedgers can be informed; that's a feature for them, a problem for MMs.
+
+## Sources
+
+- [Outcome Markets as a Cover Venue: HIP-4 and Its Traditional Comparables](https://x.com/DeanEigenmann/status/2052734335971713347) — Dean Eigenmann · May 8, 2026
+- [Prediction Market Perps - the 1% Winning Product](https://x.com/AstariaTrade/status/2051363114881568852) — Astaria · May 4, 2026
+- [What Most People Get Wrong About Prediction Markets](https://x.com/dgt10011/status/2045952603301851173) — Jeff Park · Apr 20, 2026
+- [Prediction Markets: They Grow Up So Fast](https://open.substack.com/pub/a16z/p/prediction-markets-they-grow-up-so) — Alex Immerman, Santiago Rodriguez (a16z) · Apr 16, 2026
+- [The Bane Of Binaries: What Prediction Markets Are Missing](https://x.com/0xturbanurban/status/2044433520806830101) — 0xturbanurban · Apr 15, 2026
+- [Can Polymarket Make You a Better Equity Analyst?](https://theagenticanalyst.substack.com/p/can-polymarket-make-you-a-better) — Alistair Smallwood · Apr 9, 2026
+- [How Prediction Markets Can Ascend](https://x.com/alanwu/article/2036438579824496906) — Alan Wu · Mar 25, 2026
+- [Seeing Like a Market: Event Contracts and Market Topology](https://www.slampaper.xyz/) — Lauris Marinson · Mar 1, 2026
+- [Polymarket Is Not a Casino. Why Prediction Markets Are Finance, Not Gambling](https://x.com/13_niakris/article/2025614474263093627) — Niakris · Feb 23, 2026
+- [Prediction Markets Are Not Good Markets (Yet)](https://murmurationstwo.substack.com/p/prediction-markets-are-not-good-markets) — Nic Carter · Feb 21, 2026
+- [Building the Truth Machine](https://freesystems.substack.com/p/building-the-truth-machine) — Andy Hall, Elliot Paschal · Feb 13, 2026
+- [How Prediction Markets Turn Into Risk Instruments](https://x.com/mikhryc0x/article/2020802941091741972) — Mike · Feb 9, 2026
+- [Predicting Our Own Demise](https://reducibleerrors.com/prediction-markets/) — Agustin Lebron · Aug 17, 2025
+- [Why Prediction Markets Aren't Popular](https://worksinprogress.co/issue/why-prediction-markets-arent-popular/) — Nick Whitaker, J. Zachary Mazlish · May 17, 2024
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/hyperstition-markets.md b/.qoder/plans/pm-plan/theory_concepts/hyperstition-markets.md
new file mode 100644
index 0000000000..e89fcbfb79
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/hyperstition-markets.md
@@ -0,0 +1,59 @@
+# Hyperstition Markets
+
+**Category:** Governance & Decisions
+**Source:** [PM Atlas — Hyperstition Markets](https://www.pmatlas.xyz/concepts/hyperstition-markets)
+
+---
+
+## Definition
+
+**Quick definition.** Markets where collective belief in an outcome actively contributes to making that outcome happen. Where prediction markets ask "what will happen?" hyperstition markets ask "what can we make happen?" Reflexivity is treated as a *feature*, not a bug.
+
+## Key Insights
+
+- The term "hyperstition" comes from the Cybernetic Culture Research Unit (Nick Land, Sadie Plant, 1990s Warwick) · describing fictions that make themselves real through belief and action. Hyperstition markets operationalize this by adding a financial mechanism: betting YES is implicitly committing to take action toward manifestation.
+- **The core inversion** (HYPERSTITIONS, "Stop Predicting. Start Manipulating."): prediction markets treat reflexivity as a bug · when the market's price moves the outcome, the forecast becomes self-referential and the wisdom-of-crowds argument breaks down. Hyperstition markets treat reflexivity as a *design feature*. The market's purpose is not to forecast a fixed-in-advance event but to *coordinate the belief and action that produce the event*.
+- **Hyperstition markets are futarchy with execution built in** (HYPERSTITIONS framing): a futarchic decision market decides whether to take an action; a hyperstition market is the action. Betting YES means committing capital toward manifestation; the price discovers the marginal cost of coordination needed to achieve the outcome.
+- **Dynamic subsidies** are part of the design surface: as the market price moves, subsidies adjust to either accelerate coordination (when the project is gaining momentum) or to dampen runaway speculation. The market discovers not just probability but the *price of getting it done*.
+- This reframes the wisdom-of-crowds argument: in a hyperstition market, you are not asking "is the crowd's belief calibrated to reality?" but "is the crowd's belief sufficient to move reality?" · a fundamentally different epistemic claim.
+- aaronjmars positions hyperstition markets inside a 14-mechanism taxonomy of prediction-market designs, alongside bonding-curve markets, opinion markets (Keynesian beauty contests), opportunity markets (private prices), futarchy (MetaDAO), perpetual markets, quantum markets (capital-efficient parallel conditionals), and no-loss PMs. Each mechanism optimizes for a different goal: accuracy, speed, coordination, or outcome manifestation.
+- **The closest existing primitives** are crypto memecoin launches (price-as-meme-coordination), Kickstarter-style crowdfunding (commitment threshold triggers execution), and prediction-market parlays where one leg conditions on a community action. None has the explicit dynamic-subsidy + futarchic-execution loop that defines hyperstition markets.
+- **Use cases the framing unlocks**: collective political action (a market on whether a movement reaches a threshold; YES = participation), launch coordination (a market on whether a startup reaches PMF, with YES capital flowing into the product), creator economy (a market on whether an artist breaks out, with YES purchases counting as both bets and engagement).
+- **Adversarial concerns are unusually pointed**: hyperstition markets are explicitly manipulation-as-feature. The line between "coordinating beneficial action" and "market manipulation toward arbitrary outcomes" is blurry. The mechanism has obvious wash-trading and pump-and-dump failure modes; the design challenge is constraining coordination toward outcomes that are net-positive when realized.
+- **The reflexivity loop has thermodynamic limits**: the market can only manifest outcomes where the cost of coordination is less than the value participants assign to the outcome. Markets that demand more action than the crowd's collective willingness fizzle.
+- **Distinct from impact markets** in mechanism: impact markets price an asset's value *conditional on an exogenous event*; hyperstition markets generate the event itself through participation.
+- **Distinct from prediction markets** in normative stance: a "good" prediction market is one whose price accurately tracks the underlying probability; a "good" hyperstition market is one whose price *moves the underlying* toward a chosen outcome.
+- **Manifestation as the success criterion** changes the resolution problem. Standard prediction markets resolve YES/NO based on an oracle observing an external event. Hyperstition markets need to resolve on whether the *coordinated action actually occurred*, which is closer to a milestone-funding milestone than a binary fact.
+- The framing is currently more theory and pitch deck than deployed infrastructure. No production hyperstition market with binding coordination payouts has launched at scale as of May 2026 · the active examples are conceptual posts and small experiments inside crypto culture.
+
+## Notable Quotes
+
+> "Where prediction markets ask 'what will happen?', hyperstition markets ask 'what can we make happen?'"
+> — *HYPERSTITIONS, *Stop Predicting. Start Manipulating.**
+
+> "Positions this as futarchy with execution built in"
+> — *betting YES means coordinating action toward manifestation. The market discovers the price of coordination through dynamic subsidies." · HYPERSTITIONS (via OnPrediction editorial summary)*
+
+> "Each design optimizes for different goals: accuracy, speed, coordination, or outcome manifestation."
+> — *aaronjmars, *A Small Prediction Market Design Taxonomy**
+
+## Where It Matters
+
+Hyperstition markets are the most explicit confrontation with the reflexivity problem that haunts the rest of the category. By accepting reflexivity as a feature, they open a design space that's adjacent to but distinct from prediction markets, futarchy, and impact markets · a space where the market's job is **coordination** rather than **forecasting**. For builders, hyperstition is interesting less as a deployable product today and more as a lens: it forces a clear answer to "what is this market actually *for*?" · forecasting, hedging, governance, or manifestation. For Dekant specifically, hyperstition framing surfaces an important distinction: a continuous distribution-market still forecasts; it does not coordinate. Hyperstition mechanics would be an additive layer, not a replacement.
+
+## Related Concepts
+
+- **Futarchy** · Hyperstition markets are explicitly framed as futarchy with execution built in.
+- **Reflexivity** · The single most-related concept; hyperstition markets *are* engineered reflexivity.
+- **Decision markets** · Both involve markets choosing actions, but decision markets aim at optimization while hyperstition markets aim at manifestation.
+- **No-loss prediction markets** · Both lower the barrier to participation, but for different reasons: no-loss makes betting "safer" while hyperstition makes betting "an action that matters."
+- **Parlays** · Multi-leg hyperstition markets (manifest A and B together) are an obvious extension.
+- **Wisdom of crowds** · Hyperstition flips the wisdom argument: the crowd's belief becomes the wisdom because it's also the cause.
+- **Market manipulation** · The boundary case for hyperstition: when is "coordinated participation" manipulation, and when is it coordination?
+- **Bonding-trades / bonding-curve markets** · A natural implementation primitive for hyperstition (price grows with participation).
+
+## Sources
+
+- [Stop Predicting. Start Manipulating.](https://x.com/hyperstiti0ns/article/1994422160790536299) — HYPERSTITIONS · Nov 28, 2025
+- [A Small Prediction Market Design Taxonomy](https://x.com/aaronjmars/article/1991975420669915328) — aaronjmars · Nov 22, 2025 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/impact-markets.md b/.qoder/plans/pm-plan/theory_concepts/impact-markets.md
new file mode 100644
index 0000000000..38a7a1b975
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/impact-markets.md
@@ -0,0 +1,65 @@
+# Impact Markets
+
+**Category:** Governance & Decisions
+**Source:** [PM Atlas — Impact Markets](https://www.pmatlas.xyz/concepts/impact-markets)
+
+---
+
+## Definition
+
+**Quick definition.** Markets that price assets *conditional on* specific events occurring. Example: "What does BTC trade at if the Fed cuts 75bp?" rather than "Will the Fed cut 75bp?" Where prediction markets aggregate event probabilities, impact markets aggregate event-conditional asset valuations. In a separate strand of usage, "impact markets" also refers to markets pricing the expected social impact of projects to direct funding toward the most effective ones.
+
+## Key Insights
+
+- **Galaxy/Pokorny framing**: prediction markets reveal "this event has 60% probability." Impact markets reveal "and if it happens, BTC trades at $X." Spot markets reveal current prices but cannot directly express conditional valuations. Impact markets are the missing piece · they collapse the multi-step inference of "probability × asset-correlation × position-sizing" into direct price discovery.
+- **Mechanism**: instead of trading $0/$1 conditional tokens, traders buy/sell the **asset itself in a conditional state**. The trade only settles if the event occurs; if it doesn't, the position is reverted or otherwise nullified. The market structure is closer to options or conditional perpetuals than to binary outcomes.
+- **The multi-step inference problem Impact Markets solve**: today, a trader holding BTC with a view on Fed policy must (1) gather probabilistic odds from prediction markets, (2) feed them into proprietary models to estimate BTC impact, (3) execute separate trades on spot/futures markets, and (4) hope their correlation assumptions hold when the event resolves. Impact markets collapse this into one trade.
+- **Burden transfer**: impact markets shift correlation-estimation from every individual end user to the market's price discovery process. End users still face the risk that the market-implied conditional price is wrong, but this is a fundamentally different (and smaller) risk than building a bespoke correlation model from scratch · "analogous to the difference between estimating implied volatility yourself versus reading it off an options chain."
+- **Cleaner hedging**: a BTC holder worried about an election outcome can lock in a conditional BTC price for the specific scenario in a single trade · minimizing basis risk between event view and asset exposure. Standard prediction-market "hedging" requires running parallel positions in event probabilities and asset spot, hoping correlations behave.
+- **Information gap closed**: impact markets surface the market's collective view on conditional asset valuations bound to specific events. This information doesn't currently exist in an explicitly discoverable form · neither prediction markets nor spot markets reveal it. Spreading impact-market liquidity makes it discoverable.
+- Galaxy frames impact markets as the **necessary prerequisite for decision markets** to scale beyond crypto: surface conditional asset valuations (impact) → use them to govern capital allocation (decision). Impact is the information layer; decision is the governance layer.
+- **Practical hedging examples** Galaxy cites: BTC | Fed cuts 75bp; Nvidia | AI-skeptic candidate wins election; M&A target equity | regulatory approval. These are exactly the conditional scenarios that hedge funds and portfolio managers currently model internally and trade through complicated synthetic positions.
+- **Different second meaning of "impact markets"**: the older Effective Altruism / Lightcone Infrastructure usage · markets that price *expected social impact* of nonprofit projects or research, with funding directed to projects predicted to be most impactful. This is closer to a retrofunding mechanism (pay for past impact) than a conditional pricing mechanism. Onprediction's editorial framing focuses on the Galaxy/Pokorny conditional-asset usage.
+- **Token structure for impact markets needs to be asset-denominated, not $0/$1**. This is the key architectural distinction from standard prediction-market conditional tokens · the conditional payout is in the asset (BTC, equity, stablecoin USD) rather than a synthetic outcome share. Implementation likely requires either tokenized asset wrappers or oracle-based settlement at the event's resolution.
+- **Liquidity provisioning is harder** than standard prediction markets: market makers must hedge against both the event probability and the asset's price path under each event state. This is closer to options market-making than binary-options market-making.
+- **Why Galaxy is bullish**: the "next frontier" framing positions impact markets as the breakout use case after Polymarket and Kalshi. They build on the same infrastructure (oracles, conditional tokens, CLOBs) but unlock institutional hedging demand that today's binary markets cannot serve · closing the gap between "knowing what might happen" and "knowing what it means."
+- **Why this may be hard**: impact markets need both deep prediction-market liquidity on the underlying event AND deep spot/derivatives liquidity on the underlying asset. The cross-product of these two liquidity surfaces shrinks the viable market design space significantly. They'll likely launch first for very liquid asset/event pairs (BTC × Fed cuts, BTC × election) before generalizing.
+- **Adoption sequencing**: most plausible first use cases are institutional hedging products (sophisticated buyers with explicit conditional views, willing to pay basis-risk reduction). Retail adoption would follow standardized products built on top, similar to how options retail followed institutional options.
+- **Limitations the Galaxy piece names**: validity of the conditional state, oracle reliability under tail conditions, the challenge of pricing things that have never traded conditionally before. The first wave of impact markets will likely focus on event/asset pairs where both sides have liquid reference markets.
+
+## Notable Quotes
+
+> "Prediction markets aggregate probabilities for whether events will occur. Impact Markets answer the next question: 'What happens to this company or asset [if] this event occurs?' This separation allows each market type to specialize while creating a more complete information set."
+> — *Zack Pokorny, Galaxy Research*
+
+> "This architecture solves a multi-step inference problem that plagues the market: Traders must gather probabilistic odds from prediction markets, feed them into proprietary models to estimate asset impacts, then execute separate trades on exchanges… Impact Markets collapse this entire workflow into direct price discovery of conditional valuations where trades only settle if the given event actually occurs."
+> — *Zack Pokorny*
+
+> "Impact markets transfer the burden of correlation estimation from every individual end user to the market's price discovery process… The distinction is analogous to the difference between estimating implied volatility yourself versus reading it off an options chain."
+> — *Zack Pokorny*
+
+> "While prediction markets reveal event probabilities and spot markets reveal current prices, Impact Markets answer the question neither can directly: what this asset trades at if that event occurs."
+> — *Zack Pokorny*
+
+> "Impact and Decision Markets stand to extend [the prediction-market insight] by revealing what those events are worth and, in some cases, letting markets directly determine which actions organizations take."
+> — *Zack Pokorny*
+
+## Where It Matters
+
+Impact markets are the cleanest articulation of "prediction markets as financial primitives" · the path from event contracts to a real hedging instrument used by serious capital. They are also the natural complement to Dekant's continuous-outcome prediction market: where Dekant prices the *distribution* of an outcome variable (e.g., BTC price at year-end), impact markets price an asset *conditional on a discrete event* (e.g., BTC if the Fed cuts 75bp). The two designs answer different questions and can coexist; impact markets are the conditional analog, Dekant is the unconditional distributional one.
+
+## Related Concepts
+
+- **Conditional tokens** · Impact markets generalize conditional tokens from $0/$1 payouts to asset-denominated payouts.
+- **Decision markets** · Impact markets are the information layer; decision markets are impact markets wired to binding governance.
+- **Futarchy** · Asset-futarchy is a special case of impact markets where the conditional state is "proposal passes vs. fails."
+- **Hedging** · Impact markets are explicitly framed as cleaner hedging tools than parallel prediction-market + spot positions.
+- **Price discovery** · Impact markets close an information gap that neither prediction markets nor spot markets fill.
+- **Distribution markets** · Distribution markets (Paradigm / White, 2024 · what Dekant implements) price unconditional distributions over outcomes; impact markets price asset valuations conditional on discrete events. They are complementary, not competing.
+- **Oracle design** · Impact market settlement requires both event resolution AND asset-price-at-event-time observation, doubling the oracle attack surface.
+- **Liquidity provision / market making** · Impact-market market-making is closer to options market-making than to binary market-making, raising the bar for capable LPs.
+
+## Sources
+
+- [Prediction Markets' Next Frontier: Impact and Decision Markets](https://www.galaxy.com/insights/research/prediction-markets-impact-markets-decision-markets-futarchy) — Zack Pokorny, Galaxy Research · Jan 12, 2026
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/implied-correlation.md b/.qoder/plans/pm-plan/theory_concepts/implied-correlation.md
new file mode 100644
index 0000000000..6bd8ca460b
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/implied-correlation.md
@@ -0,0 +1,55 @@
+# Implied Correlation
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Implied Correlation](https://www.pmatlas.xyz/concepts/implied-correlation)
+
+---
+
+## Definition
+
+**Quick definition.** The degree to which related prediction-market outcomes *should* move together based on economic logic. When markets price correlated events inconsistently, the implied correlation differs from the historically-justified correlation · and a relative-value trade exists.
+
+## Key Insights
+
+- Jon Turek frames implied correlation as a direct import from established trading literature: "relative value trading, dual digitals, dispersion trading" · concepts "very familiar to the trading community" that he argues map cleanly onto PMs.
+- Turek's structural claim: correlation creates either **leverage** (juicing returns on a directional view by stacking correlated bets) **or arbitrage** (when historical correlation diverges from market-implied correlation), and both are live opportunities on Polymarket now.
+- The Polymarket worked example is more granular than originally summarized: **30–35% recession probability** (matches Goldman sell-side estimates vs. an economist baseline of 15–20%); within the unemployment distribution, **65% chance unemployment rises to 5%** and **35% chance it rises to 5.5%**.
+- Despite that distribution, the Fed-rate market is pricing "a baseline of no cuts, with an even bias towards a hike and a cut as the next move" · and "only a 20% chance that the Fed cuts more than two times this year".
+- Turek's historical reference set for unemployment rising **+1.2% or more in a year** (i.e. 5.5%): 1974–75 oil shock, 1980–82 double-dip, 1990–91 Gulf War, 2001 dot-com bust, 2007–09 Great Recession, 2020 COVID. **Average rate cuts across those six episodes: seven.**
+- Macro context Turek cites: US economy "shed 92k jobs" last month, ~6 months of weak payroll growth, falling JOLTs job openings, and a fresh "energy shock from the war in Iran" · all pushing the unemployment-skew higher, but the Fed-cut market hasn't repriced accordingly.
+- The structural cause Turek names explicitly: PM contracts are "priced idiosyncratically" · each market with its own MMs, retail base, narrative cycle, and no cross-market arbitrageur ensuring economic coherence. This is a renewable source of relative-value trades for anyone with a coherent macro/economic model.
+- Trade construction Turek proposes: **long "YES" on Fed rate cuts + short "NO" on unemployment outcomes** · captures payouts in *either* macro outcome because the legs hedge each other where history says they should be correlated.
+- The natural product layer is covariance markets (which would let traders express implied-correlation views directly) and AI agents (which can scan thousands of pairs faster than humans).
+- Connection to st1ne's "conditional probability arbitrage across correlated markets" MEV edge · the trader-side counterpart to building covariance infrastructure.
+- The pattern resembles **classic equity pair-trading** but operates over discrete events with cleaner economic linkages.
+
+## Notable Quotes
+
+> "Polymarket currently prices a 60%+ chance of 5% unemployment this year alongside only a 10% chance of aggressive Fed rate cuts, even though every historical episode of that unemployment spike triggered an average of seven cuts."
+> — *Jon Turek*
+
+> "Cross-market mispricings like this are common and create attractive relative value trades."
+> — *Jon Turek*
+
+> "These markets have a high degree of implied correlation and that is not being reflected in the current prices. Even at a high level, if the unemployment rate went to 5% this year, which prediction markets assume to be a +60% chance outcome, there is no way that 'on hold' + 'next move hike' should be a combined 50% chance on Polymarket."
+> — *Jon Turek*
+
+> "What we have is a lot of listed markets that are being priced idiosyncratically but many of them have an implied correlation to them."
+> — *Jon Turek*
+
+## Where It Matters
+
+Implied correlation is the cleanest argument for why prediction markets · *as a network of independently-priced contracts* · fail to aggregate information coherently across related events. Each market may be individually well-calibrated, but the joint distribution is incoherent. This is partly what limits the asset class's use for serious hedging and macro signaling, and it's the empirical case for both covariance-market infrastructure and cross-market AI agents.
+
+## Related Concepts
+
+- **Relative value trading** · the strategy that consumes implied-correlation mispricing.
+- **Covariance markets** · the proposed primitive for expressing it directly.
+- **Arbitrage** · parent category.
+- **Information aggregation** · what implied correlation reveals is broken in current PMs.
+- **AI agents** · scaling enabler.
+
+## Sources
+
+- [Prediction Markets and Implied Correlation](https://cheapconvexity.substack.com/p/prediction-markets-and-implied-correlation) — Jon Turek · Mar 24, 2026
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/incentive-compatibility.md b/.qoder/plans/pm-plan/theory_concepts/incentive-compatibility.md
new file mode 100644
index 0000000000..72bc697ece
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/incentive-compatibility.md
@@ -0,0 +1,71 @@
+# Incentive Compatibility
+
+**Category:** Mechanism Design
+**Source:** [PM Atlas — Incentive Compatibility](https://www.pmatlas.xyz/concepts/incentive-compatibility)
+
+---
+
+## Definition
+
+Designing mechanisms where truthful participation is each player's optimal strategy regardless of others' actions. In prediction markets, this is the mathematical condition that price-revelation is in every trader's interest · the formal underpinning for why markets aggregate information at all.
+
+## Key Insights
+
+- Truth-telling is the *dominant* strategy under proper scoring rules · this is what makes LMSR work. A forecaster maximizing expected reward must report her true probability estimate. This is the canonical incentive-compatibility result in the prediction-market literature (Baheet).
+- Srinivasan, Karger & Chen (SKC, 2023) extended incentive compatibility to *unverifiable* outcomes. From the paper's abstract: "We present a novel incentive-compatible prediction market mechanism to elicit and efficiently aggregate information from a pool of agents without observing the outcome, by paying agents the negative cross-entropy between their prediction and that of a carefully chosen reference agent." The reference agent is "the final agent ... chosen as the reference agent since they observe the full history of market forecasts."
+- SKC's formal result: "It is a perfect Bayesian equilibrium (PBE) for all agents to report truthfully in our mechanism and to believe that all other agents report truthfully." The mechanism applies to verifiable outcomes too, but is "primarily of interest for unverifiable outcomes."
+- The SKC mechanism uses *random termination* of the market · Monad's explainer notes "no one can be sure they're the final trader or how many more will come after them, which, as discussed in the paper, helps keep everyone honest." The last k agents receive a fixed reward R as a participation prize; all others are paid the delta of cross-entropy against `q(T)`.
+- Monad's payoff intuition: "An early trader who nudges the number from 40 → 50% (a 10-point improvement) gets more than a late trader who nudges 60 → 61%, even if the late trader's number is 'more accurate' in an absolute sense. This design choice prevents free-riding. Without it, someone could wait until the market has converged, copy that near-final number, and collect a prize for 'accuracy' without adding insight."
+- Monad explicitly ties SKC's design to the Keynesian beauty contest problem: "In this mechanism you don't get paid for matching what you think others will say. Instead you get paid for how much your move pushes the market toward where it ultimately ends."
+- Yiling Chen & David Pennock's Harvard survey (Jan 2025) names incentive compatibility, computational tractability, and individual rationality as the three common objectives prediction mechanisms share, alongside the unusual properties of *expressiveness* and *liquidity*. They note: "A pure prediction mechanism may reasonably operate at a loss; maximizing revenue or even balancing the budget may not be a concern. If the operator wants information, she may be perfectly happy to pay for it."
+- Chen & Pennock formalize incentive compatibility as: "every agent's best strategy is to honestly report all of their information as soon as they have it, an important property that's difficult to achieve in general."
+- Incentive compatibility is *not* automatic in prediction markets · it requires a specific scoring rule (logarithmic, quadratic, spherical) and breaks down under collusion, manipulation, and information asymmetry.
+- aaron (On War Markets) argues incentive compatibility on conflict markets is structurally broken by moral hazard: the IDF insider trading case shows participants have incentives to *create* the outcome they bet on, not just predict it. The information value is real but the moral-hazard tax is underpriced.
+- Guillory & Zimmermann's "Assassination Semantics" piece is essentially an incentive-compatibility critique: when a market embeds violence as a resolution path, blanket void-on-death rules can flip incentives toward causing harm to recover bets.
+- JP's "Ahead of the Headlines" frames prediction markets as a real-time information layer that complements journalism · skin-in-the-game accountability is the incentive-compatibility argument at the platform level. JP's framing: "There's no reward for confidently being wrong. There's only cost." He acknowledges COVID-19 as a case where the mechanism failed ("Markets are not omniscient. They are human").
+- Incentive compatibility connects to the *governance* of the market: who chooses resolution criteria, who runs the oracle, and whether platforms (Kalshi vs Polymarket) have different incentives to list manipulable contracts.
+- Baheet's punchline: prediction market builders need economists and game-theory experts on their teams · incentive compatibility is not a UX problem.
+
+## Notable Quotes
+
+> "It is a perfect Bayesian equilibrium (PBE) for all agents to report truthfully in our mechanism and to believe that all other agents report truthfully."
+> — *Srinivasan, Karger & Chen, *Self-Resolving Prediction Markets for Unverifiable Outcomes**
+
+> "Our key insight is that a reference agent with access to more information can serve as a reasonable proxy for the ground truth."
+> — *SKC, *ibid.**
+
+> "An early trader who nudges the number from 40 → 50% (a 10-point improvement) gets more than a late trader who nudges 60 → 61%, even if the late trader's number is 'more accurate' in an absolute sense."
+> — *michaellwy, *Explainer on Self-Resolving Prediction Markets**
+
+> "Every agent's best strategy is to honestly report all of their information as soon as they have it, an important property that's difficult to achieve in general."
+> — *Chen & Pennock, *Designing Markets for Prediction**
+
+> "Truth-telling is the dominant strategy through incentive compatibility."
+> — *Baheet, *The Game Theory Behind Prediction Markets**
+
+> "There's no reward for confidently being wrong. There's only cost."
+> — *JP, *Ahead of the Headlines**
+
+## Where It Matters
+
+Incentive compatibility is the load-bearing assumption behind every claim that prediction markets are good for information aggregation. If it fails · through manipulation, collusion, reflexivity, or moral hazard · the entire epistemic case collapses. Every new mechanism in the space (SKC for unverifiable outcomes, Trepa's orthogonal precision, hyperstition markets, futarchy) is, at root, a claim about restoring or extending incentive compatibility into a new regime. For continuous-outcome markets like Dekant, the question is whether the kernel-aggregation step preserves incentive-compatible reporting across the distribution.
+
+## Related Concepts
+
+- Proper scoring rules · the formal mechanism class that delivers incentive compatibility
+- Self-resolving markets · incentive compatibility without an oracle
+- Peer prediction · incentive compatibility via inter-respondent comparison
+- LMSR · the most-deployed incentive-compatible market maker
+- Market manipulation · the failure mode of incentive compatibility
+- Information aggregation · the consequence when incentive compatibility holds
+- Decision markets · incentive compatibility under endogenous outcomes
+
+## Sources
+
+- [On War Markets](https://x.com/probaaron/article/2027765452689014822) — aaron · X
+- [Ahead of the Headlines: Prediction Markets and the Collective Mind](https://jprz1321.substack.com/p/ahead-of-the-headlines-prediction) — JP · Substack
+- [Assassination Semantics: Why Every Market Carries the Risk of Violence](https://x.com/BetBreakNews/article/2008587654158340126) — Sean Guillory & Dan Zimmermann · X
+- [Explainer on Self-Resolving Prediction Markets](https://blog.monad.xyz/blog/self-resolving-prediction-markets) — michaellwy · Monad Blog
+- [The Game Theory Behind Prediction Markets](https://x.com/Baheet_/status/1965758390430208066) — Baheet · X
+- [Self-Resolving Prediction Markets for Unverifiable Outcomes](https://arxiv.org/abs/2306.04305) — Siddarth Srinivasan, Ezra Karger, Yiling Chen · arXiv
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/info-finance.md b/.qoder/plans/pm-plan/theory_concepts/info-finance.md
new file mode 100644
index 0000000000..2143a4e2b1
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/info-finance.md
@@ -0,0 +1,76 @@
+# Info Finance
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Info Finance](https://www.pmatlas.xyz/concepts/info-finance)
+
+---
+
+## Definition
+
+Using financial market mechanisms to elicit, aggregate, and trade on informational signals about future events. Coined by Vitalik Buterin in Nov 2024 · prediction markets are *one* application within a broader ecosystem that also touches governance, scientific research, journalism, and social media.
+
+## Key Insights
+
+- Vitalik's framing: PMs represent one app within a broader info-finance ecosystem. Mechanisms can improve governance, scientific research, journalism, and social media via information-pricing that goes beyond simple betting (Buterin, Nov 2024).
+- Robin Hanson's regulatory argument: PMs deserve the same regulatory treatment as journalism and academia. Six common harms are shared across all information systems (insider trading, manipulation, etc.) · markets should be approved by default and restricted only on clear evidence of specific harm (Hanson, response to CFTC call for comments).
+- ARK sizes PM opportunity "up to ~$5 trillion" medium-term, arguing PMs "could become a foundational layer of financial services, impacting markets well beyond sports." Frames PMs as truth-discovery infrastructure backed by capital rather than opinion, against a backdrop of historically low trust in journalism and government (only 22% of Americans trust the federal government). Sports adoption is the on-ramp, not the destination (Grous, Prasanna, Hadi · ARK).
+- Equity analyst workflow using PM probabilities: translate Polymarket's 51% tariff refund probability into 35% effective probability for Logitech's margin impact; 29.5% FDA approval into $5.4B EV uplift for Eli Lilly. Raw probabilities must be adjusted for contract wording mismatches and economic relevance (Alistair Smallwood).
+- 2025 = $63.5B volume; 2026 run rate $200B+. Sports drive current revenue (83% of Kalshi) but valuations price in info-infrastructure future that hasn't arrived. Distribution platforms (Robinhood, Coinbase) capture most value as they vertically integrate into exchange infrastructure (Kaviish).
+- ~13,500 Polymarket contracts: >80% volume in sports/crypto/elections; accuracy on "useful" markets hasn't improved since early 2025. AI chatbots may supersede PMs as the primary forecasting interface (Dan Schwarz).
+- Probability layers: attention markets (content virality), credibility markets (trust as score), demand markets (consumer intent before production) · probability signals embedded invisibly into every decision surface on the internet (Aggie).
+- Prediction markets are at an inflection point. History from 1419 Vatican papal elections → Iowa Electronic Markets → Polymarket era. Insider trading scandals (Musk tweets, French elections), moral hazard (assassination markets), wave of new entrants (Robinhood, DraftKings, Crypto.com, FanDuel) signal mainstream adoption (Eniola).
+- Agentic bazaar: PMs are the natural marketplace for sovereign AI agents to trade their core commodity · information. Agents monetize alpha through positions; market creators earn fees from surfacing unanswered questions; reproducible computation enables incorruptible AI judges. Alternative to centralized AI lab alignment · market incentives align agents through financial participation (Ben Fielding).
+- Retail financial speculation has permanently shifted from investment to *entertainment*, driven by smartphones and zero-commission. 0DTE options = 59% of options volume in 2025. Event markets expanding to cover everything; insider trading cases proliferate (Hariharan, Dopamine Markets annual letter).
+- Perpetual attention markets: Trendle treats social engagement as a tradable index. Two-layer architecture (attention index + market layer). Anti-gaming: normalization, deseasonalization, quantile clipping. Funding rates penalize crowded positions. "Attention is the only scarce resource in the digital age" (michaellwy).
+- TikTok moment for PMs: shift from edge-seeking tools for professionals to identity-signaling social platforms. TikTok-like feed where bets become public expressions of belief, removing friction of intent (michaellwy).
+- Financialisation of social networks: PMs as predecessors to financialized social. Polymarket briefly hit #1 in app stores · linking user views to financial stakes creates engagement through capital formation rather than pure attention capture. Stress-test for Web3 social primitives (Joel John).
+- Vitalik (FULL_READ) on Venezuela: at the moment people put "over a hundred thousand dollars on the line, betting that there is a 23% chance that this election would be the one where Maduro would actually get struck down," it served as a *news-detection signal* · independent of whether the bet resolved correctly. The PM = newspaper for non-bettors, betting site for bettors. "You can be more informed by reading the news *and* the charts, than by reading either one alone."
+- Vitalik on the AI unlock: "subsidies are required" on many low-volume markets because there aren't enough naive traders for sophisticated traders to take profit from. AI agents change the math: "we could potentially get reasonably high-quality info elicited even on markets with $10 of volume."
+- Vitalik on Hanson's "distilled human judgement": every time you need a decision, set up a PM on what a trusted-but-slow mechanism would say. Run the costly mechanism 0.01% of the time (random sampling or for high-volume markets) and compensate participants based on that. "Credibly neutral fast and cheap distilled version of the original highly trustworthy but highly costly mechanism."
+- Vitalik on personal tokens (friend.tech, Bitclout): "proto info-finance" with the wrong economic design · reflexivity and bubble dynamics dominate. Hanson's "prestige futures" is the proposed clean version.
+- Hanson "On Prediction Market Regulation" (FULL_READ) · six axes of info-institution harm: (A) reveal info better kept secret, (B) break promised secrecy, (C) waste time/money, (D) misleading contributions for favorable treatment, (E) change the world to get favorable treatment, (F) reward participants unequally. PMs share these with journalism, gossip, academia. Hanson's empirical claim: "at public firms announcements, half of the price change happens beforehand, and half of that is from insider trading" · i.e., insider rules in equity markets don't actually prevent leakage, so we shouldn't expect PM rules to either. Speculative markets are "far more resistant to manipulation than other info institutions" because "when traders expect more efforts to manipulate a price, they respond so that prices on average become MORE accurate." Free-speech argument: trades should be treated like protests · "as there are things you can say with trades you can't say via words."
+- Hariharan "Dopamine Markets" (FULL_READ): 2024 election attracted >$4B between Polymarket and Kalshi. Polymarket acquired QCEX for a CFTC license. Polymarket raised $2B at $9B from ICE; Kalshi raised $1B at $11B from Paradigm. Sports betting in 2025 = ~$165B wagered, 95% of bettors lose. FanDuel/DraftKings stocks fell ~30%. Insider trading cases proliferating: Taylor Swift engagement, Google search ranking, Nobel Peace Prize, 100+ UFC fights, NBA (Terry Rozier, Chauncey Billups, Jontay Porter). 43% of Americans say sports betting is bad for society (Pew, July 2025), up 9pp in 3 years. Investment-as-entertainment dynamic confirmed: 0DTE options = 59% of options volume in 2025; Hyperliquid breakout; Coinbase pushes "perpification of everything."
+- Joel John "Financialisation of Social Networks" (FULL_READ): blockchains move capital at the speed of data; meme markets and Polymarket are predecessors to a new social-network primitive where "the next big exchange will be a social network. The next big social network could be an exchange." Three primitives needed: asset issuance, context+trading, coordination. Receipts (fitness data → on-chain credentials) and Muzify (Spotify streams → artist-fan graph) are early examples.
+- michaellwy "Perpetual Attention Markets" (FULL_READ): Trendle aggregates X/Reddit/YouTube signals into a per-topic Engagement Index normalized to 0-1, with deseasonalization (sports teams spike on game day so that's filtered out). Up to 5x leverage. There's no spot oracle to anchor to, so funding rates serve as a "crowding tax" rewarding the minority side · "Trendle makes timing the end of hype a coherent strategy." Future expansion to Google Search, TikTok, news media.
+
+## Notable Quotes
+
+> "Prediction markets represent one application within a broader 'info finance' ecosystem."
+> — *Vitalik Buterin*
+
+> "Markets should be approved by default and restricted only on clear evidence of specific harm."
+> — *Robin Hanson*
+
+> "Attention is the only scarce resource in the digital age."
+> — *michaellwy*
+
+## Where It Matters
+
+"Info finance" is the framing every serious PM team has adopted as the long-form pitch · it reframes "PMs as gambling" into "PMs as one app on top of a probability-pricing protocol stack." For builders, this is the *narrative* that lets you talk to VCs and regulators without sounding niche. For Dekant specifically, info finance is the home for the distribution-market thesis: a binary contract is just the cheapest possible probability surface; the real prize is pricing every shape of belief, not just every yes/no question.
+
+## Related Concepts
+
+- **Information aggregation** · the underlying mechanism
+- **Probability infrastructure** · Aggie's more product-flavored synonym
+- **Decision markets / Futarchy** · info-finance applied to governance
+- **Attention markets / Credibility markets / Demand markets** · proposed adjacent applications
+- **Nowcasting** · using PM prices as live econ indicators
+- **AI agents** · natural counterparty class
+- **Regulatory classification** · the legal scaffolding info-finance needs
+
+## Sources
+
+- [On Prediction Market Regulation](https://www.overcomingbias.com/p/on-prediction-market-regulation) — Robin Hanson · Apr 29, 2026 [FULL_READ]
+- [Prediction Markets: The Potential Multi-Trillion Dollar Asset Class Hiding In Plain Sight](https://www.ark-invest.com/articles/analyst-research/prediction-markets-potential-multi-trillion-dollar-asset-class) — Grous, Prasanna, Hadi (ARK) · Apr 22, 2026 [FULL_READ]
+- [Can Polymarket Make You a Better Equity Analyst?](https://theagenticanalyst.substack.com/p/can-polymarket-make-you-a-better) — Alistair Smallwood · Apr 9, 2026 [FULL_READ]
+- [The Financialization of Uncertainty](https://x.com/kaviish/status/2041214800731033700) — Kaviish · Apr 6, 2026 [FULL_READ]
+- [Are Prediction Markets Good for Anything?](https://asteriskmag.com/issues/14/are-prediction-markets-good-for-anything) — Dan Schwarz · Apr 1, 2026 [FULL_READ]
+- [The Probability Layers Are Coming](https://x.com/BlondiePredicts/status/2038595927225622569) — Aggie · Mar 30, 2026 [FULL_READ]
+- [Are Prediction Markets Decaying or Evolving?](https://x.com/_drNeyo/article/2029205512391172364) — Eniola · Mar 4, 2026 [FULL_READ]
+- [Prediction Markets are the Agentic Bazaar](https://x.com/benfielding/article/2023130119708242317) — Ben Fielding · Feb 16, 2026 [FULL_READ]
+- [Dopamine Markets: 2025 Annual Letter](https://www.dopaminemarkets.com/p/dopamine-markets-2025-annual-letter) — Shreyas Hariharan · Jan 8, 2026 [FULL_READ]
+- [Perpetual Attention Markets](https://blog.monad.xyz/blog/perpetual-attention-markets) — michaellwy · Dec 15, 2025 [FULL_READ]
+- [The TikTok Moment for Prediction Markets](https://x.com/michael_lwy/status/1948400467886891314) — michaellwy · Jul 24, 2025 [FULL_READ]
+- [From Prediction Markets to Info Finance](https://vitalik.eth.limo/general/2024/11/09/infofinance.html) — Vitalik Buterin · Nov 9, 2024 [FULL_READ]
+- [Financialisation of Social Networks](https://www.decentralised.co/p/financialisation-of-social-networks) — Joel John · Oct 23, 2024 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/information-aggregation.md b/.qoder/plans/pm-plan/theory_concepts/information-aggregation.md
new file mode 100644
index 0000000000..d29ee1a3b8
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/information-aggregation.md
@@ -0,0 +1,118 @@
+# Information Aggregation
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Information Aggregation](https://www.pmatlas.xyz/concepts/information-aggregation)
+
+---
+
+## Definition
+
+The process by which markets combine dispersed private knowledge into a single consensus price signal. The Hayekian core idea: a market price is a low-bandwidth summary of every trader's private information, weighted by how much capital they're willing to stake on that information.
+
+## Key Insights
+
+- Roughly 3% of accounts drive most price discovery on prediction markets · their trades anticipate future prices, respond to news immediately, and improve calibration across a market's lifecycle. The other 97% contribute volume but minimal information, and their losses fund the informed minority (Gomez-Cram et al., SSRN 2026 · [FULL_READ] via Cloudflare; editorial summary preserved).
+- This finding *reframes* the standard wisdom-of-crowds narrative: PM accuracy is not the average opinion of many · it's an informed minority that retail flow subsidizes. Has direct implications for platform design, surveillance, and how platforms market "accuracy."
+- Insider trading is structurally a feature of information aggregation, not a bug: insider flow is what makes prices accurate (Nic Carter, citing Mansour, Coplan, Tenev, Hanson). Platforms face a calibration problem: too permissive and noise traders flee perceiving rigging; too strict and informed flow disappears, prices decay into sentiment.
+- Carter spells out the contradiction directly: "The social value of prediction markets derives from financially incentivizing insiders to divulge confidential information, but this collapses noise trader confidence in the market over time." This is the central regulatory paradox the industry has not resolved.
+- Hanson (quoted by Decrypt and cited by Carter): "If the point of [prediction] markets is to get accurate information on the prices, then you definitely want to allow insiders to trade, even if that discourages other people from betting because that makes the prices more accurate. And that's the priority."
+- Prediction markets are the next stage in the history of expression (print → radio → social media → markets) · only markets demand that speakers bear consequence for being wrong. Frames staked speech as out-trusting cheap talk in an AI-saturated information environment (Abhitej, Bento.fun).
+- Polymarket data shows extreme concentration: 70% of 1.7M addresses lost money; the top 0.04% captured >70% of $3.7B realized profits. The structure funnels retail into informed counterparties, including platform-operated MM desks at Kalshi and Crypto.com (Momin).
+- Play-money prediction markets are accurate only in a low-manipulation regime; this accuracy is self-undermining · the more important they become, the more valuable they are to manipulate (alan).
+- 2025 prediction market volume = ~$63.5B with $200B+ 2026 run rate. Structural tension: sports drive current revenue (83% of Kalshi volume) but valuations price in an information infrastructure future that hasn't arrived (Kaviish).
+- "Information Vectors" thesis: binary contracts fragment liquidity and flatten beliefs into 1-bit structures; achieving 8-bit resolution requires 256 separate markets. Proposal: treat beliefs as vectors over probability distributions on a shared liquidity surface; reward variance compression (entropy reduction), not just final outcome correctness (functionSPACE).
+- Probability layers thesis: prediction markets are a proof-of-concept for a broader shift. Three layers beyond trading: attention markets (price content virality), credibility markets (trust as continuously updated score), demand markets (consumer intent before production) (Aggie).
+- TAM should include the supply side: as the cost of producing real-time probability estimates collapses, the addressable market extends beyond trading volume to every decision that benefits from better forecasts. Liquidity formation runs entertainment → information → institutional (functionSPACE).
+- LLM-as-updater framing: more tractable than LLM-as-predictor. Distinguish cold prediction (no prior context) from updating (revising existing estimates as new info arrives) · implies AI tools deployed *alongside* human traders rather than replacing them (OddChain).
+- OddChain detail (the MIT/Berkeley/Seoul/Kalshi "Market-Conditioned Prompting" paper): 856 Kalshi earnings-call mention market contracts spanning 50 companies and 70 earnings events; LLM (GPT 5.1) given up to 100 news articles + prior transcripts. *Without* MCP framing, the LLM underperformed the market baseline. MixMCP (70% market, 30% MCP) improved Brier from 0.1402 → 0.1392 and accuracy from 79.8% → 80.3% · statistically tiny (~4 extra correct predictions out of 856). The 50–60% probability band is where MCP wins most often (17/30 cases; 5/8 in 60–70% band).
+- "Discovery vs Betrayal" framework for insider trading: in distributed-truth markets like elections, insiders sharpen the signal because no one holds the full answer; in concentrated-truth markets like earnings, insiders monetize sealed results rather than synthesize public fragments. The real question is what kind of asymmetry a market can absorb (Dougie).
+- Tetlock's Superforecasting framework was operationalized at scale by Polymarket · converting crowd forecasting into a liquid financial market (Ahnianchykau).
+- Prediction markets sometimes *underperform* simple baselines: Polymarket forecasts for weekly influenza hospitalizations were "dominated by the FluSight ensemble," and monthly measles forecasts were "outperformed by simple statistical baselines." Diagnosed failure modes: "placement of probability mass on impossible outcomes (e.g., decreasing values in cumulative forecasts)" and low trading volume. The best ensemble combination "puts zero weight on the markets" (Dudley & Magdaleno, May 2026).
+- Game-theoretic foundation: truth-telling is dominant strategy through incentive compatibility; LMSR works as a proper scoring rule (Baheet).
+- Self-resolving prediction markets can work for unverifiable outcomes · Srinivasan/Karger/Chen 2023 prove it is a "perfect Bayesian equilibrium (PBE) for all agents to report truthfully" when payoff is negative cross-entropy vs a reference agent who observes the full market history. Critically, this design works for verifiable AND unverifiable outcomes.
+- Nielsen-moment thesis: coordination value > accuracy. Avci's full argument: Nielsen's authority didn't come from methodological correctness · diary-based sampling was known to be flawed · but from being the shared reference point. Opting out became "professional exile." Billboard's pre-1991 charts missed entire genres (hip-hop, country) because suburban record stores dominated the sample. SoundScan revealed albums debuting at #1 that never cracked the top 40. Implication: once Polymarket/Golden Globes and Kalshi/CNN partnerships lock in, displacement is nearly impossible regardless of methodological superiority.
+- Combinatorial prediction markets · Powell, Hanson, Laskey, Twardy (SUM 2013) · extend the standard model to conditional events ("A if B") and Boolean combinations. Their DAGGRE experimental study used a murder-mystery scenario with a Bayesian network providing gold-standard probabilities. Theory: "the greater expressivity of combinatorial prediction markets should improve accuracy by capturing dependencies among related questions."
+- Trepa's orthogonal precision multiplier rewards forecasts decorrelated from consensus, addressing Keynesian-beauty-contest equilibrium where private information gets underweighted (Blanco, Chung, Meka).
+- Vitalik's umbrella framing: info finance is "correct by construction" · start from a fact you want to know, then deliberately design a market to optimally elicit it. Prediction markets are a three-sided market: bettors predict, readers consume, market outputs predictions as a public good. AI is the unlock: "we could potentially get reasonably high-quality info elicited even on markets with $10 of volume."
+- Hanson's "distilled human judgement" mechanism: subsidize prediction markets that predict what an expensive trusted human process *would* say if invoked; only invoke that process 0.01% of the time. The market becomes a credibly neutral fast/cheap "distilled version" of the costly mechanism.
+
+## Notable Quotes
+
+> "Roughly 3% of accounts drive most price discovery: their trades anticipate future prices, respond to news immediately, and improve calibration across a market's lifecycle."
+> — *Gomez-Cram, Guo, Jensen, Kung, "Prediction Market Accuracy: Crowd Wisdom Or Informed Minority?"*
+
+> "If the point of [prediction] markets is to get accurate information on the prices, then you definitely want to allow insiders to trade, even if that discourages other people from betting because that makes the prices more accurate. And that's the priority."
+> — *Robin Hanson (in Decrypt, cited by Nic Carter)*
+
+> "The social value of prediction markets derives from financially incentivizing insiders to divulge confidential information, but this collapses noise trader confidence in the market over time."
+> — *Nic Carter, *Prediction Markets Are Not Good Markets (Yet)**
+
+> "Insiders sharpen the signal because no one holds the full answer; in concentrated-truth markets like earnings, insiders monetize sealed results rather than synthesize public fragments."
+> — *Dougie, "Discovery and Betrayal"*
+
+> "Achieving 8-bit resolution requires 256 separate markets."
+> — *functionSPACE, "Information Vectors"*
+
+> "Info finance is that, but correct by construction. Similar to the concept of correct-by-construction in software engineering, info finance is a discipline where you (i) start from a fact that you want to know, and then (ii) deliberately design a market to optimally elicit that information from market participants."
+> — *Vitalik Buterin, *From Prediction Markets to Info Finance**
+
+> "Nielsen provided coordination rather than truth, and coordination is harder to displace than accuracy because coordination compounds"
+> — *the more people use a system, the more costly it becomes to use something else." · Mehmet Avci, *The Nielsen Moment for Prediction Markets**
+
+## Where It Matters
+
+Information aggregation is the *core thesis* every PM platform sells · but the Gomez-Cram paper recasts the story: PMs work because of a sharp informed minority, not crowds. That changes platform design (do you cultivate sharps or retail? both, but for different reasons), surveillance (insider flow is constitutive, not deviant), and product framing (don't sell "wisdom of crowds" · sell "informed pricing under skin-in-the-game"). It also reshapes the manipulation conversation: real-money markets resist manipulation precisely because informed traders profit from correcting it. For Dekant's distribution-market thesis, the implication is that you want the curve-drawing primitive to *reward* the informed minority with a richer surface than a binary, so their information actually transmits at higher resolution.
+
+## Related Concepts
+
+- **Price discovery** · the surface; information aggregation is the underlying process
+- **Wisdom of crowds** · the folk theory; PM data partly debunks it (informed minority hypothesis)
+- **Forecasting accuracy** · the testable consequence
+- **Adverse selection / Insider trading** · the dark side of informed flow that platforms must manage
+- **Distribution markets** · argued by Tide/functionSPACE as the higher-resolution form of aggregation
+- **Info finance** · Vitalik's framing of PMs as one app inside a wider information-pricing stack
+- **Probability infrastructure** · Aggie's framing of the embedded endgame
+- **Keynesian beauty contest** · the failure mode where private info gets underweighted
+
+## Sources
+
+- [Orthogonal Precision in Trepa: A Tunable Second-Order Oracle for High-Frequency Forecasting](https://github.com/TrepaOrg/trepa-research/blob/main/Trepa_Orthogonal_Precision_20260513_Blanco%26Chung%26Meka.pdf) — Ilich Blanco, Jong-Chan Chung, Leon Meka · May 13, 2026 [FULL_READ]
+- [Prediction Markets Underperform Simple Baselines For Infectious Disease Forecasting](https://arxiv.org/abs/2605.11220) — Carson Dudley, Reiden Magdaleno · May 11, 2026 [FULL_READ · abstract]
+- [Prediction Markets Have An Inescapable Insider Trading Problem](https://x.com/nic_carter/status/2048123008200724599) — Nic Carter · Apr 26, 2026 [FULL_READ]
+- [Predictions Are The New Expression](https://x.com/abhitejxyz/status/2047353485700825546) — Abhitej · Apr 24, 2026 [FULL_READ]
+- [Polls Are Dead. Long Live Prediction Markets.](https://x.com/CalBlockchain/status/2047460674532790499) — Blockchain at Berkeley · Apr 23, 2026 [FULL_READ]
+- [The Prediction Market Epidemic: Who's Actually Winning](https://x.com/mominsaqib/status/2046551020231385529) — Momin · Apr 21, 2026 [FULL_READ]
+- [When Prediction Markets Need Stake](https://x.com/alanwu/status/2044049214393524393) — alan · Apr 14, 2026 [FULL_READ]
+- [The Financialization of Uncertainty](https://x.com/kaviish/status/2041214800731033700) — Kaviish · Apr 6, 2026 [FULL_READ]
+- [Prediction Market Accuracy: Crowd Wisdom Or Informed Minority?](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6617059) — Gomez-Cram, Guo, Jensen, Kung · Apr 1, 2026 [FULL_READ]
+- [How to Use Prediction Markets as a High Quality Info Source](https://uncover.substack.com/p/how-to-use-prediction-markets-as) — Isar Bhattacharjee · Mar 30, 2026 [FULL_READ]
+- [The Probability Layers Are Coming](https://x.com/BlondiePredicts/status/2038595927225622569) — Aggie · Mar 30, 2026 [FULL_READ]
+- [Information as Supply](https://x.com/functionspaceHQ/article/2035959494728176075) — functionSPACE · Mar 23, 2026 [FULL_READ]
+- [Can LLMs Beat the Market?](https://www.oddchain.com/p/can-llms-beat-the-market) — OddChain · Mar 19, 2026 [FULL_READ]
+- [Discovery and Betrayal: Insiders in Prediction Markets](https://x.com/DougieDeLuca/article/2033910178341413105) — Dougie · Mar 18, 2026 [FULL_READ]
+- [The Book That Predicted Polymarket](https://x.com/mikita_crypto/article/2029939210350645729) — Mikita Ahnianchykau · Mar 6, 2026 [FULL_READ]
+- [Ahead of the Headlines: Prediction Markets and the Collective Mind](https://jprz1321.substack.com/p/ahead-of-the-headlines-prediction) — JP · Feb 25, 2026 [FULL_READ]
+- [Polymarket Is Not a Casino. Why Prediction Markets Are Finance, Not Gambling](https://x.com/13_niakris/article/2025614474263093627) — Niakris · Feb 23, 2026 [FULL_READ]
+- [The Truth Machine Era Is Here](https://x.com/dgt10011/status/2024228182178595247) — Jeff Park · Feb 19, 2026 [FULL_READ]
+- [Prediction Markets are the Agentic Bazaar](https://x.com/benfielding/article/2023130119708242317) — Ben Fielding · Feb 16, 2026 [FULL_READ]
+- [Thoughts on the Law of Insider Trading and Prediction Markets](https://x.com/dbarabander/article/2019769802735178236) — Daniel Barabander · Feb 6, 2026 [FULL_READ]
+- [Prediction Markets Don't Bend Reality](https://fiftycentdollars.substack.com/p/prediction-markets-dont-bend-reality) — Adhi Rajaprabhakaran · Feb 3, 2026 [FULL_READ]
+- [What If We're Capturing the Wrong Signal?](https://x.com/TideMarkets/article/2016912289941827801) — Jo · Jan 29, 2026 [FULL_READ]
+- [Prediction Markets as an Asset Class](https://x.com/akshayraj_v0/article/2016909929920233802) — Akshay · Jan 29, 2026 [FULL_READ]
+- [The Option Value of Waiting in Prediction Markets](https://x.com/0xnagu/article/2016620973391564951) — 0xnagu · Jan 28, 2026 [FULL_READ]
+- [Information Vectors: An Intro to Composable Beliefs](https://x.com/functionspaceHQ/article/2014809461647671570) — functionSPACE · Jan 24, 2026 [FULL_READ]
+- [The Nielsen Moment for Prediction Markets](https://reachavci.substack.com/p/the-nielsen-moment-for-prediction) — Mehmet Avci · Jan 12, 2026 [FULL_READ]
+- [Manifesto: Make Precision Pay](https://x.com/TideMarkets/article/2008508916616098086) — Tide · Jan 6, 2026 [FULL_READ]
+- [Prediction Markets · Everything You Need to Know](https://a16zcrypto.com/posts/podcast/prediction-markets-explained/) — Sonal Chokshi, Alex Tabarrok, Scott Kominers · Sep 25, 2025 [FULL_READ · podcast notes]
+- [The Game Theory Behind Prediction Markets](https://x.com/Baheet_/status/1965758390430208066) — Baheet · Sep 10, 2025 [FULL_READ]
+- [How Manipulable Are Prediction Markets?](https://arxiv.org/abs/2503.03312) — Itzhak Rasooly, Roberto Rozzi · Mar 5, 2025 [FULL_READ · abstract]
+- [The Definitive Guide to Prediction Markets](https://docsend.com/view/atcc6k258s4umq6s) — Four Pillars · Jan 15, 2025 [FULL_READ]
+- [Designing Markets for Prediction](https://bpb-us-e1.wpmucdn.com/sites.harvard.edu/dist/b/845/files/2025/01/aim10.pdf) — Yiling Chen, David M. Pennock · Jan 14, 2025 [FULL_READ · PDF]
+- [Prediction Markets and Beyond](https://a16zcrypto.com/posts/podcast/prediction-markets-information-aggregation-mechanisms/) — Tabarrok, Kominers, Chokshi · Nov 21, 2024 [FULL_READ · podcast notes]
+- [From Prediction Markets to Info Finance](https://vitalik.eth.limo/general/2024/11/09/infofinance.html) — Vitalik Buterin · Nov 9, 2024 [FULL_READ]
+- [Crypto Prediction Markets](https://dirtroads.substack.com/p/63-crypto-prediction-markets) — Luca Prosperi · Oct 11, 2024 [FULL_READ]
+- [The Art of Forecasting](https://paragraph.com/@filarm/the-art-of-forecasting) — fil · Sep 30, 2024 [FULL_READ]
+- [Deep Dive #8 | Decentralized Prediction Markets](https://ampbura.substack.com/p/deep-dive-8-decentralized-prediction) — Amp Burapachaisri · Feb 23, 2024 [FULL_READ]
+- [Self-Resolving Prediction Markets for Unverifiable Outcomes](https://arxiv.org/abs/2306.04305) — Srinivasan, Karger, Chen · Jun 7, 2023 [FULL_READ · abstract]
+- [Should Prediction Markets Be Charities?](https://www.overcomingbias.com/p/should_predictihtml) — Peter McCluskey · Dec 11, 2006 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/information-asymmetry.md b/.qoder/plans/pm-plan/theory_concepts/information-asymmetry.md
new file mode 100644
index 0000000000..719bece596
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/information-asymmetry.md
@@ -0,0 +1,73 @@
+# Information Asymmetry
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Information Asymmetry](https://www.pmatlas.xyz/concepts/information-asymmetry)
+
+---
+
+## Definition
+
+When some traders possess materially better knowledge about likely outcomes than other participants. The cause of both *insider trading* (the legal problem) and *adverse selection* (the market-making problem). In PMs, asymmetry is both the engine of price discovery and the largest threat to retail participation.
+
+## Key Insights
+
+- PMs make information legible in a way stocks/options do not. When someone bets big on an attack on Maduro, the bet's *meaning* is unambiguous. Andrew Courtney argues this legibility is a feature even when it surfaces uncomfortable national-security implications.
+- Polymarket data: 70% of 1.7M addresses lost money; top 0.04% captured >70% of $3.7B realized profits. The structure predictably funnels retail into informed counterparties · including platform-operated MM desks at Kalshi and Crypto.com. PMs are useful as information layer but poor retail trading products (Momin).
+- The investing/gambling line is about +EV, not the game itself. PMs are structurally different from other derivatives because they're precise (binary payoffs create clean basis risk to truth) and have finite expiry. Fears about insider trading are overblown · liquidity in obscure asymmetric markets will be negligible (Jeff Park). Closes with media-criticism: prestige outlets attack PMs because they threaten institutional control over truth.
+- Binary PMs as consumer-wrapped binary options. Minsky's "vega wedge" · a structural overcharge (magnitude varies by asset; the specific ~4.8%/7-20% figures are unverified) · is the structural overcharge binary hedgers pay when they replicate via vanilla options. PMs can undercut this in deep categories but still lack a shared Black-Scholes pricing language (0xturbanurban).
+- Information contagion: insider trades on Polymarket may have leaked into regulated oil and stock futures markets before Trump's March 2026 Iran announcement. Quant funds extract signal from pseudonymous crypto trades and act on it in KYC venues · without breaking laws. Structural gap where information flows freely across platforms even with different regulatory regimes (Rajiv Sethi).
+- "From Iran to Taylor Swift": screen of 93,000 Polymarket markets flags traders with 69.9% win rate (>60σ above chance), estimating $143M in anomalous profits. Documents specific cases (geopolitics, celebrity announcements). Proposes regulatory framework: platform-level registration, contract-level restrictions on high-risk categories, extended misappropriation doctrine (Joshua Mitts, Moran Ofir · Harvard Corporate Governance).
+- "Discovery vs Betrayal" framework: distributed-truth markets (elections) · insiders sharpen signal because no one holds the full answer; concentrated-truth markets (earnings) · insiders monetize sealed results rather than synthesize public fragments. Real question: what kind of asymmetry can a market absorb without losing the participation that makes the signal useful (Dougie).
+- "Trading on violence": wallets profited $1.2M on timing of US strikes on Iran; trades linked to classified intelligence. Kalshi's KYC-based surveillance vs Polymarket's pseudonymous blockchain create different enforcement challenges. Platforms should reconsider contract offerings before regulators act (Rajiv Sethi).
+- Two structural problems blocking PMs from being transformative: corporate hedging is impractical due to fragmentation/basis risk; insider trading undermines retail participation. Without structural reforms PMs remain a sports betting product (Nic Carter).
+- AI underperforms in PMs because edge is embodied/local · monitoring flights, calling embassies. AI remains constrained from this domain (Avci).
+- Three manipulation vectors taxonomy: information asymmetry (insiders know outcome), reflexivity (signal influences outcome), social coercion (participants can cause outcome). A market's manipulation profile determines whether you're trading on information edge, narrative momentum, or ability to cause the outcome (aaronjmars).
+- Mitts & Ofir full-read: six wallets earned $1.2M total profits on the Iran strike (Feb 28, 2026); the "Magamyman" account placed its first trade 71 minutes before the news broke when the market implied just 17% probability, banking $553K. "Burdensome-Mix" earned $485K from a $38.5K bet on Maduro's capture. Universe screened: 93,000+ markets, 50,000 wallets, Feb 2024–Feb 2026. Five composite-score signals: cross-sectional bet size, within-trader bet size, profitability, pre-event timing, directional concentration. 210,718 suspicious wallet-market pairs identified.
+- Mitts & Ofir on legal gaps: (1) "classical" and "misappropriation" insider-trading theories both require trading in *securities* · most PM contracts are commodities so SEC anti-fraud doesn't apply. (2) CFTC Rule 180.1 may not yield a Cady-Roberts duty to disclose, and "trading on lawfully obtained commercial information without deception remains legal." (3) Wire fraud requires the information to have commercial value to the *source* · for military operations or social-relationship knowledge that bar isn't met. (4) Structural asymmetry: Kalshi is a CFTC DCM with surveillance obligations; Polymarket is in legal gray zone, creating incentive to insider-trade on Polymarket.
+- Mitts & Ofir proposed response · three elements: (1) platform-level mandatory registration/surveillance, even for non-US-incorporated operators that offer to US persons, (2) contract-level restrictions on government-data/military/corporate-event categories, (3) misappropriation doctrine extended by CFTC rulemaking to cover non-securities confidentiality duties (so a soldier who trades on classified plans breaches duty to government even when contracts aren't securities). The Israeli IDF case is the closest existing precedent under Israeli law.
+- Mitts & Ofir cite Feb 2026 Fed Reserve Board study: Kalshi macro markets on CPI/GDP releases achieve accuracy "that rivals or exceeds professional forecasts, with rich intraday dynamics that daily data entirely miss."
+- Sethi "Information Contagion" full-read: March 24, 2026 · unusually large volume spike in oil and stock futures 15 minutes *before* Trump's Iran-negotiations announcement on social media. The trade hypothesis: not an insider acting directly on regulated KYC venues (too risky) but a quant fund extracting signal from prior Polymarket wallet activity (a wallet that had profited on the original strike timing then placed bets implying ceasefire). Conclusion: "if insiders believe that they can trade without fear of detection on Polymarket, then more conventional investment vehicles operating on a larger scale will be on the lookout for signals of insider activity. They can extract and exploit information coming from insiders without themselves facing legal jeopardy."
+- Sethi "Trading on Violence" full-read: Kalshi has conducted "more than 200 investigations into insider trading, with a dozen cases currently active." Kalshi explicitly bars war/assassination markets because "as a federally regulated prediction market, we are required and feel it is important not to enable direct profiting from war, assassination, terrorism, or other violent outcomes." Polymarket's contract on "Khamenei out as Supreme Leader" resolved Yes (binary); Kalshi's resolved at last-trade-price pre-death. Cross-platform arbitrageurs lost money on the resolution-method gap.
+- Sethi/Guillory/Zimmermann (cited): "Markets that resolve on whether someone stays in power, speaks publicly, shows up somewhere, or holds a certain job carry implicit incentives connected to their continued existence. Nearly every such market dealing with control, decision, or public appearance is technically an assassination market."
+- Andrew Courtney "Legibility" full-read: a US soldier allegedly traded the plans to attack Maduro on Polymarket, "could have leaked intel and put US forces at risk... risked ~$33,000 and profited over $400,000." Stocks = low legibility ("more buyers than sellers" is the canonical Wall Street non-explanation); options = medium legibility (needs pricing expertise); PMs = high legibility (everyone can read the bet). "Tail wags the dog" attack vector: buy a thin geopolitical PM contract to manipulate large oil/equity positions in liquid markets.
+- Nic Carter "Prediction Markets Are Not Good Markets (Yet)" full-read: structural argument is twofold. (1) PMs are bad for hedging because "the more useful the market is to the hedger, the less liquid it is likely to be" · a biotech firm hedging a specific FDA decision can only buy a generic Trump-wins contract with imperfect correlation. Speculators don't want to take the other side of FDA-approves-drug-XYZ. (2) Insider trading is "both the point and the death" of PMs · the social value comes from incentivizing insiders to reveal confidential info, but that erodes noise trader confidence. Hanson quoted: "you definitely want to allow insiders to trade... that's the priority."
+
+## Notable Quotes
+
+> "When someone bets big on an attack on Maduro, everyone immediately knows what the bet is about, unlike a stock price spike that could mean anything."
+> — *Andrew Courtney, "Legibility"*
+
+> "Six newly created Polymarket wallets collectively earned approximately $1.2 million" on the US-Israel Iran strike contract."
+> — *Mitts & Ofir*
+
+> "Information flows freely across platforms even when regulatory frameworks differ."
+> — *Rajiv Sethi, "Information Contagion"*
+
+## Where It Matters
+
+Information asymmetry is the most operationally important concept in the PM regulatory debate. The Mitts/Ofir Harvard piece is the empirical heart of the *Polymarket has an insider problem* argument · and Sethi's contagion piece extends it to regulated venues, removing the "it's only crypto" defense. Platforms have three live decisions: (1) KYC vs pseudonymity, (2) which contract categories to list (assassinations, earnings, classified geopolitics), (3) how to disclose informed-trader concentration. For Dekant, the distribution-market design partly inherits these problems · but the higher-dimensional nature of a curve may make insider edge harder to monetize cleanly (an insider knows a *point*, not the full distribution shape).
+
+## Related Concepts
+
+- **Insider trading** · the legal/ethical surface
+- **Adverse selection / Toxic flow** · the market-microstructure surface
+- **Price discovery** · the upside of asymmetry
+- **Market surveillance** · the platform's response
+- **Reflexivity** · companion manipulation vector
+- **Legibility** · why PM asymmetry is harder to hide than equity asymmetry
+- **Hedging** · the legitimate use case for asymmetric information
+
+## Sources
+
+- [Legibility](https://whirligigbear.substack.com/p/legibility) — Andrew Courtney · Apr 27, 2026 [FULL_READ]
+- [The Prediction Market Epidemic: Who's Actually Winning](https://x.com/mominsaqib/status/2046551020231385529) — Momin · Apr 21, 2026 [FULL_READ]
+- [What Most People Get Wrong About Prediction Markets](https://x.com/dgt10011/status/2045952603301851173) — Jeff Park · Apr 20, 2026 [FULL_READ]
+- [The Bane Of Binaries: What Prediction Markets Are Missing](https://x.com/0xturbanurban/status/2044433520806830101) — 0xturbanurban · Apr 15, 2026 [FULL_READ]
+- [Information Contagion](https://rajivsethi.substack.com/p/information-contagion) — Rajiv Sethi · Mar 31, 2026 [FULL_READ]
+- [From Iran to Taylor Swift: Informed Trading in Prediction Markets](https://corpgov.law.harvard.edu/2026/03/25/from-iran-to-taylor-swift-informed-trading-in-prediction-markets/) — Joshua Mitts, Moran Ofir · Mar 25, 2026 [FULL_READ]
+- [Discovery and Betrayal: Insiders in Prediction Markets](https://x.com/DougieDeLuca/article/2033910178341413105) — Dougie · Mar 18, 2026 [FULL_READ]
+- [Trading on Violence](https://rajivsethi.substack.com/p/trading-on-violence) — Rajiv Sethi · Mar 2, 2026 [FULL_READ]
+- [Prediction Markets Are Not Good Markets (Yet)](https://murmurationstwo.substack.com/p/prediction-markets-are-not-good-markets) — Nic Carter · Feb 21, 2026 [FULL_READ]
+- [Is AI Any Good at Predicting?](https://reachavci.substack.com/p/is-ai-any-good-at-predicting) — Avci · Feb 2, 2026 [FULL_READ]
+- [Risks of Prediction Markets](https://x.com/aaronjmars/article/1993094222195400863) — aaronjmars · Nov 25, 2025 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/insider-trading.md b/.qoder/plans/pm-plan/theory_concepts/insider-trading.md
new file mode 100644
index 0000000000..f0b64163ee
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/insider-trading.md
@@ -0,0 +1,102 @@
+# Insider Trading
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Insider Trading](https://www.pmatlas.xyz/concepts/insider-trading)
+
+---
+
+## Definition
+
+**Quick definition.** Trading on material non-public information (MNPI) obtained through breach of a confidentiality duty or implied promise. In prediction markets, it arises when participants trade on privileged access to information about upcoming events, creating adverse selection for everyone else and exposing the platform to legal and reputational risk.
+
+## Key Insights
+
+- Mitts & Ofir (Harvard Corporate Governance forum): screened **93,000 Polymarket markets across 50,000 unique wallets, Feb 2024–Feb 2026**. Flagged 210,718 wallet-market pairs with a **69.9% win rate** · more than 60 standard deviations above chance under a permutation test. Aggregate anomalous profit ≈ **$143M**, an explicit lower bound (buy-side only, excludes positions <$500, can't catch traders who deliberately stay small). Composite score combines five signals: cross-sectional bet size, within-trader bet size, profitability, pre-event timing, directional concentration.
+- Mitts & Ofir concrete cases: Feb 28, 2026 US-Israeli strike on Iran · six newly-created wallets earned ≈$1.2M, "Magamyman" placed first trade **71 minutes before** the news at 17% implied probability and resolved with $553k. The Maduro capture: "Burdensome-Mix" earned ≈$485k from a $38,500 investment, biggest trades placed hours before public announcement.
+- Mitts & Ofir legal analysis: three reasons existing law fails · (1) Section 10(b)/Rule 10b-5 classical and misappropriation theories both require *securities*, and most PM contracts are commodities; (2) CFTC Rule 180.1, the analog to 10b-5, doesn't clearly carry a Cady-Roberts disclose-or-abstain duty, and trading on "lawfully obtained" commercial information without deception remains legal; (3) federal wire fraud (United States v. Chastain, 2025) requires that the misappropriated information have commercial value to the original source · military and personal information often has none.
+- Mitts & Ofir's proposed three-layer regulatory response: (1) platform-level registration/surveillance for any operator offering contracts to US persons, leveraging CFTC extraterritorial jurisdiction; (2) contract-level targeted rules for high-risk categories (government data releases, military/diplomatic operations, corporate material events); (3) liability theory directed at informed traders themselves on decentralized platforms · extending Rule 180.1 misappropriation doctrine through CFTC rulemaking to cover non-securities duties of confidentiality owed by soldiers, government employees, and corporate insiders.
+- Mitts & Ofir on H.R. 7004 (Public Integrity in Financial Prediction Markets Act of 2026, Rep. Torres): wouldn't have deterred any of the documented cases · Maduro/Iran trades involved military personnel covered by separate statutes, the IDF case involved a non-US Israeli reservist, the OpenAI browser case involved corporate trade secrets, the Taylor Swift case involved personal social knowledge.
+- Rajiv Sethi: wallets profited **$1.2M** on the timing of US strikes on Iran; pattern likely involves classified intelligence. KYC-based surveillance (Kalshi) vs. pseudonymous blockchain (Polymarket) creates fundamentally different enforcement surfaces. Kalshi has conducted **>200 investigations into insider trading, with a dozen cases currently active**.
+- "Information Contagion" (Sethi): on March 24, 2026 at 6:50am ET, an unusual spike in oil/stock futures trading volume preceded Trump's 7:05am ET social-media announcement about Iran negotiations by 15 minutes. "Whoever entered large short positions in oil and long positions in the stock index made millions in minutes." Sethi's point: insider trades on Polymarket leaked into regulated KYC venues · quant funds extracted the signal from pseudonymous crypto trades and acted on it in regulated derivatives markets, without (technically) breaking any law.
+- DOJ case against MSgt Gannon Ken Van Dyke: $400k on Polymarket on the Maduro raid. Israeli reservist arrests preceded similar pattern.
+- Courtney's **legibility** thesis: a US soldier traded ~$33k for $400k+ profit on Polymarket on the Maduro plans · tiny in financial-markets terms, but legibility makes the information leak immediate and public. Unlike a stock spike that the public can't read, a Polymarket bet on "US strikes Maduro" tells the world exactly what someone knows. This makes PMs uniquely dangerous as intelligence-leak vectors.
+- Nic Carter: insider trading is **structural, not a bug**. Robin Hanson explicitly said in a 2024 Decrypt interview: "If the point of [prediction] markets is to get accurate information on the prices, then you definitely want to allow insiders to trade, even if that discourages other people from betting because that makes the prices more accurate. And that's the priority." Kalshi bans it in ToS; Polymarket has only a catch-all law-violation clause.
+- Carter's "rogues' gallery" of suspected insider trading: 2025 Nobel Peace Prize, Google's 2025 Year in Search Rankings, Maduro's capture, Israel's 2024 strikes on Iran, OpenAI GPT-5.2 release, UFC fight results, Taylor Swift engagement. Polymarket's pseudonymity + on-chain visibility is "the worst combination": you can sort of cover your tracks while every transaction is publicly auditable.
+- Daniel Barabander (legal analysis): no distinct "insider trading statute" applies · it's governed by **existing fraud law**, requiring a deceptive breach of an implied promise. Many PM insiders (e.g., a political candidate betting on his own race) may owe no duty at all.
+- Jay Sykes (Congressional Research Service): walks through SEC Rule 10b-5, CFTC Rule 180.1, the STOCK Act, Title 18; surveys four pending bills in the 119th Congress that would close gaps. Notes the CFTC's February 2026 advisory on two Kalshi enforcement actions.
+- Michael Li's CFTC comment: four-dimensional framework · (1) **information structure** (legibility, concentration of decision-makers, institutional insulation); (2) **manipulation economics** (whether the contract creates incentives to *cause* the outcome and at what cost); (3) **social utility** of the price signal (policy/economic > informational > entertainment > hyper-specific); (4) **repugnance as a market-design constraint** (Roth's category · markets whose existence is intolerable regardless of efficiency). Li separates "insider trading" into three patterns: information asymmetry, duty-breach trading, outcome influence · calibrated remedies require holding them apart.
+- Li on Coinbase's "mention market": on October 30, 2025, Brian Armstrong told investors he'd been "a little distracted" tracking a Polymarket contract on what he'd say, then deliberately rattled off the words bettors had wagered on. The person who controlled the outcome publicly acknowledged responding to the price · pure manipulation economics.
+- Li on the P2P.me case: a Coinbase-Ventures-backed firm used Polymarket to bet on its own fundraising round, netting <$15k. "The same actor controlled the outcome of the underlying fundraise and traded on a market resolving on that outcome."
+- Hariharan's three-layer solution: platform detection + position limits scaled to account size; market design (dynamic spread widening, MM insurance pools); legal frameworks (compliance, CFTC guidance). AlphaRaccoon predicted 22/23 Google Year-in-Search rankings (>$1M) and earlier $150k on Gemini 3.0 release date.
+- Dougie's discovery-vs-betrayal framework: in distributed-truth markets like elections, informed traders *sharpen* the signal; in concentrated-truth markets (earnings, planned events) insiders monetize a sealed result.
+- Seva Gunitsky ("Priced to Kill"): prediction markets create three dangers · **military insider trading at scale, manipulation of outcome resolution, propaganda advantages for autocratic regimes**. Concrete cases: the Israeli Air Force major told investigators "the entire squadron is on Polymarket. The entire air force is betting." A war journalist (Fabian) was bribed and threatened by "No" bettors trying to get him to change his reporting on whether an Iranian missile struck Israeli soil. A "hair dryer" attack on a Paris weather sensor near Charles de Gaulle airport let a trader collect $14k on April 6 and $20k on April 15 betting on abnormal temperatures · France's meteo agency filed a criminal complaint, Polymarket switched data sources but did not refund the payouts.
+- Gunitsky on regime-power consolidation: "Prediction market accuracy relies not on the crowd but on a small minority of informed traders. If only three percent of accounts drive price discovery, you don't need to outbid millions of independent signals, only to co-opt or suppress some fraction of those three percent." Donald Trump Jr. is a strategic adviser to Kalshi and an investor in Polymarket; "the only person indicted so far is a sergeant who used his personal email."
+- aaron ("On War Markets"): proposed guardrails include delayed settlement and conflict-of-interest screens. Kalshi avoids conflict markets; Polymarket lists them freely.
+- Robin Hanson defends a light-touch regime: prediction markets deserve the same regulatory treatment as other information institutions (journalism, academia), approved by default and restricted only on clear evidence of specific harm. All info institutions · gossip, academia, journalism · can induce people to reveal secrets, waste time, make misleading contributions, change the world for favorable treatment; PMs should be treated similarly, not specially.
+- Noah Litvin: every standard objection (gambling, insider trading, manipulation, slot-machine durations) has a larger-scale analog already accepted in traditional finance · the $950M oil ceasefire trades on CME, LIBOR, accredited-investor rules.
+- Jeff Park: insider-trading fears are overblown for obscure markets because liquidity will be negligible in asymmetric questions.
+
+## Notable Quotes
+
+> "Prediction markets have an inescapable insider trading problem."
+> — *Nic Carter*
+
+> "Information flows freely across platforms even when regulatory frameworks differ."
+> — *Rajiv Sethi, "Information Contagion"*
+
+> "Insider trading in prediction markets is governed by existing fraud law rather than a distinct insider trading statute."
+> — *Daniel Barabander*
+
+> "The real question is not whether insiders should be allowed but what kind of informational asymmetry a market can absorb without losing the participation and trust that make the signal useful."
+> — *Dougie*
+
+> "The entire squadron is on Polymarket. The entire air force is betting."
+> — *Israeli Air Force major to investigators, quoted in Gunitsky*
+
+> "These profits represent transfers from uninformed retail participants to those with access to material non-public information"
+> — *a regressive outcome that undermines the democratic appeal of prediction markets as venues where ordinary forecasters can profit from accurate beliefs." · Mitts & Ofir*
+
+> "If the point of [prediction] markets is to get accurate information on the prices, then you definitely want to allow insiders to trade, even if that discourages other people from betting because that makes the prices more accurate. And that's the priority."
+> — *Robin Hanson, 2024 Decrypt interview, quoted by Nic Carter*
+
+> "The entire prediction market edifice, with its blockchain infrastructure, smart contracts, and information-aggregation theory was defeated by a man with a hair dryer."
+> — *Seva Gunitsky*
+
+## Where It Matters
+
+Insider trading is simultaneously the largest legal/regulatory risk facing the asset class and a feature without which prices wouldn't be accurate. Every platform must take a position on the spectrum from Polymarket (pseudonymous, permissive) to Kalshi (KYC, restrictive). The unsolved question is whether jurisdictions converge on contract-level restrictions (no military, no earnings, no personal info), platform-level surveillance, or legal-doctrine extensions (misappropriation theory) · and how those interact with cross-platform information contagion into KYC venues.
+
+## Related Concepts
+
+- **Adverse selection** · the economic frame insiders create for other traders.
+- **Information asymmetry** · the upstream cause.
+- **Market manipulation** · the active-trading flip side.
+- **Market surveillance** · detection mechanism.
+- **Regulatory classification** · determines which insider-trading regime applies.
+- **Federal preemption** · determines whether state or CFTC rules govern.
+- **Reflexivity / endogeneity** · when insider trades themselves influence outcomes (war markets).
+- **Legibility** · why insider trades are obvious in PMs and ambiguous in stocks.
+- **Resolution criteria / oracle design** · bad resolution is one channel of insider abuse.
+
+## Sources
+
+- [Priced to Kill](https://hegemon.substack.com/p/polymarket-is-making-wars-more-dangerous) — Seva Gunitsky · May 13, 2026
+- [Why Prediction Markets Are Hard to Regulate](https://michaellwy.substack.com/p/my-comment-to-cftcs-prediction-markets) — Michael Li · Apr 30, 2026
+- [On Prediction Market Regulation](https://www.overcomingbias.com/p/on-prediction-market-regulation) — Robin Hanson · Apr 29, 2026
+- [You Don't Hate Prediction Markets. You Hate Capitalism.](https://x.com/noahlitvin/status/2048527516587966660) — Noah Litvin · Apr 27, 2026
+- [Legibility](https://whirligigbear.substack.com/p/legibility) — Andrew Courtney · Apr 27, 2026
+- [Prediction Markets Have An Inescapable Insider Trading Problem](https://x.com/nic_carter/status/2048123008200724599) — Nic Carter · Apr 26, 2026
+- [What Most People Get Wrong About Prediction Markets](https://x.com/dgt10011/status/2045952603301851173) — Jeff Park · Apr 20, 2026
+- [Are We on the Brink of a Prediction Market Supercycle?](https://x.com/mphrediction/status/2043419029378048164) — mph · Apr 12, 2026
+- [How Prediction Markets Are Blurring the Line Between Trading and Betting](https://www.bloomberg.com/news/articles/2026-04-09/how-prediction-markets-are-blurring-the-line-between-trading-and-betting) — Christopher Beam (Bloomberg) · Apr 9, 2026
+- [Information Contagion](https://rajivsethi.substack.com/p/information-contagion) — Rajiv Sethi · Mar 31, 2026
+- [How to Use Prediction Markets as a High Quality Info Source](https://uncover.substack.com/p/how-to-use-prediction-markets-as) — Isar Bhattacharjee · Mar 30, 2026
+- [From Iran to Taylor Swift: Informed Trading in Prediction Markets](https://corpgov.law.harvard.edu/2026/03/25/from-iran-to-taylor-swift-informed-trading-in-prediction-markets/) — Joshua Mitts, Moran Ofir · Mar 25, 2026
+- [Discovery and Betrayal: Insiders in Prediction Markets](https://x.com/DougieDeLuca/article/2033910178341413105) — Dougie · Mar 18, 2026
+- [Prediction Markets and Insider Trading Law](https://www.congress.gov/crs-product/LSB11406) — Jay B. Sykes (CRS) · Mar 18, 2026
+- [Are Prediction Markets Decaying or Evolving?](https://x.com/_drNeyo/article/2029205512391172364) — Eniola · Mar 4, 2026
+- [Trading on Violence](https://rajivsethi.substack.com/p/trading-on-violence) — Rajiv Sethi · Mar 2, 2026
+- [On War Markets](https://x.com/probaaron/article/2027765452689014822) — aaron · Feb 28, 2026
+- [How to Solve Insider Trading in Prediction Markets](https://www.dopaminemarkets.com/p/how-to-solve-insider-trading-in-prediction) — Shreyas Hariharan · Feb 10, 2026
+- [Thoughts on the Law of Insider Trading and Prediction Markets](https://x.com/dbarabander/article/2019769802735178236) — Daniel Barabander · Feb 6, 2026
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/kelly-criterion.md b/.qoder/plans/pm-plan/theory_concepts/kelly-criterion.md
new file mode 100644
index 0000000000..027c5ec49d
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/kelly-criterion.md
@@ -0,0 +1,50 @@
+# Kelly Criterion
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Kelly Criterion](https://www.pmatlas.xyz/concepts/kelly-criterion)
+
+---
+
+## Definition
+
+**Quick definition.** A formula for optimal bet sizing that maximizes long-run growth by balancing edge against risk of ruin. In prediction markets Kelly is the canonical bet-sizing framework for traders with a perceived edge, but full-Kelly is widely considered too aggressive in practice · fractional Kelly is the working standard.
+
+## Key Insights
+
+- outpxce (seven-figure PM trader): uses **fractional Kelly sizing** as core practice. Trades non-English-media OSINT and Telegram for edge; locks capital as a "bond mule" for small premiums on near-resolution markets. Notes edge has compressed as space matured.
+- Jacob Zhao's AI-agents framework: compares **Kelly criterion vs. fixed-fraction bet sizing**. Argues AI agents will become dominant market participants within two years.
+- Roan: Kelly position sizing is one of three core tools (with fill-quality adverse-selection measurement and probability term structure) for building a repeatable PM edge.
+- Lihong's structural critique: market probabilities are NOT real probabilities. Three reasons they diverge even with rational participants: **favorite-longshot bias from Kelly betting**, risk-premium distortion from market correlation, risk-neutral forward pricing in long-dated contracts. The Kelly mechanism itself biases prices toward favorites and away from longshots because risk-averse Kelly bettors underweight tail outcomes.
+- gemchanger ("Sharpe Ratio Is a Lie"): the full quant-finance toolkit (Deflated Sharpe Ratio, combinatorial purged CV, Black-Litterman, Bayesian regimes, ML) transfers to PMs because **binary resolution eliminates the unobservable noise that obscures strategy quality** elsewhere. PMs are the purest testbed for investment theory; Kelly is the natural sizing rule given clean signal.
+- LMSR has a mathematical identity with the softmax function · bridging quant finance and PM pricing in ways that make Kelly's information-theoretic foundation directly applicable.
+- Implicit across all sources: Kelly is necessary but insufficient. Sizing rules must be paired with **execution-quality controls** (Della Vedova) because being right about edge but bad at fills still loses money.
+
+## Notable Quotes
+
+> "Edge has compressed as space matured."
+> — *outpxce*
+
+> "Prediction markets are the purest testing environment for investment theory because binary resolution eliminates the unobservable noise that obscures strategy quality in traditional finance."
+> — *gemchanger*
+
+## Where It Matters
+
+Kelly is how serious traders translate their probability estimates into capital allocation. Its salience in PMs is unusually high because binary outcomes make the "edge" calculation cleaner than in any other asset class · you can compute realized vs. expected returns trade-by-trade without the residual factor-exposure noise that haunts equities. But Lihong's point matters: if many participants use Kelly, market prices themselves bend toward favorites, which is partial structural explanation for longshot bias.
+
+## Related Concepts
+
+- **Adverse selection** · the binding risk Kelly sizes against.
+- **Forecasting accuracy / calibration** · the inputs Kelly depends on.
+- **Longshot bias** · Lihong argues Kelly *causes* part of this bias.
+- **Market making** · analogous sizing problem from the LP side.
+- **AI agents** · Kelly is one of two leading sizing rules for autonomous traders.
+- **Execution quality** · required complement to Kelly.
+
+## Sources
+
+- [Market Probabilities Are NOT Real Probabilities](https://x.com/Lihong062/status/2051017075758686622) — Lihong · May 3, 2026
+- [Turning Probability into Assets: A Look Ahead at Prediction Market Agents](https://x.com/0xjacobzhao/article/2029229969914904694) — Jacob Zhao · Mar 5, 2026
+- [Your Hedge Fund's Sharpe Ratio Is a Lie. Prediction Markets Are the Only Place It Can't Hide.](https://x.com/gemchange_ltd/article/2026300422483271733) — gemchanger · Feb 25, 2026
+- [Why Prediction Markets Aren't Gambling? (The Math)](https://x.com/RohOnChain/article/2020565633453412751) — Roan · Feb 9, 2026
+- [On Prediction Markets](https://x.com/outpxce/status/2013397772074905950) — outpxce · Jan 20, 2026
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/keynesian-beauty-contest.md b/.qoder/plans/pm-plan/theory_concepts/keynesian-beauty-contest.md
new file mode 100644
index 0000000000..754e454175
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/keynesian-beauty-contest.md
@@ -0,0 +1,65 @@
+# Keynesian Beauty Contest
+
+**Category:** Mechanism Design
+**Source:** [PM Atlas — Keynesian Beauty Contest](https://www.pmatlas.xyz/concepts/keynesian-beauty-contest)
+
+---
+
+## Definition
+
+A dynamic in forecasting contests where participants predict *what others will predict* rather than their true belief, causing private information to be underweighted and forecasts to converge on the public consensus. Named for Keynes's 1936 metaphor of newspaper beauty contests where readers vote not for the prettiest face but for the face they think others will vote for.
+
+## Key Insights
+
+- The Keynesian beauty contest is the failure mode of forecasting-contest design: if rewards depend heavily on *closeness to the consensus*, rational participants will herd to consensus rather than report private signals, and the contest aggregates *less* information than a market would.
+- Blanco, Chung & Meka (May 2026) · *Orthogonal Precision in Trepa* · is the only article on the on-page corpus, but it is foundational. The paper's framing: "Under rank-order tournaments · where rewards depend on relative performance rather than absolute accuracy · the equilibrium can shift from fundamental information revelation to second-order expectations: agents forecast what others will forecast, a dynamic Keynes famously likened to a beauty contest."
+- **Trepa as case study:** "Trepa, a one-minute numerical forecasting game on Solana, operates precisely such a contest. Participants pay a fixed entry fee (1 USDC), submit an estimate of the BTC price 30 seconds hence, and are rewarded based on how close their estimate is to the true outcome · but only if they beat the median error. The combination of a median cutoff, steep accuracy weights (γ = 6), and risk aversion pushes the equilibrium toward herding: optimal predictions anchor on the public consensus and deviate only timidly toward private signals."
+- The protocol mechanics: prize distribution uses a water-filling allocation with accuracy weight `aᵢ = 1/(1+rᵢ)^γ` where `rᵢ = eᵢ/m` and `γ = 6`. A per-winner profit cap C = 100 F (100 USDC gain on 1 USDC entry) prevents extreme concentration. Trepa is "backed by Colosseum and Balaji Srinivasan."
+- **Diagnosis:** Theorem 3.1 in Trepa proves: "For λ = 0 and ρ > 0, the unique symmetric equilibrium satisfies w = 0 (pure herding) whenever the risk-aversion coefficient ρ exceeds a finite threshold. For lower risk aversion, w is positive but small, bounded above by a value that vanishes as the curvature γ of the accuracy weight increases."
+- **Their fix · the orthogonal precision multiplier:** rewards forecasts that are both accurate *and decorrelated from the consensus*. The augmented utility: `Uᵢ = A(eᵢ)·(1 + λDᵢ)` where `Dᵢ = |xᵢ - E[x̄_-i | Iᵢ]| = w|sᵢ - E[Y|sᵢ]|`. "This distance rewards a report only if it deviates from what the rest of the crowd is expected to report, given the same information. It naturally vanishes under pure herding (w = 0) and increases linearly with the weight on private signal."
+- **Phase transition (Theorem 4.1):** "For λ > λc, an interior equilibrium emerges with w∗ > 0 increasing in λ, bounded above by wmax = E[|sᵢ−θ|]/(E[|sᵢ−θ|]+noise) < 1." The protocol "does not need to reward diversity at all times; a minimal incentive is required to overcome the herding barrier created by risk aversion and the median cutoff."
+- **Information gain via mutual information:** Signal Extraction Rate `SER = I(Y_{t+Δ}; x̃_t) - I(Y_{t+Δ}; Y_t)` quantifies what Trepa's consensus reveals about future prices beyond the spot price. Theorem 5.1: "for any λ > λc (i.e., w∗ > 0), SER > 0. That is, the orthogonal precision mechanism extracts predictive information that complements the spot oracle."
+- **Anti-collusion design:** the modified diversity multiplier uses conditional information contribution `∆Iᵢ` thresholded at small τ. Proposition 6.1: "Any coalition of M agents with perfectly correlated reports receives the same total orthogonality bonus as a single independent agent. The marginal benefit of adding another correlated member is zero."
+- **Instability of perfect herding:** "Because ties lose, perfect herding is not an absorbing state. Any agent has an incentive to deviate infinitesimally to break the tie, creating a structural tension that reinforces the need for orthogonal precision as a stabilizing mechanism."
+- The paper proves equilibrium existence via potential game theory: `Φ(x) = -Σᵢϕ(eᵢ) + λΣᵢψ(|xᵢ-x̄_-i|) - (κ/2)Σᵢ(xᵢ-θ)²` is continuously differentiable, concave, with compact strategy set · guaranteeing a pure Nash equilibrium.
+- The orthogonal precision concept generalizes: any forecasting mechanism that uses inter-participant comparison (peer prediction, self-resolving markets, prediction-contest scoring) is susceptible to Keynesian-beauty-contest equilibria and needs a second-order correction to elicit private signal.
+- The connection to **reflexivity** is direct: when forecasters anticipate consensus, the consensus becomes self-fulfilling · a *forecaster-level* reflexivity that compounds with any market-level reflexivity.
+- The connection to **proper scoring rules**: standard proper scoring rules elicit truth-telling *against ground truth*, but in contests where the scoring is *relative to peers* (median error, rank order), properness against ground truth doesn't imply properness against the contest. Hence the need for orthogonal-style corrections.
+- For **prediction-market design**: the beauty-contest dynamic is one reason markets · where each trader takes capital risk against the consensus · can outperform contests as information aggregators. Market participants are paid to *deviate* from consensus when they think consensus is wrong.
+- Open question: how to apply orthogonal-precision corrections to *market* mechanisms (vs. contest mechanisms), where the structure already partially incentivizes deviation.
+
+## Notable Quotes
+
+> "Forecasting contests with rank-order payouts risk collapsing into pure Keynesian beauty contests (KBC): participants report the crowd's expectation, extinguishing independent signals."
+> — *Blanco, Chung & Meka, *Orthogonal Precision in Trepa**
+
+> "The combination of a median cutoff, steep accuracy weights (γ = 6), and risk aversion pushes the equilibrium toward herding: optimal predictions anchor on the public consensus and deviate only timidly toward private signals."
+> — *Blanco, Chung & Meka, *ibid.**
+
+> "For λ = 0 and ρ > 0, the unique symmetric equilibrium satisfies w = 0 (pure herding) whenever the risk-aversion coefficient ρ exceeds a finite threshold."
+> — **ibid.* (Theorem 3.1)*
+
+> "This tunability is the core operational insight."
+> — *Blanco, Chung & Meka on the phase transition at λc*
+
+> "Any coalition of M agents with perfectly correlated reports receives the same total orthogonality bonus as a single independent agent. The marginal benefit of adding another correlated member is zero."
+> — **ibid.* (Proposition 6.1)*
+
+## Where It Matters
+
+The Keynesian beauty contest is the design hazard underneath every relative-scoring mechanism: forecasting contests, prediction tournaments, peer prediction, self-resolving markets, even contest-style oracles. As crypto-native prediction infrastructure expands beyond pure CLOB markets into contest-based oracles (Trepa) and self-resolving mechanisms (SKC), the beauty-contest pathology becomes the central design obstacle. The Trepa orthogonal-precision construction is the most direct on-page solution and likely a primitive that other mechanisms will reuse.
+
+## Related Concepts
+
+- Reflexivity · Keynesian beauty contest is the forecaster-level analog
+- Proper scoring rules · standard rules elicit truth against ground truth; beauty contests break this in contest settings
+- Information aggregation · beauty-contest equilibria *reduce* aggregation efficiency
+- Oracle design · contest-based oracles need orthogonality corrections
+- Self-resolving markets · peer-prediction-style mechanisms susceptible to herd equilibria
+- Peer prediction · directly vulnerable to beauty-contest dynamics
+- LOX (log-odds excess lateness) · measures one form of forecaster lag that connects to herding
+
+## Sources
+
+- [Orthogonal Precision in Trepa: A Tunable Second-Order Oracle for High-Frequency Forecasting](https://github.com/TrepaOrg/trepa-research/blob/main/Trepa_Orthogonal_Precision_20260513_Blanco%26Chung%26Meka.pdf) — Ilich Blanco, Jong-Chan Chung, Leon Meka · GitHub
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/legibility.md b/.qoder/plans/pm-plan/theory_concepts/legibility.md
new file mode 100644
index 0000000000..6abdde976f
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/legibility.md
@@ -0,0 +1,53 @@
+# Legibility
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Legibility](https://www.pmatlas.xyz/concepts/legibility)
+
+---
+
+## Definition
+
+The degree to which market prices reveal specific underlying information. Prediction markets have *high* legibility because contracts reference explicit events, making the information behind a price move visible to all participants · unlike stocks where a price spike could mean anything (earnings, M&A, macro, flow). The clean basis-to-truth that makes PM prices uniquely interpretable.
+
+## Key Insights
+
+- Single source · Andrew Courtney's "Legibility" Substack essay (Apr 27, 2026).
+- Core argument: prediction markets make information legible in a way stocks and options do not. When someone bets big on an attack on Maduro, *everyone immediately knows what the bet is about* · there's no ambiguity, no need to construct a thesis. The contract specification is the interpretation.
+- Contrast with equity markets: a stock spike could mean earnings beat, takeover rumor, macro news, index rebalancing, factor flow, or pure noise. The signal in equity prices is *high-bandwidth but uninterpretable* without significant context. PM prices are *narrower bandwidth but instantly interpretable* because the contract names the event being priced.
+- Legibility is a *feature*, even when it surfaces uncomfortable national-security implications. The Maduro-attack example matters because it forces a societal question: do we want a public, real-time market in geopolitical risk events? Courtney argues yes · the legibility is what makes PMs an honest information source rather than another opaque derivative.
+- Implicit consequence: legibility *amplifies* the insider-trading problem (because insider trades are easier to detect and embarrass), but also makes platforms harder to manipulate covertly (because every price move has a public interpretation). Compare to the Mitts/Ofir Harvard paper on Iran/Taylor Swift trading · that paper exists because PM legibility surfaced the asymmetric trades.
+- Courtney (FULL_READ) explicit hierarchy: **Stock prices = low legibility** ("more buyers than sellers" is the classic Wall Street non-explanation). **Options = medium legibility** (timing and probability distribution but requires pricing expertise to read). **Prediction markets = high legibility** (every wallet's bet has a single public meaning).
+- Courtney's basis-risk framing: prediction markets collapse the basis risk between information and the contract. "A corn farmer wants to hedge their specific crop, the CME corn futures contract is deliverable based on No. 2 yellow corn... there's almost always some basis risk." A hurricane parametric insurance pays on wind speed in Miami but the owner cares about *their roof*. A prediction market on "US attacks Iran" collapses the gap between the bettor's information and the contract being traded. "The specific contract collapses the basis between your information and what you're betting on."
+- Courtney's regulatory implication: because the cost of insider trading on PMs can be much higher than counterparty losses (e.g., leaking US troop movements), "pseudonymous wallet-based market structures are a poor fit for the most sensitive contracts. You want KYC, market surveillance, and audit trails. Ideally, you want to identify likely insiders up front and prevent them from trading."
+- Courtney's "tail wags the dog" attack vector · a novel manipulation pattern enabled by legibility: an adversary first builds a position in oil futures, then buys a huge amount of a thinly-traded geopolitical PM contract. Other traders see the PM signal and incorporate it into oil pricing. Oil spikes. The adversary sells oil futures, having "incinerated a few hundred thousand dollars to manipulate the prediction market, and made millions in the futures market. This is harder to catch, the 'insider' looks dumb on the prediction market, and the trading is much less legible on the more liquid exchange."
+- Courtney on war markets: "While war markets are currently not allowed in the US under the CFTC, legibility does not respect jurisdictional boundaries. If the market exists offshore, the information it reveals is still visible everywhere. If the information is anywhere, it's everywhere." Also: "all markets are death and war markets to some extent" · oil traders already implicitly bet on war outcomes; the only difference with explicit war markets is legibility, not the existence of the trade.
+
+## Notable Quotes
+
+> "When someone bets big on an attack on Maduro, everyone immediately knows what the bet is about, unlike a stock price spike that could mean anything."
+> — *Andrew Courtney*
+
+> "Andrew Courtney argues this legibility is a feature even when it surfaces uncomfortable national security implications."
+> — *onprediction summary*
+
+> "Prediction markets have high legibility because contracts reference explicit events, making the information behind a price move visible to all participants unlike stocks where price moves have ambiguous causes."
+> — *onprediction.xyz definition*
+
+## Where It Matters
+
+Legibility is the structural property that determines whether PMs become *information infrastructure* or remain a niche speculative product. Three implications: 1. **Regulatory:** legibility makes insider trading more *visible*, which is partly why PM insider trading is now a real political issue (Mitts/Ofir, Sethi) while equity insider trading is largely background noise. Platforms have to decide whether the regulatory cost of legible information is worth the credibility benefit. 2. **Product:** legibility is what makes PM prices *embeddable* in other products · a Bloomberg terminal could show "Polymarket: 42% chance of recession by Q4" without needing a 200-word footnote, because the contract is self-explanatory. Equity prices don't ship with that property. 3. **For Dekant:** distribution-market contracts inherit legibility (the curve shape is a public, interpretable belief over a named quantity) and *increase* the resolution · a curve carries more legible information than a binary "YES/NO at X%". The risk: a more complex contract surface could be *less* legible if users can't read the curve. UX is critical.
+
+## Related Concepts
+
+- **Information asymmetry** · legibility surfaces asymmetry (insider trades are easier to spot)
+- **Price discovery** · legibility makes discovery more interpretable to outsiders
+- **Insider trading** · legibility is why PM insider trades become public news
+- **Market surveillance** · legibility makes surveillance more tractable
+- **Information aggregation** · legibility is what makes the aggregated signal usable
+- **Probability infrastructure** · legibility is the property that makes embedding viable
+- **Nowcasting** · legibility is why PM prices can substitute for econ indicators
+
+## Sources
+
+- [Legibility](https://whirligigbear.substack.com/p/legibility) — Andrew Courtney · Apr 27, 2026 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/liquidity-fragmentation.md b/.qoder/plans/pm-plan/theory_concepts/liquidity-fragmentation.md
new file mode 100644
index 0000000000..318ef85cd7
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/liquidity-fragmentation.md
@@ -0,0 +1,50 @@
+# Liquidity Fragmentation
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Liquidity Fragmentation](https://www.pmatlas.xyz/concepts/liquidity-fragmentation)
+
+---
+
+## Definition
+
+**Quick definition.** The dispersion of trading capital across multiple independent order books when a single question is split into many binary contracts. In prediction markets this creates *ghost markets* where tail outcomes receive little or no volume, reducing the information captured by the market structure as a whole.
+
+## Key Insights
+
+- functionSPACE V1 (36,777 Polymarket events): volume follows an **extreme Pareto distribution** · top 3 markets capture **>75% of trading activity** regardless of event size; the rest are largely untradeable ghost markets. The $0.01 tick size compounds the problem by adding a "rounding tax" that makes low-probability contracts structurally imprecise.
+- functionSPACE V2 (18,863 multi-market events, split continuous vs. categorical): both types concentrate **90% of volume in top 5–6 markets**, but **ghost markets are largely a *categorical* phenomenon**. Continuous events (price brackets, weather ranges, margin %) distribute volume more evenly and survive the liquidity cliff longer at high N. **Continuous events overtook categorical by event count in 2026 Q1** · making the case for continuous-distribution primitives apply to a growing share of the platform.
+- Lotus: **cross-venue fragmentation** is its own problem · same contract trading at **58–67 cents** on different platforms. The January 2026 Polymarket XRP exploit paid **$231K on thin weekend liquidity** precisely because fragmentation prevents cross-venue MM defense.
+- Lotus also reports that across 150M Polymarket trades, the **top 5% of skilled traders earned $228M** while spread capture barely moves P&L · most LP P&L is event-driven, not microstructure-driven, which is what fragmentation does.
+- The structural argument running through all three pieces: **binary contracts impose a fragmentation tax that continuous-distribution markets don't pay**. This is the same conclusion the distribution-markets and covariance-markets literature reaches from the design side.
+
+## Notable Quotes
+
+> "The top 3 markets capture over 75% of trading activity regardless of event size."
+> — *functionSPACE V1*
+
+> "Ghost markets turn out to be largely a categorical phenomenon."
+> — *functionSPACE V2*
+
+> "Same contract at 58-67 cents on different platforms."
+> — *Lotus*
+
+## Where It Matters
+
+Liquidity fragmentation is the structural argument *for* multi-outcome markets, continuous-distribution markets, covariance markets, and parlays-without-fragmentation. Every multi-binary representation of a single underlying question pays a Pareto tax · only the top few contracts get traded, the rest are dead. Builders working on distribution primitives (Dekant, functionSPACE's vision, Limitless's CLOB-on-distributions) are explicitly attacking this tax.
+
+## Related Concepts
+
+- **Binary contracts** · the structural cause.
+- **Multi-outcome / distribution markets** · the proposed fix.
+- **Minimum viable liquidity** · many fragmented markets fall below MVL.
+- **Long-tail markets** · fragmentation kills them.
+- **Cross-platform arbitrage** · fragmentation across venues.
+- **Semantic tick size** · the $0.01 tick is the rounding tax that compounds fragmentation.
+- **Covariance markets** · explicit solution for the parlay-fragmentation case.
+
+## Sources
+
+- [Binary Events V2: Does Liquidity Trade The Tails?](https://x.com/functionspaceHQ/status/2048536153662636173) — functionSPACE · Apr 27, 2026
+- [Market Making In PMs Sucks](https://x.com/uselotusai/status/2046639095531614455) — Lotus · Apr 21, 2026
+- [Binary Events: What Happens When You Split One Market Into Twenty](https://x.com/functionspaceHQ/status/2039554933024776516) — functionSPACE · Apr 2, 2026
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/liquidity-provision.md b/.qoder/plans/pm-plan/theory_concepts/liquidity-provision.md
new file mode 100644
index 0000000000..07b5979ed0
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/liquidity-provision.md
@@ -0,0 +1,125 @@
+# Liquidity Provision
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Liquidity Provision](https://www.pmatlas.xyz/concepts/liquidity-provision)
+
+---
+
+## Definition
+
+**Quick definition.** Supplying capital so traders can buy and sell prediction-market positions without excessive price impact or delay. In prediction markets this is structurally harder than in other asset classes because outcomes resolve to binary values and counterparties often hold near-perfect information.
+
+## Key Insights
+
+- Polymarket's late-2022 migration from an LMSR AMM to a central limit order book was the moment the industry recognized that prediction-market liquidity needs a different mechanism than token swaps. In a binary market that resolves to 0 or 1, impermanent loss becomes *permanent*: the pool inevitably holds worthless shares on the losing side, and trading fees can never offset a guaranteed structural loss (Melee, "Why AMMs Failed Prediction Markets").
+- CLOBs fixed AMMs' capital destruction but introduced a new pathology: passive participation is essentially impossible and only professional market makers can quote. Kalshi reportedly has ~23 active market makers with the top three providing **70% of election-contract liquidity** · any market those firms ignore is dead on arrival (Melee, "The Problem With CLOBs").
+- Polymarket's liquidity is **episodic and attention-driven**: p95 peak hour shows hundreds of millions in open interest while the p50 median is thin. Order book analysis shows surface symmetry at top-of-book but systematic ask-side skew at deeper levels (@allquantor / ZEIT Finance).
+- Trapped/locked collateral is the binding constraint, not money. Dollars get reserved multiple times against mutually exclusive outcomes; better netting and capital efficiency, not more capital, is the fix (@allquantor).
+- Trading **volume explains <1% of variance in forecast accuracy** across 149 Kalshi CPI markets (Rajaprabhakaran, MVL): the regression of log-MAE on log-volume yielded a slope of −0.063 (p=0.28, R²=0.008) across a 1.6 million-fold volume range. Volume ran from 7 contracts to 11.4M contracts (April 2025 Robinhood-driven spike, 816× the dog-days trough) · and median absolute forecast error stayed near 0.093 percentage points throughout. Volume from casual traders functioned as **venture-capital seed funding** for forecasters to build CPI pipelines; once the pipelines existed, MVL collapsed and the market self-sustained at <10k contracts/event.
+- Rajaprabhakaran's MVL formula: **MVL = Cost of Expertise / Price Gap**. Below this floor, the bounty isn't large enough to attract anyone with real information and the price is just noise; above it, additional liquidity doesn't improve accuracy, it just raises the cost the next informed trader has to pay to correct a mispricing. CPI MAE actually *improved* (from 0.131 pp in 2022 to 0.074 pp in 2025) as volume collapsed 95% · the trend tracks a learning curve, not a liquidity curve.
+- ARK Invest sizes the prediction-market opportunity at **up to ~$5 trillion medium-term**, framing the real potential as prediction markets becoming "a foundational layer of financial services" rather than disrupting sports betting (Grous, Prasanna, Hadi, ARK Invest, Apr 2026).
+- Andy Hall & Elliot Paschal: **98.7% of political prediction markets qualify as "ghost towns"** · wide spreads, almost no one on the other side · yet overall calibration still clusters near the line. Only **70 of 131 resolved US elections (53%)** appeared on both Kalshi and Polymarket; among 1,826 active electoral events, overlap drops to **18%**. They explicitly call this a "public goods problem" solvable via cross-subsidy from sports markets (which generate billions in volume) plus philanthropic funders like Open Philanthropy, Schmidt Futures, or IARPA.
+- Hall & Paschal's "Building the Truth Machine" blueprint: (1) stock the shelves with policy questions, (2) fund the floor with paid market-makers, (3) recruit AI traders where humans won't show up, (4) standardize resolution rules across platforms (ISDA-equivalent for PMs) to stop fragmentation. The OPM-website resolution debacle on a Polymarket government-shutdown contract is cited as why standardization matters.
+- Alan Wu reframes prediction-market prices as **public goods**: benefits are non-excludable but liquidity costs fall on a narrow trader base. Cross-subsidization (profitable markets fund socially valuable ones) is the proposed growth mechanism, like newspaper ads funding investigative journalism.
+- Parimutuel pools solve the cold-start problem because they bootstrap liquidity without market makers; the friction points are locked positions, timing constraints, and price readability (Melee).
+- Jeff Opinion's Meta Pool proposes resolution-aware meta-pools that unify liquidity across fragmented platforms with different oracles, estimating **$3.4–8.5M annual efficiency losses** from current fragmentation.
+- Functionspace's Information Vectors thesis: binary event contracts fragment liquidity and flatten beliefs into 1-bit structures · achieving 8-bit resolution requires **256 separate markets**. Continuous probability surfaces concentrate liquidity instead.
+- Polymarket fast crypto markets (5m, 15m) generate ~16% of platform volume but ~40% of fees; bots control 55–62% of fast-market volume and Bitcoin drives 77% of turnover (Dune, "Anatomy of Polymarket's Fastest Markets").
+- Limitless achieves **50–400 bps spreads** on price-prediction markets (better than onchain options at 1000+ bps) · these short-expiry contracts behave like a new asset class enabling synthetic covered calls and structured hedging (Ranger Global).
+- Astaria argues prediction-market perpetuals are structurally different from crypto perps because event contracts lack a tradeable underlying; most implementations become "liquidation arcades" and the winning path is institutional event-risk hedging infrastructure.
+- Sid argues prediction markets are the **future of advertising** · sponsors seed liquidity in markets traders then research at length, contrasted with seconds of passive display-ad exposure (specific seed-size and per-hour figures from the underlying X article; not independently verifiable from the fetched stub).
+- Inception Capital (Hiroki Kotabe, "The Prediction Market Primitive"): the path to billion-user prediction markets runs through an **AI quartet** · content creators, event recommenders, liquidity allocators, and information aggregators · enabling "Tinder-like" microscopic-scale markets embedded in everyday digital life. Vitalik called prediction markets the "holy grail of epistemics technology."
+- Polymarket: 4-account cluster controlled ~23% of open interest during 2024 election, ~41% of volume looked like wash trading · current platforms lack the structural conditions for reliable forecasting (Luca Prosperi).
+- LessWrong "Will Jesus Christ Return": ~$100k wagered, market stable at 3% · but to fade to 1% requires locking ~$1M for a year to earn 1% (worse than T-bills). The "Time Value of Money" hypothesis explains why "Yes" trades elevated: holders are speculating that No-side counterparties will need cash later in the year to bet on other markets (elections, China invading Taiwan, conclave) and will sell their No positions at a premium. Historical proof: late-October 2024, Kamala in Kentucky and Trump in Massachusetts both traded at 0.3% and spiked to 1.5% on election day as No-holders desperately needed cash · a 5x return. Implication: in low-liquidity PM markets, prices reflect the **time-value of platform cash**, not literal probabilities, and Polymarket loan/liquidity-reward design distorts this further.
+- Whitaker & Mazlish: PMs are caught between three participant types that don't match · gamblers want quick resolutions (42% of 2020 election volume traded in the final week), long-term investors prefer compounding in stocks, and MMs need consistent retail flow that only exists for elections/sports. None of the three finds PMF in the broader question space.
+- Nardi (Shoal): Polymarket grew TVL to ~$130M and cumulative volume to ~$1B as of late 2024, but its categorical, episodic structure means liquidity is built around 1-2 dominant questions per cycle rather than a generalized base layer.
+
+## Notable Quotes
+
+> "In a binary market that resolves to 0 or 1, impermanent loss becomes permanent: the pool inevitably holds worthless shares on the losing side, and trading fees cannot offset a guaranteed structural loss."
+> — *Melee, "Why AMMs Failed Prediction Markets"*
+
+> "Kalshi reportedly has 23 active market makers with the top three providing 70% of election-contract liquidity, meaning any market those firms ignore is dead on arrival."
+> — *Melee, "The Problem With CLOBs"*
+
+> "Trading volume explains less than 1% of variance in forecast accuracy."
+> — *Adhi Rajaprabhakaran, "Minimum Viable Liquidity"*
+
+> "Polymarket doesn't have a money problem. It has a plumbing problem."
+> — *@allquantor*
+
+> "The information doesn't require deep liquidity. It requires the right people, the right questions, and just enough money to make it worth their while."
+> — *Rajaprabhakaran, on MVL*
+
+> "The primary product of prediction markets isn't the ability to trade. It's the information that trading produces. The price is the product."
+> — *Rajaprabhakaran, cited in Hall & Paschal*
+
+> "Right now, there's not much interesting stuff happening on Polymarket... at some point in 2025, other markets will get a lot of attention. The New York mayoral election... a new pope... and if it changes, some of the people betting No on Christ's return will want to unlock that money."
+> — *Eric Neyman, LessWrong, on Time-Value-of-PM-Cash*
+
+## Where It Matters
+
+Liquidity provision is the bottleneck on every other property of the asset class: thin markets cannot resist manipulation, cannot attract informed traders, cannot offer leverage safely, and cannot serve as hedging instruments. Because binary outcomes make traditional market-making unprofitable without subsidies, every successful platform must either subsidize liquidity (LMSR/incentives), concentrate it (CLOB with pro MMs), or pool it (parimutuel/AMM with adjusted mechanics) · and most of the design space in 2026 is occupied by attempts to escape the trade-offs each of these creates.
+
+## Related Concepts
+
+- **Market making** · the supply side of liquidity provision; rests on adverse-selection economics.
+- **Adverse selection** · explains why binary markets are structurally hostile to passive liquidity.
+- **Minimum viable liquidity** · argues there is a *threshold* of liquidity that matters, not unbounded depth.
+- **Order book / continuous double auction** · the dominant trading mechanism that replaced AMMs.
+- **Parimutuel markets** · the bootstrap alternative when no market maker exists.
+- **LMSR / market scoring rules** · the original automated subsidy mechanism, now mostly abandoned for binaries.
+- **Liquidity fragmentation** · what happens when many binary markets divide a question; meta-pool / covariance-market solutions try to undo this.
+- **Position collateralization** · frees locked capital, the operational definition of "plumbing problem."
+- **Long-tail markets** · the unsolved liquidity-provision frontier.
+
+## Sources
+
+- [Sportsbooks vs Prediction Markets - Market Structure and Its Effects](https://x.com/0xtaetaehoho/status/2054215076471873575) — taetaehoho · May 12, 2026
+- [A Game of Volatility](https://x.com/Kunallegendd/status/2054191944944038084) — Kunal Doshi · May 12, 2026
+- [Prediction Market Perps - the 1% Winning Product](https://x.com/AstariaTrade/status/2051363114881568852) — Astaria · May 4, 2026
+- [Prediction Markets: The Potential Multi-Trillion Dollar Asset Class Hiding In Plain Sight](https://www.ark-invest.com/articles/analyst-research/prediction-markets-potential-multi-trillion-dollar-asset-class) — Nicholas Grous, Varshika Prasanna, Raye Hadi (ARK Invest) · Apr 22, 2026
+- [The Problem With CLOBs](https://x.com/meleemarkets/status/2046318159225897289) — Melee · Apr 21, 2026
+- [What Happens When Institutional Liquidity Enters Prediction Markets](https://x.com/DaedalusRsch/status/2046219948452979151) — Daedalus Research · Apr 20, 2026
+- [Faster, Shorter, More Automated: Anatomy of Polymarket's Fastest Markets](https://x.com/Dune/status/2044045952730726863) — Dune · Apr 14, 2026
+- [Why AMMs Failed Prediction Markets](https://x.com/meleemarkets/status/2043766417342755259) — Melee · Apr 13, 2026
+- [Parimutuel Prediction Markets](https://x.com/meleemarkets/status/2041244791716110409) — Melee · Apr 6, 2026
+- [How Prediction Markets Can Ascend](https://x.com/alanwu/article/2036438579824496906) — Alan Wu · Mar 25, 2026
+- [The World's Biggest Risk Event Just Exposed Prediction Markets' Biggest Gap](https://x.com/jolimmmm/article/2036119259420807216) — Jo Lim · Mar 24, 2026
+- [Noisy Traders Are Not Dumb Money](https://x.com/functionspaceHQ/article/2032397043579462028) — functionSPACE · Mar 13, 2026
+- [Polymarket Doesn't Have a Money Problem. It Has a Plumbing Problem.](https://x.com/ZEITFinance/article/2031500989576949770) — @allquantor · Mar 11, 2026
+- [Turning Probability into Assets: A Look Ahead at Prediction Market Agents](https://x.com/0xjacobzhao/article/2029229969914904694) — Jacob Zhao · Mar 5, 2026
+- [Prediction Markets Are the Future of Advertising](https://x.com/sidbharth/article/2027740100214603934) — Sid · Feb 28, 2026
+- [23 Reasons Prediction Markets Are Broken Today](https://x.com/linfluence/status/2026757563518431696) — Alexander Lin · Feb 26, 2026
+- [Minimum Viable Liquidity](https://fiftycentdollars.substack.com/p/minimum-viable-liquidity) — Adhi Rajaprabhakaran · Feb 24, 2026
+- [Prediction Markets Are Not Good Markets (Yet)](https://murmurationstwo.substack.com/p/prediction-markets-are-not-good-markets) — Nic Carter · Feb 21, 2026
+- [Prediction Markets: The Path to $1 Trillion and What Needs to Happen Next](https://x.com/a1research__/article/2022340314770375024) — Sakshi Mishra · Feb 14, 2026
+- [Leverage Fixes Prediction Markets: The Case for Why 10x Is Safer Than 1x](https://x.com/njokuScript/article/2022351299547689325) — njokuScript · Feb 14, 2026
+- [Building the Truth Machine](https://freesystems.substack.com/p/building-the-truth-machine) — Andy Hall, Elliot Paschal · Feb 13, 2026
+- [The 7 Axes of Prediction Markets](https://x.com/Jake_Nyquist/article/2021222519991124343) — Jake Nyquist · Feb 10, 2026
+- [How to Solve Insider Trading in Prediction Markets](https://www.dopaminemarkets.com/p/how-to-solve-insider-trading-in-prediction) — Shreyas Hariharan · Feb 10, 2026
+- [How Prediction Market Traders Reinvented the Bond](https://www.ft.com/content/3382195d-b417-4fc8-9c2e-17f38027a635) — Stephanie Stacey (FT) · Feb 6, 2026
+- [The Super Bowl of Prediction Markets: Kalshi and Polymarket's Battle for Price vs Liquidity](https://panteraresearchlab.xyz/research/the-super-bowl-of-prediction-markets-kalshi-and-polymarkets-battle-for-price-vs-liquidity/) — Ally Zach, Danning Sui · Feb 5, 2026
+- [The Option Value of Waiting in Prediction Markets](https://x.com/0xnagu/article/2016620973391564951) — 0xnagu · Jan 28, 2026
+- [Everyone's Promising 20x Leverage on Prediction Markets. Here's Why It's Hard.](https://medium.com/@nick.c.ruzicka/everyones-promising-20x-leverage-on-prediction-markets-here-s-why-it-s-hard-8684d9a77e11) — Nick Ruzicka · Jan 27, 2026
+- [Information Vectors: An Intro to Composable Beliefs](https://x.com/functionspaceHQ/article/2014809461647671570) — functionSPACE · Jan 24, 2026
+- [The Shape of Prediction Markets to Come](https://www.galaxy.com/insights/research/prediction-markets-leverage-ai-agents-defi) — Will Owens (Galaxy) · Jan 19, 2026
+- [Prediction Markets: A New Kind of Asset Class](https://x.com/njokuScript/article/2010080717729046613) — njokuScript · Jan 11, 2026
+- [Why Prediction Markets Are Where They Are](https://tim0x.substack.com/p/why-prediction-markets-are-where) — Tim.0x · Jan 5, 2026
+- [Liquidity in Prediction Markets and the Rise of a New Asset Class](https://x.com/Ranger_Global/status/2008177990971122003) — Ranger Global · Jan 5, 2026
+- [Prediction Markets: The Next Level](https://x.com/0xkeshav_/article/1970163909224169626) — keshav · Sep 23, 2025
+- [The Liquidity Problem in Prediction Markets: Part 0](https://x.com/semajeth/status/1966144230826414225) — semaji.eth · Sep 11, 2025
+- [Meta Pool: A Unified Infrastructure for Liquidity and Resolution Trust in Prediction Markets](https://x.com/jeff__opinion/article/1957611486991487486) — Jeff Opinion · Aug 19, 2025
+- [Opportunity Markets](https://www.paradigm.xyz/2025/08/opportunity-markets) — Dave White, Matt Liston (Paradigm) · Aug 18, 2025
+- [How to Offer Prediction Market Parlays Without Fragmenting Liquidity](https://x.com/voliti_co/status/1946232456459255869) — Chicken · Jul 18, 2025
+- [Will Jesus Christ Return in an Election Year?](https://www.lesswrong.com/posts/LBC2TnHK8cZAimdWF/will-jesus-christ-return-in-an-election-year) — LessWrong · Mar 25, 2025
+- [How Manipulable Are Prediction Markets?](https://arxiv.org/abs/2503.03312) — Itzhak Rasooly, Roberto Rozzi · Mar 5, 2025
+- [The Definitive Guide to Prediction Markets](https://docsend.com/view/atcc6k258s4umq6s) — Four Pillars · Jan 15, 2025
+- [No, You Can't Bet on Everything and That's Okay](https://rnikhil.com/2024/12/18/prediction-market-crypto) — Nikhil R · Dec 18, 2024
+- [Prediction Markets (II): Spoiling the Election Love Story](https://dirtroads.substack.com/p/64-prediction-markets-ii-spoiling) — Luca Prosperi · Nov 2, 2024
+- [Crypto Prediction Markets](https://dirtroads.substack.com/p/63-crypto-prediction-markets) — Luca Prosperi · Oct 11, 2024
+- [Polymarket and the Proliferation of Prediction Markets](https://www.shoal.gg/p/prediction-markets) — Alex Nardi · Oct 8, 2024
+- [Mechanisms for Prediction Markets](https://paragraph.com/@mechanism.institute/prediction-markets) — Raye Hadi, Sofia Cossar, Ori Shimony · Aug 22, 2024
+- [Why Prediction Markets Aren't Popular](https://worksinprogress.co/issue/why-prediction-markets-arent-popular/) — Nick Whitaker, J. Zachary Mazlish · May 17, 2024
+- [The Prediction Market Primitive](https://medium.com/inception-capital/the-prediction-market-primitive-e48a055676bf) — Hiroki Kotabe · Apr 4, 2024
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/lmsr-logarithmic-market-scoring-rule.md b/.qoder/plans/pm-plan/theory_concepts/lmsr-logarithmic-market-scoring-rule.md
new file mode 100644
index 0000000000..e2e290ca15
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/lmsr-logarithmic-market-scoring-rule.md
@@ -0,0 +1,66 @@
+# LMSR (Logarithmic Market Scoring Rule)
+
+**Category:** Mechanism Design
+**Source:** [PM Atlas — LMSR (Logarithmic Market Scoring Rule)](https://www.pmatlas.xyz/concepts/lmsr-logarithmic-market-scoring-rule)
+
+---
+
+## Definition
+
+An automated market maker that prices trades using a logarithmic cost function, guaranteeing bounded loss to the subsidizer. Robin Hanson's LMSR is the canonical prediction-market AMM · but in practice it failed for binary contracts and Polymarket migrated to a CLOB in late 2022.
+
+## Key Insights
+
+- **LMSR failed for production binary markets.** In a binary market that resolves to 0 or 1, impermanent loss becomes *permanent*: the pool inevitably holds worthless shares on the losing side, and trading fees cannot offset a guaranteed structural loss. Polymarket's late-2022 migration from an LMSR AMM to a central limit order book is the inflection point where the industry recognized this (Melee, *Why AMMs Failed Prediction Markets*).
+- Dalen / Daedalus Research formalizes LMSR's structural problem in a clean line: "For cost-function market makers such as LMSR, providing more liquidity increases worst-case loss; without external subsidies, they are expected to run at a deficit proportional to the liquidity they offer." This is why LMSR works in subsidized academic / forecasting-tournament contexts but bleeds capital in production.
+- LMSR is mathematically identical to the **softmax function** · this bridges quant finance and prediction-market pricing, and is why ML-derived methods (Bayesian regime detection, factor models, machine learning) transfer directly (gemchanger).
+- Chen & Pennock (*Designing Markets for Prediction*, Jan 2025) position LMSR as the canonical example among automated market makers designed for "(effectively infinite) liquidity for prediction markets." Their framing: "Many automated market maker mechanisms have been designed to provide (effectively infinite) liquidity for prediction markets; much effort has been put into understanding manipulation in prediction markets and designing prediction mechanisms to achieve incentive compatibility."
+- Shaw Dalen / Daedalus Research (Oct 2025) proposes a *Black–Scholes for prediction markets*: a unified stochastic kernel (logit jump-diffusion) that treats traded probability `pt = S(xt)` as a Q-martingale. The risk-neutral drift is pinned down by `µ(t,x) = -[½S''(x)σ_b²(t,x) + ∫(S(x+z)-S(x)-S'(x)χ(z))ν_t(dz)] / S'(x)`. What remains tradable: belief volatility `σb`, jump intensity, and cross-event correlation/co-jumps. Defines a derivative menu (belief variance/volatility swaps, correlation swaps, corridor variance, threshold/first-passage notes) on top of an LMSR-compatible state.
+- Dalen's central thesis on why LMSR alone isn't enough: "Today, venues execute via scoring rules or AMMs or CLOBs, but there is no shared stochastic model for how probabilities evolve across time, shocks, or related events. Without a kernel, makers cannot isolate 'belief risk' (level, volatility, jumps, co-movement) or lay it off in a standard way; spreads widen around news; inventory near the 0/1 boundaries becomes hard to manage."
+- Powell, Hanson, Laskey & Twardy (2013) ran the canonical experimental study on combinatorial prediction markets using the DAGGRE platform · a Bayesian-network-driven murder-mystery experiment where participants traded conditional and Boolean events. Their experimental result: "economic theory suggests that the greater expressivity of combinatorial prediction markets should improve accuracy by capturing dependencies among related questions" · empirically validated against flat structures. LMSR's log cost function decomposes naturally over the combinatorial state space.
+- Baheet's game-theory explainer: LMSR works *because* it's a proper scoring rule · truth-telling is the dominant strategy. Builders need economists, not just engineers.
+- Jo Lim's Strait of Hormuz piece argues LMSR/CLMSR is uniquely suited to events *without underlying assets* · the protocol-native liquidity property (you don't need a counterparty) is what makes scoring-rule markets viable for granular, multi-outcome risk pricing.
+- LMSR's "bounded loss" property is the killer feature for *subsidized* markets · a market designer puts up capital `b · ln(n)` and that's the max they can lose, irrespective of trade volume. This is what makes long-tail markets economically viable.
+- The liquidity parameter `b` is the key design knob: high `b` = tight prices, more capital risk, lower price sensitivity. Low `b` = volatile prices, less capital risk, more responsive to small trades.
+- **Open problem:** LMSR's structural-loss issue for binaries doesn't apply identically to continuous outcomes or to non-binary multi-outcome markets · there's an emerging design conversation (CLMSR, scoring-rule AMMs for continuous distributions) that Dekant's L2-norm CFAMM is part of.
+
+## Notable Quotes
+
+> "In a binary market that resolves to 0 or 1, impermanent loss becomes permanent: the pool inevitably holds worthless shares on the losing side, and trading fees cannot offset a guaranteed structural loss."
+> — *Melee, *Why AMMs Failed Prediction Markets**
+
+> "For cost-function market makers such as LMSR, providing more liquidity increases worst-case loss; without external subsidies, they are expected to run at a deficit proportional to the liquidity they offer."
+> — *Dalen et al., *Toward Black–Scholes for Prediction Markets**
+
+> "Today, venues execute via scoring rules or AMMs or CLOBs, but there is no shared stochastic model for how probabilities evolve across time, shocks, or related events."
+> — *Dalen et al., *ibid.**
+
+> "Uses LMSR's mathematical identity with the softmax function to bridge quant finance and prediction market pricing."
+> — *gemchanger, *Your Hedge Fund's Sharpe Ratio Is a Lie**
+
+> "Many automated market maker mechanisms have been designed to provide (effectively infinite) liquidity for prediction markets."
+> — *Chen & Pennock, *Designing Markets for Prediction**
+
+## Where It Matters
+
+LMSR is the mechanism every prediction-market designer benchmarks against. The orthodoxy is "LMSR for low-liquidity / long-tail, CLOB for liquid markets" · Polymarket validated this by migrating to CLOB once liquidity was sufficient. But the failure of LMSR on binaries does *not* generalize to continuous markets, where the partition-of-unity structure (and Dekant's L2-norm CFAMM specifically) keeps the AMM solvent. LMSR remains the conceptual ancestor of every cost-function AMM in the space, and the softmax identity is what lets traditional quant tools transfer directly. Dalen's logit jump-diffusion framework is a strong candidate for the missing institutional pricing kernel sitting on top of an LMSR-compatible state.
+
+## Related Concepts
+
+- Proper scoring rules · LMSR is the cost-function dual of the log scoring rule
+- Market scoring rules · LMSR is the canonical instance
+- Market making · LMSR is an *automated* market maker (no inventory risk in the usual sense)
+- Order book · Polymarket abandoned LMSR for CLOB
+- Combinatorial prediction markets · LMSR decomposes naturally for combinatorial pricing
+- Liquidity provision · LMSR's `b` parameter sets subsidized liquidity
+- Incentive compatibility · LMSR inherits IC from the log scoring rule
+
+## Sources
+
+- [Why AMMs Failed Prediction Markets](https://x.com/meleemarkets/status/2043766417342755259) — Melee · X
+- [Your Hedge Fund's Sharpe Ratio Is a Lie. Prediction Markets Are the Only Place It Can't Hide.](https://x.com/gemchange_ltd/article/2026300422483271733) — gemchanger · X
+- [Toward Black–Scholes for Prediction Markets](https://arxiv.org/pdf/2510.15205) — Shaw Dalen, Daedalus Research Team · arXiv
+- [The Game Theory Behind Prediction Markets](https://x.com/Baheet_/status/1965758390430208066) — Baheet · X
+- [Designing Markets for Prediction](https://bpb-us-e1.wpmucdn.com/sites.harvard.edu/dist/b/845/files/2025/01/aim10.pdf) — Yiling Chen, David M. Pennock · Harvard
+- [Combinatorial Prediction Markets: An Experimental Study](https://link.springer.com/chapter/10.1007/978-3-642-40381-1_22) — Powell, Hanson, Laskey & Twardy · Springer
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/long-tail-markets.md b/.qoder/plans/pm-plan/theory_concepts/long-tail-markets.md
new file mode 100644
index 0000000000..f7e4e92387
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/long-tail-markets.md
@@ -0,0 +1,57 @@
+# Long-tail markets
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — Long-tail markets](https://www.pmatlas.xyz/concepts/long-tail-markets)
+
+---
+
+## Definition
+
+**Quick definition.** PMs on niche or specialized questions that individually attract thin liquidity but collectively generate significant informational value. The PM analog of YouTube's long-tail of content.
+
+## Key Insights
+
+- functionSPACE's binary-events analysis: across Polymarket's 18,863 multi-market events, 90% of volume concentrates in the top 5-6 markets. "Ghost markets" (tail outcomes receiving little or no volume) are largely a categorical (team/candidate) phenomenon, not a continuous-bracket phenomenon. Continuous events distribute volume more evenly across buckets and survive the liquidity cliff at higher N.
+- Continuous events overtook categorical by event count in 2026 Q1 · the case for a continuous-distribution primitive applies to a growing share of the platform. (Directly relevant to Dekant's thesis.)
+- taetaehoho's sportsbook-vs-PM spread analysis: PM prices are 100-300 bps better than DraftKings/FanDuel on liquid markets, but long-tail PMs show 10-50% spreads. Sportsbooks tighten via counterparty-aware pricing; PMs compensate through maker competition and order book transparency.
+- Melee's CLOB diagnosis: order books fixed Polymarket's AMM capital destruction problem but introduced a new pathology · passive participation is impossible, only professional MMs can quote. Kalshi reportedly has 23 active MMs, with the top three providing 70% of election-contract liquidity. Any market those firms ignore is dead on arrival.
+- This is the structural reason PMs remain concentrated in politics and sports while entertainment, science, and culture verticals stay empty. Melee argues for peer-to-peer architecture that lets the first participant seed liquidity for the second.
+- Turf's sdk.markets: parimutuel pool infrastructure built on Base for arbitrary questions. CLOBs don't fit thin community markets; parimutuel pools are simpler and fairer when there's no natural counterparty. Includes design choices for the classic parimutuel "wait and see" sniping problem (short answer windows, snapshot locking, DPM-style pricing) and three resolution modes (single admin, multi-admin consensus, AI oracle reading from URLs).
+- functionSPACE's "Information as Supply": as the cost of producing real-time probability estimates collapses, PM TAM extends beyond trading volume to every decision that benefits from better forecasts. Scaling to $1T requires massive breadth in long-tail markets rather than concentrated depth in a few high-volume categories.
+- The ordered liquidity-formation path: entertainment -> information -> institutional demand. Long-tail markets are how PMs cross from category to category.
+- Min-viable-liquidity threshold (referenced from related concept page): some long-tail markets fall below the threshold where informed traders can correct mispricing. Beyond a certain expense, additional liquidity doesn't improve forecast accuracy · but most long-tail markets don't yet hit even the basic threshold.
+- The covariance-market argument (Chicken, in parlays concept): long-tail combinations should be served by parametric mechanisms (covariance markets, continuous payouts) rather than enumerating every joint outcome as a separate binary.
+- Andy Hall's Truth Machine fix involves cross-subsidizing long-tail political markets from sports profits · making them viable even if they can't pay their own MMs.
+
+## Notable Quotes
+
+> "Continuous events overtook categorical events by event count in 2026 Q1."
+> — *paraphrased from functionSPACE, *Binary Events V2**
+
+> "Any market those firms [the top three Kalshi MMs] ignore is dead on arrival."
+> — *paraphrased from Melee, *The Problem With CLOBs**
+
+> "Scaling to $1T requires massive breadth in long-tail markets rather than concentrated depth in a few high-volume categories."
+> — *paraphrased from functionSPACE, *Information as Supply**
+
+## Where It Matters
+
+Long-tail markets are the open frontier · incumbents can't profitably serve them with CLOBs, and the informational value compounds across categories. They're also the market structure where alternative mechanisms (parimutuel, continuous payouts, AI MMs) have the strongest case. For Dekant: continuous-curve markets specifically dissolve the "ghost markets" problem documented by functionSPACE · instead of fragmenting one question into many thin binary contracts, traders express a shape over the entire outcome space in one pool.
+
+## Related Concepts
+
+- Minimum viable liquidity · the threshold most long-tail markets fail
+- Parimutuel markets · alternative bootstrap mechanism for thin markets
+- Liquidity fragmentation · the failure mode of categorical long-tail design
+- Cross-subsidization · how profitable markets fund socially valuable ones
+- Continuous prediction markets · Dekant's positioning against ghost-market pathology
+- Self-resolving markets · automatable resolution lowers the per-market overhead
+
+## Sources
+
+- [Sportsbooks vs Prediction Markets - Market Structure and Its Effects](https://x.com/0xtaetaehoho/status/2054215076471873575) — taetaehoho · May 12 2026 ·
+- [Binary Events V2: Does Liquidity Trade The Tails?](https://x.com/functionspaceHQ/status/2048536153662636173) — functionSPACE · Apr 27 2026 ·
+- [Every Opinion Deserves A Market](https://x.com/turfsports_/status/2047030930699854334) — Turf · Apr 22 2026 ·
+- [The Problem With CLOBs](https://x.com/meleemarkets/status/2046318159225897289) — Melee · Apr 21 2026 ·
+- [Information as Supply](https://x.com/functionspaceHQ/article/2035959494728176075) — functionSPACE · Mar 23 2026 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/longshot-bias.md b/.qoder/plans/pm-plan/theory_concepts/longshot-bias.md
new file mode 100644
index 0000000000..284d4c2580
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/longshot-bias.md
@@ -0,0 +1,59 @@
+# Longshot Bias
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Longshot Bias](https://www.pmatlas.xyz/concepts/longshot-bias)
+
+---
+
+## Definition
+
+The tendency for low-probability outcomes to be systematically overpriced in prediction markets · bettors pay too much for tail outcomes (relative to true probability), so longshots have negative expected return. Originally documented in horse racing; appears in modern PMs too, though 2026 research argues a confounding "yes bias" may explain much of what's been attributed to favorite-longshot.
+
+## Key Insights
+
+- Three structural reasons PM prices diverge from true probabilities even with rational participants: (1) favorite-longshot bias from Kelly betting, (2) risk-premium distortion from market correlation, (3) risk-neutral forward pricing in long-dated contracts (Lihong).
+- Market making in PMs is structurally broken. Cross-venue fragmentation (same contract at 58–67¢ on different platforms). January 2026 Polymarket XRP exploit paid $231K on thin weekend liquidity. Kalshi's structural longshot bias persists. Evidence from 150M Polymarket trades: top 5% skilled traders earned $228M; spread capture barely moves P&L. Passive LPs behave more like *underwriters of terminal risk* than classical market makers (Lotus).
+- The yes bias might not exist as a standalone effect. Analysis of 7,292 resolved Polymarket markets and 28,793 on-chain trades: traders buy whichever token is *cheaper*, not whichever is labeled YES. Apparent bias = compound effect of longshot preference channeled through Polymarket's "Will X happen?" framing, which systematically assigns the longshot to the YES token (functionSPACE).
+- Tick-level order flow across Polymarket/Kalshi: classic favorite-longshot bias may be a *statistical artifact* masking a pervasive "yes bias" driven by temporal volatility and incomplete contract-lifecycle controls. Whales are NOT the sharpest participants · heavily capitalized traders systematically bleed EV to small-order traders, likely from ideological conviction rather than informational edge (Deleep, Lee, Bai, Suresh, Dhawan · SSRN).
+- "Decomposing Crowd Wisdom" Bayesian model on 292M trades across 327K Kalshi/Polymarket contracts: persistent underconfidence in political markets where prices are "chronically compressed toward 50%." Trade-size scale effect Δ = 0.53 [0.29, 0.75] on Kalshi politics doesn't replicate on Polymarket (Δ = 0.11 [-0.15, 0.39]) · platform-specific microstructure (Nam Anh Le).
+- 72M Kalshi trades · three biases: longshot bias (5¢ contracts win only 4.18% of the time · implied probability 5% vs realized 4.18%, ~16% relative overpricing), maker-taker asymmetry, YES/NO asymmetry. Finance most efficient (0.17% spread); crypto least (2.69%) (Ranger Global).
+- "Microstructure of Wealth Transfer": 72.1M Kalshi trades ($18.26B volume). Systematic wealth transfer from takers to makers averaging 1.12% excess on each side. Takers disproportionately buy YES longshots, accepting returns 64pp lower than equivalent NO positions. Transfer emerged only after Kalshi's Oct 2024 legal victory attracted professional algorithmic MMs. Market efficiency varies sharply by category: finance near-efficient; entertainment/media gaps >7pp (Jonathan Becker).
+- "Next Level" · DeFi primitive for borrowing against PM positions. Collateralization solves capital lock-up in long-dated markets; could correct persistent mispricings like longshot mispricing; opens composability with broader financial ecosystem. Flags liquidation risks unique to binary outcomes (keshav).
+- Becker microstructure paper (FULL_READ, partial · methodology + headline numbers): on Kalshi, traders accept expected values as low as 43 cents on the dollar for longshot contracts, "worse than a Las Vegas slot machine" (93¢ on the dollar). 72.1M trades, $18.26B volume. Mispricing metric δ_S = mean(outcomes) − mean(implied probabilities). Sports = 72% of Kalshi notional volume, politics 13%, crypto 5%. Three contributions: (1) confirms longshot bias and quantifies by price level, (2) decomposes returns by market role (taker vs maker), (3) identifies YES/NO asymmetry where takers disproportionately favor YES at longshot prices.
+
+## Notable Quotes
+
+> "5c contracts win only 4.18% of the time."
+> — *Ranger Global*
+
+> "Takers disproportionately buy YES longshots, accepting returns 64 percentage points lower than equivalent NO positions."
+> — *Jonathan Becker*
+
+> "Whales are not the sharpest participants: heavily capitalized traders systematically bleed expected value to small-order traders."
+> — *Deleep et al.*
+
+## Where It Matters
+
+Longshot bias is the single most exploitable systematic mispricing in PMs · and the 2026 research suggests it's been *underestimated* because it's been entangled with YES/NO labeling effects. If a PM builder wants to claim "accurate prices," longshot bias must be measured per-platform and per-category. For aggressive traders, the bias is renewable arbitrage (sell longshots, especially YES longshots, on Kalshi entertainment/media categories). For Dekant, the design implication is more subtle: a distribution-market curve doesn't inherit YES/NO labeling at all, so the entire longshot-vs-favorite asymmetry has to be re-examined in continuous space · but the same retail behavior (overpaying for tail outcomes) will reappear as overweighting the tails of the drawn distribution.
+
+## Related Concepts
+
+- **Yes bias** · confound; possibly the *real* explanation
+- **Calibration** · the diagnostic against which longshot bias is measured
+- **Retail flow / Toxic flow** · who pays the longshot tax
+- **Adverse selection** · what MMs are hedging against
+- **Market making** · why fading longshots is the obvious LP strategy
+- **Kelly criterion** · why rational participants can still produce longshot bias
+- **Liquidity fragmentation** · driver of cross-platform longshot price gaps
+
+## Sources
+
+- [Market Probabilities Are NOT Real Probabilities](https://x.com/Lihong062/status/2051017075758686622) — Lihong · May 3, 2026 [FULL_READ]
+- [Market Making In PMs Sucks](https://x.com/uselotusai/status/2046639095531614455) — Lotus · Apr 21, 2026 [FULL_READ]
+- [The Yes Bias Might Not Exist](https://x.com/functionspaceHQ/article/2037374271278989409) — functionSPACE · Mar 27, 2026 [FULL_READ]
+- [How Wise Is the Crowd? Bias and Edge in Prediction Markets](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6322678) — Deleep, Lee, Bai, Suresh, Dhawan · Feb 28, 2026 [FULL_READ]
+- [Decomposing Crowd Wisdom: Domain-Specific Calibration Dynamics in Prediction Markets](https://arxiv.org/abs/2602.19520) — Nam Anh Le · Feb 23, 2026 [FULL_READ · abstract]
+- [Prediction Market Biases Revealed in 72 Million Trades](https://x.com/Ranger_Global/article/2016867581743747447) — Ranger Global · Jan 29, 2026 [FULL_READ]
+- [The Microstructure of Wealth Transfer in Prediction Markets](https://www.jbecker.dev/research/prediction-market-microstructure) — Jonathan Becker · Jan 18, 2026 [FULL_READ]
+- [Prediction Markets: The Next Level](https://x.com/0xkeshav_/article/1970163909224169626) — keshav · Sep 23, 2025 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/lox-log-odds-excess-lateness.md b/.qoder/plans/pm-plan/theory_concepts/lox-log-odds-excess-lateness.md
new file mode 100644
index 0000000000..e387714847
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/lox-log-odds-excess-lateness.md
@@ -0,0 +1,53 @@
+# LOX (Log-Odds Excess Lateness)
+
+**Category:** Mechanism Design
+**Source:** [PM Atlas — LOX (Log-Odds Excess Lateness)](https://www.pmatlas.xyz/concepts/lox-log-odds-excess-lateness)
+
+---
+
+## Definition
+
+A metric measuring how much a forecaster's probability updates *lag behind* optimal Bayesian updating. Specifically, the gap between when information *should* have been priced in (under a hazard model of information arrival) and when it actually was. A diagnostic for whether late market volume reflects late *information* or late *participation* (driven by adverse selection).
+
+## Key Insights
+
+- **Origin:** 0xnagu (Jan 2026), *The Option Value of Waiting in Prediction Markets*. The only article on the on-page corpus, but it introduces a substantively new metric.
+- **The question LOX answers:** When prediction markets see clustered volume near resolution, is it because (a) information arrived late (a *hazard* effect · there was nothing to trade on earlier), or (b) early entry was punished by adverse selection (a *toxicity* effect · informed traders wait too)?
+- This is a structural disambiguation: hazard and toxicity look identical in volume time-series but have *opposite* implications for market design. Hazard is fine. Toxicity means the market maker is being adversely selected and is bleeding capital.
+- **LOX is computed from on-chain trades** · it measures whether new entrants *hesitate* more than volume alone would predict under a pure hazard model. A high LOX means traders are hanging back even when they have information, which signals adverse selection.
+- The decomposition framework is built around Bayesian updating: an optimal Bayesian forecaster updates their log-odds as new evidence arrives. If actual updates lag, the gap is "excess lateness" · log-odds excess lateness.
+- **Empirical finding (the boxing-markets puzzle):** boxing markets cluster with *news* markets, not with other *sports* markets, despite being categorized as sports. This is because boxing has information-driven late-resolving structure (pre-fight news, weigh-ins, last-minute fitness reports) more like a political event than a baseball game.
+- The framework provides a diagnostic for **adverse selection severity**: when LOX is high, market makers are losing money to informed traders, and the market needs design intervention (wider spreads, fee changes, batching).
+- The "option value of waiting" is the toxicity side: an informed trader is better off waiting for the spread to compress or for additional information to arrive before showing their hand. This option has value, and the value is observable through LOX.
+- LOX is a microstructure metric that fits the broader category of *adverse-selection diagnostics* (cf. Kyle's lambda, VPIN in equities) but is specifically constructed for the probability-space setting of prediction markets.
+- The metric is relevant for **market making mechanism design**: Avellaneda-Stoikov-style market making frameworks (XO Labs' work, covered under market-scoring-rules) need a measure of toxicity to set inventory-aware spreads · LOX is one candidate.
+
+## Notable Quotes
+
+> "Builds a formal framework to decompose why prediction markets have late volume: is it because information arrives late (hazard), or because early entry is punished by adverse selection (toxicity)?"
+> — *0xnagu, *The Option Value of Waiting in Prediction Markets**
+
+> "LOX, a metric computed from on-chain trades that measures whether new entrants hesitate more than volume alone would predict."
+> — **ibid.**
+
+> "Boxing markets cluster with news markets despite being categorized as sports."
+> — **ibid.**
+
+## Where It Matters
+
+LOX is part of the emerging quantitative-microstructure toolkit for prediction markets · the discipline of measuring *why* a market is behaving as it does, not just *what* it's doing. As prediction markets professionalize (DWF Ventures' "financial derivatives asset class" framing), metrics like LOX become the foundation for adaptive market-making, leverage pricing, and platform-level surveillance. For Dekant, the continuous-distribution setting opens an even richer LOX-equivalent space · lateness can be measured locally or against the entire belief curve.
+
+## Related Concepts
+
+- Adverse selection · LOX is a quantitative measure of adverse-selection severity
+- Market making · high LOX requires wider inventory-aware spreads
+- Liquidity provision · LPs use LOX as a toxicity signal
+- Information aggregation · LOX measures *how fast* aggregation happens
+- Insider trading · informed traders create high LOX
+- Price discovery · LOX measures the lag in discovery
+- Temporal arbitrage · LOX is conceptually adjacent (both measure timing-driven edge)
+
+## Sources
+
+- [The Option Value of Waiting in Prediction Markets](https://x.com/0xnagu/article/2016620973391564951) — 0xnagu · X
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/market-making.md b/.qoder/plans/pm-plan/theory_concepts/market-making.md
new file mode 100644
index 0000000000..6b343f4765
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/market-making.md
@@ -0,0 +1,121 @@
+# Market Making
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Market Making](https://www.pmatlas.xyz/concepts/market-making)
+
+---
+
+## Definition
+
+**Quick definition.** Continuously quoting bid and ask prices to facilitate trading, earning the spread while managing inventory risk. In prediction markets, classical market-making frameworks break because prices are bounded probabilities (0–1) and outcomes resolve discontinuously to one or zero.
+
+## Key Insights
+
+- semaji.eth's three-part series ranks market-making difficulty: Indian options (easy), crypto (medium), **prediction markets (legendary)** · gap risk is worse than any other asset class because informed counterparties can have near-perfect information and take out entire order books.
+- Sportsbook spreads tighten through counterparty-aware pricing (price-discriminate against winners); prediction markets compensate via **maker competition and order-book transparency**. PM prices are 100–300 bps better on liquid markets but long-tail markets have 10–50% spreads (taetaehoho).
+- Polymarket fast markets: 19 algo addresses extract consistent profits through paired trading and maker execution while **69% of retail traders lose money** (Kunal Doshi, "A Game of Volatility").
+- Rose-Berman ("Kalshi's Favorite Lie"): the exchange-isn't-your-counterparty pitch is a **bigger fiction than it admits**. Every Kalshi trade has a maker and a taker; takers cross spreads, makers harvest them, Kalshi takes a fee from both. A fair coin-flip ($0.50 contract) becomes a structural loss because (1) Kalshi's fee = 0.07 × C × P × (1-P) peaks at 50/50, and (2) you have to cross a maker spread to enter and a second maker spread to exit. By the time a 50/50 contract resolves, the maker has taken ~3.4% in expectation regardless of who wins.
+- Rose-Berman: maker dominance is **structural, not stylistic** · only the people with the fastest systems and best models can profitably maintain quotes; everyone else is takers paying tribute. Kalshi has no stake in *who* wins but has a strong stake in users *trading* · meaning losing-money entertainment, like a casino, is the business.
+- Kalshi has ~23 active market makers; top three provide 70% of election-contract liquidity. Markets those firms ignore are dead on arrival (Melee).
+- XO Labs tried Avellaneda–Stoikov in **logit space** and lost $1,114 in backtest; iterated to a probability-space engine with inventory-skewed spreads, volatility regime detection, and multi-outcome coordination · profitable at $453.
+- Ranger Global on YES/NO minting and merging invariant: depth can expand whenever matched counterparties exist; **probability-scaled dynamic fees** shrink near 0 and 1. PM traders systematically underreact to spot moves by 10–20%; **latency <100 ms captures 73% of arbitrage profits**.
+- January 2026 Polymarket XRP exploit paid **$231K on thin weekend liquidity** (Lotus); same contract trades at 58–67 cents simultaneously on different platforms (cross-venue fragmentation).
+- Kalshi reprices faster than Polymarket (**median 7-second lead**) but Polymarket needs **3–4x more volume** to move prices comparably · Kyle-style market-impact analysis shows the speed-vs-depth tradeoff between CLOB-on-prem and on-chain order books. The Pantera/Sui dataset specifics: Kalshi leads initial repricing in **80% of large moves**; for 20pp+ swings Kalshi leads in 85.7%; Polymarket's median Kyle-LD is 10⁶·⁹⁶ vs. Kalshi's 10⁶·⁴² (3-4× more notional needed to move prices over 60s).
+- Pantera/Sui's hypothesis on why Polymarket has deeper concentrated liquidity despite lower volume: Polymarket's **~3-second delay on marketable orders in live sports** reduces adverse selection for LPs, letting them quote tighter prices with lower risk. Kalshi's immediate execution exposes resting liquidity to faster information shocks.
+- Becker analysis of 72.1M Kalshi trades / $18.26B volume: systematic **wealth transfer from takers to makers averages 1.12% excess returns on each side**; takers disproportionately buy YES longshots, accepting -64-percentage-point worse returns vs. equivalent NO positions. Variation by category is enormous: Finance has a 0.17pp gap (efficient), Sports 2.23pp, Crypto 2.69pp, Entertainment 4.79pp, Media 7.28pp, World Events 7.32pp.
+- Becker's **inflection point**: before October 2024 (Kalshi's legal victory over CFTC + Q4 election boom), the maker-taker gap was *inverted* · takers earned +2.0% and makers lost 2.0%. Volume went from $30M in Q3 2024 to $820M in Q4 2024. The arrival of sophisticated algorithmic makers was the cause; "wealth transfer from takers to makers is not inherent to PM microstructure; it requires sophisticated market makers, and sophisticated market makers require sufficient volume to justify participation."
+- Della Vedova on 222M Polymarket trades: **forecasting accuracy does not predict profitability**. Traders who pick the right side still lose money because they arrive late; automated traders with near-random directional skill profit by **paying 2.52 cents less per contract**.
+- Polymarket has structurally converged with a derivatives exchange: $23.7M in taker fees in 83 days, maker-rebate fee model, bots controlling 55–62% of volume (Dune).
+- Avoiding toxic flow vs. capturing retail flow is the central tension. semaji.eth uses the Jane Street coffee question to illustrate: conditional on someone trading with you, you should be **less confident** your trade was good.
+- 4casters: raising fees from 0.5% to 0.75% had no material impact on volume · sports bettors are **less price-sensitive** than assumed. The sportsbook square-vs-sharp distinction maps onto Kalshi (square, 3.5% fee) vs. sharp markets that will thrive offshore.
+- "Bond mule" strategy (outpxce) locks capital for small premiums on near-resolution markets · a market-making variant that monetizes time decay rather than spread.
+- Daedalus Research's Black-Scholes-for-PM paper proposes a logit jump-diffusion stochastic kernel that exposes belief volatility, jump intensity, and correlation as quotable risk factors.
+- Human Invariant proposes **priority batch auctions** (cancels → makers → takers) to shift competition from latency to price accuracy, letting market makers tighten quotes. FCFS creates a latency war and a co-location arms race; if cancels are prioritized ahead of makers, MMs can react to news before snipers fill at stale prices, which directly tightens spreads.
+- sybilpm's MM expected-profit equation: **E[π] = (s/2 · V(s)) − P_news · L_snipe**. In traditional markets L_snipe is a few ticks; in PMs it's 80 cents on the dollar. When P_news is high, the sniping term dominates and the MM has two options: widen s, or pull quotes · both make the market worse.
+- sybilpm on the "liquidity mirage": at 3am Tuesday a reasonable order book is visible, but it'll vanish the instant news drops. Polymarket and Kalshi spend millions on liquidity rewards that, in practice, route retail LPs into being "exit liquidity for snipers" · rewards never sized to cover 80-cents-per-contract gap risk.
+- Sethi/Sirolly wash-trading paper: market makers' network signature is **heterophily** (they trade with many random counterparties), while wash-traders show **homophily** (they trade with their collusive clique). Their iterative network-redistribution algorithm flagged a 200-wallet cluster (all names starting "MAY") that did 116M shares / $113M volume but netted a $57 aggregate loss · distinguishing wash trading from MM activity that superficially looks similar.
+- Bender (Citizens / Juice Reel data, Mar 2026): the **median PM user ROI is -8%** (vs. -5% for legal U.S. sportsbook bettors), but users trading >$500K since July 2025 had **median +2.6% ROI** · consistent with sharp-bettor returns. Drivers: prediction markets are open to all (sportsbooks ban sharps), and sharper competition means weaker median outcomes. One professional bettor: "We all want to be on the other side of the public; that's the dream. Being a market maker is highly attractive. We all want to be DraftKings and FanDuel."
+
+## Notable Quotes
+
+> "Conditional on someone trading with you, you should be less confident your trade was good."
+> — *semaji.eth, "The Liquidity Problem in Prediction Markets, Part I"*
+
+> "Gap risk is effectively worse than any other asset class because informed counterparties can have near-perfect information and take out entire order books."
+> — *semaji.eth*
+
+> "Polymarket has structurally converged with a derivatives exchange."
+> — *Dune, "Anatomy of Polymarket's Fastest Markets"*
+
+> "Forecasting accuracy does not predict profitability."
+> — *Della Vedova*
+
+> "You weren't providing liquidity to a forecaster. You were exit liquidity for a bot."
+> — *sybilpm, on MMs being snapped during news jumps*
+
+> "The wealth transfer from takers to makers is not inherent to prediction market microstructure; it requires sophisticated market makers, and sophisticated market makers require sufficient volume to justify participation."
+> — *Becker*
+
+> "Wash traders trade with other wash traders in their collusive clique, while market makers neither know nor care who their counterparties are."
+> — *Sethi/Sirolly*
+
+## Where It Matters
+
+Market making is the single profession that determines whether a prediction market exists at all. Because no retail trader will repeatedly cross a >20% spread, the supply of professional MMs essentially *defines* which categories of question can be traded. The empirical record (Becker, Della Vedova, Ranger Global) shows that maker P&L overwhelmingly comes from execution edge and fee rebates, not directional accuracy · which means the platform's job is less about onboarding "smart traders" and more about engineering microstructure (CLOB rules, batched auctions, maker rebates, dynamic spreads) that lets makers stay solvent.
+
+## Related Concepts
+
+- **Liquidity provision** · capital supply side; MMs are the active counterpart.
+- **Adverse selection / toxic flow** · the binding constraint on MM profitability.
+- **Bid-ask spread** · the unit economics of MMing.
+- **Retail flow** · the desirable counterparty.
+- **Gap risk** · why classical MM models break in binaries.
+- **Batched auctions / continuous double auction** · alternative microstructures designed to protect MMs.
+- **Execution quality** · what MMs sell.
+- **Kelly criterion** · sizing under information edge, applied to inventory management.
+- **LMSR / market scoring rules** · automated MM substitute.
+- **Hedging** · MMs require it; PM-native hedging substrate is missing for many binaries.
+
+## Sources
+
+- [Sportsbooks vs Prediction Markets - Market Structure and Its Effects](https://x.com/0xtaetaehoho/status/2054215076471873575) — taetaehoho · May 12, 2026
+- [A Game of Volatility](https://x.com/Kunallegendd/status/2054191944944038084) — Kunal Doshi · May 12, 2026
+- [Kalshi's Favorite Lie](https://howgamblingworks.substack.com/p/kalshis-favorite-lie) — Isaac Rose-Berman · Apr 29, 2026
+- [Why Every Prediction-Market Terminal Will Fail (and the Two That Won't)](https://x.com/0xCryptoNomads/status/2048664550225170605) — cryptonomads · Apr 27, 2026
+- [The Problem With CLOBs](https://x.com/meleemarkets/status/2046318159225897289) — Melee · Apr 21, 2026
+- [Anatomy Of A New Asset Class I: How Markets Turn Capital Into Probability](https://x.com/Ranger_Global/status/2046547375649427572) — Ranger Global · Apr 21, 2026
+- [Market Making In PMs Sucks](https://x.com/uselotusai/status/2046639095531614455) — Lotus · Apr 21, 2026
+- [What Most People Get Wrong About Prediction Markets](https://x.com/dgt10011/status/2045952603301851173) — Jeff Park · Apr 20, 2026
+- [Market Making for Prediction Markets: A Probability-Space Approach](https://x.com/xolabs_/status/2045161249588269257) — XO Labs · Apr 17, 2026
+- [Faster, Shorter, More Automated: Anatomy of Polymarket's Fastest Markets](https://x.com/Dune/status/2044045952730726863) — Dune · Apr 14, 2026
+- [The Two Kinds of Prediction Markets](https://x.com/4castersbet/status/2042268072266920213) — 4casters · Apr 9, 2026
+- [Leverage in Prediction Markets](https://x.com/0xMims/status/2041498106181627918) — Darren · Apr 7, 2026
+- [Parimutuel Prediction Markets](https://x.com/meleemarkets/status/2041244791716110409) — Melee · Apr 6, 2026
+- [Is Polymarket a Retail Product or a Pro Trading Venue?](https://x.com/sealaunch_/article/2037228250116522082) — sealaunch intelligence · Mar 27, 2026
+- [Prediction Markets vs. Sports Betting: Market Dynamics, ROI by Cohorts, and Competitive Implications](https://citizens.bluematrix.com/docs/pdf/3beb4505-5a8b-49da-89df-ebdb104ebea9.pdf) — Jordan Bender · Mar 23, 2026
+- [Noisy Traders Are Not Dumb Money](https://x.com/functionspaceHQ/article/2032397043579462028) — functionSPACE · Mar 13, 2026
+- [Polymarket Doesn't Have a Money Problem. It Has a Plumbing Problem.](https://x.com/ZEITFinance/article/2031500989576949770) — @allquantor · Mar 11, 2026
+- [The Sniper's Tax](https://sybilpm.substack.com/p/the-snipers-tax) — sybilpm · Mar 8, 2026
+- [Turning Probability into Assets: A Look Ahead at Prediction Market Agents](https://x.com/0xjacobzhao/article/2029229969914904694) — Jacob Zhao · Mar 5, 2026
+- [23 Reasons Prediction Markets Are Broken Today](https://x.com/linfluence/status/2026757563518431696) — Alexander Lin · Feb 26, 2026
+- [Your Hedge Fund's Sharpe Ratio Is a Lie. Prediction Markets Are the Only Place It Can't Hide.](https://x.com/gemchange_ltd/article/2026300422483271733) — gemchanger · Feb 25, 2026
+- [Minimum Viable Liquidity](https://fiftycentdollars.substack.com/p/minimum-viable-liquidity) — Adhi Rajaprabhakaran · Feb 24, 2026
+- [How to Solve Insider Trading in Prediction Markets](https://www.dopaminemarkets.com/p/how-to-solve-insider-trading-in-prediction) — Shreyas Hariharan · Feb 10, 2026
+- [Who Profits from Prediction Markets? Execution, Not Information](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6191618) — Joshua Della Vedova · Feb 7, 2026
+- [The Super Bowl of Prediction Markets: Kalshi and Polymarket's Battle for Price vs Liquidity](https://panteraresearchlab.xyz/research/the-super-bowl-of-prediction-markets-kalshi-and-polymarkets-battle-for-price-vs-liquidity/) — Ally Zach, Danning Sui · Feb 5, 2026
+- [Prediction Market Biases Revealed in 72 Million Trades](https://x.com/Ranger_Global/article/2016867581743747447) — Ranger Global · Jan 29, 2026
+- [The Option Value of Waiting in Prediction Markets](https://x.com/0xnagu/article/2016620973391564951) — 0xnagu · Jan 28, 2026
+- [On Prediction Markets](https://x.com/outpxce/status/2013397772074905950) — outpxce · Jan 20, 2026
+- [The Microstructure of Wealth Transfer in Prediction Markets](https://www.jbecker.dev/research/prediction-market-microstructure) — Jonathan Becker · Jan 18, 2026
+- [Liquidity in Prediction Markets and the Rise of a New Asset Class](https://x.com/Ranger_Global/status/2008177990971122003) — Ranger Global · Jan 5, 2026
+- [The Case For Alternative Ordering Mechanisms in Prediction Markets](https://www.humaninvariant.com/blog/pm-ordering) — Human Invariant · Nov 12, 2025
+- [The Detection of Wash Trading](https://rajivsethi.substack.com/p/the-detection-of-wash-trading) — Rajiv Sethi · Nov 12, 2025
+- [Toward Black–Scholes for Prediction Markets](https://arxiv.org/pdf/2510.15205) — Shaw Dalen, Daedalus Research Team · Oct 17, 2025
+- [The Liquidity Problem in Prediction Markets, Part II: Adverse Selection in Prediction Markets](https://x.com/semajeth/status/1975211193418563697) — semaji.eth · Oct 6, 2025
+- [Who Are You Really Playing Against?](https://x.com/thejay/article/1968392782998835518) — Jay Malavia · Sep 18, 2025
+- [The Liquidity Problem in Prediction Markets, Part I: Adverse Selection and Market Making](https://x.com/semajeth/status/1967598604006134024) — semaji.eth · Sep 15, 2025
+- [The Liquidity Problem in Prediction Markets: Part 0](https://x.com/semajeth/status/1966144230826414225) — semaji.eth · Sep 11, 2025
+- [Predicting Our Own Demise](https://reducibleerrors.com/prediction-markets/) — Agustin Lebron · Aug 17, 2025
+- [No, You Can't Bet on Everything and That's Okay](https://rnikhil.com/2024/12/18/prediction-market-crypto) — Nikhil R · Dec 18, 2024
+- [Why Prediction Markets Aren't Popular](https://worksinprogress.co/issue/why-prediction-markets-arent-popular/) — Nick Whitaker, J. Zachary Mazlish · May 17, 2024
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/market-manipulation.md b/.qoder/plans/pm-plan/theory_concepts/market-manipulation.md
new file mode 100644
index 0000000000..e1bb83b335
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/market-manipulation.md
@@ -0,0 +1,93 @@
+# Market Manipulation
+
+**Category:** Mechanism Design
+**Source:** [PM Atlas — Market Manipulation](https://www.pmatlas.xyz/concepts/market-manipulation)
+
+---
+
+## Definition
+
+Deliberately trading to distort prices away from true probabilities for strategic or financial gain. In prediction markets, manipulation has unusually high stakes because prices double as public information signals, so distorted prices contaminate journalism, policy, and downstream financial decisions.
+
+## Key Insights
+
+- Rasooly & Rozzi's field experiment (arXiv 2503.03312) "involves randomly shocking prices across 817 separate markets; we then collect hourly price data to examine whether the effects of these shocks persist over time" · manipulation effects "are visible even 60 days after they have occurred" but "as predicted by our model, the effects of the manipulations somewhat fade over time." Markets with more traders, greater volume, and external probability estimates are harder to manipulate.
+- Rajiv Sethi's wash-trading paper (Sirolly, Ma, Kanoria, Sethi) introduces a modular detection algorithm with two stages: (1) initialization · each wallet receives a score based on "propensity to open and close positions repeatedly"; (2) iterative network-based redistribution · "we update each wallet's score based on the scores of its volume-weighted counterparties" until convergence. Wash traders exhibit *homophily* · they trade only within their collusive clique; market makers exhibit *heterophily* · "market makers neither know nor care who their counterparties are."
+- Concrete wash-trading cluster found: "MAY20, MAY175, and MAY176 ... part of a cluster of 200 with names all starting with MAY, trading almost exclusively with each other. These wallets collectively traded over 116 million shares and generated more than 113 million in dollar volume, but ended up with an aggregate loss of just $57.86." Wallet MAY117 alone "bought and sold over a million shares across 33 markets over several months and ended up with profits of precisely zero."
+- Sethi's example trade pattern: a trader buys, then sells at "a price that is a tenth of a penny lower, resulting in a guaranteed loss" · accepting modest losses in exchange for boosting recorded volume. Such activity is "prohibited by law on regulated exchanges in the United States, and can be subject to significant sanctions."
+- Wash-trading detection is robust to parameter changes but vulnerable to strategic adaptation · Sethi flags that any published algorithm "is likely to result in strategic responses by wash traders that better allow them to evade detection."
+- Sethi *Trading on Violence*: six Polymarket wallets opened shortly before US strikes on Iran, bought large positions, "banked $1.2 million in profits in a matter of hours." Kalshi has run "more than 200 investigations into insider trading, with a dozen cases currently active." KYC at Kalshi is "primarily designed to address money laundering and terrorism financing, but also facilitates the detection of insider trading."
+- The Polymarket / Kalshi structural asymmetry on insider trading: Polymarket trades in USDC with on-chain transparency but pseudonymous wallets · "Insider trading under these conditions becomes easier to notice but harder to prevent."
+- Same-named contracts resolve differently: Polymarket's "Khamenei out as Supreme Leader" resolved YES on his death, while the corresponding Kalshi contract "resolved to the price at last trade prior to his death." Kalshi CEO position: "as a federally regulated prediction market, we are required and feel it is important not to enable direct profiting from war, assassination, terrorism, or other violent outcomes."
+- Hanson (Overcoming Bias) frames prediction-market regulation around six info-institution failure modes · (A) reveal info better kept secret, (B) reveal secrets, (C) waste time/money, (D) misleading contributions, (E) change the world to get favorable treatment, (F) reward participants unequally · and argues all info institutions (journalism, academia, prediction markets) should face the same scrutiny.
+- Hanson on insider trading: "for stocks ... at public firms announcements, half of the price change happens beforehand, and half of that is from insider trading. We shouldn't expect prediction market rules to succeed much better, or to result in much worse harms."
+- Hanson on manipulation resistance: "speculative markets are actually far more resistant to manipulation than other info institutions. When traders expect more efforts to manipulate a price, they respond so that prices on average become MORE accurate. Also, in head-to-head comparisons with other info institutions, with the same question, time, participants, and resources, speculative markets have been consistently about as accurate or much more accurate."
+- Hanson's life-insurance analogy on (E): "life insurance has big enough stakes and easy enough personal influence that we reasonably regulate it to prevent murder for money. But we see almost no cases of traders successfully sabotaging firms to profit from stock trades."
+- Gunitsky (*Priced to Kill*) reports an IDF major's testimony: "The entire squadron is on Polymarket. The entire air force is betting." A "study of every transaction on Polymarket found only three percent of accounts are responsible for almost all price discovery. The remaining suckers generate volume but no information."
+- Gunitsky on conflict-market mechanics: pair of Israeli traders "split $160,000 in winnings" on strike timing, then bet again on the 12-day war and a Yemen strike. A US Army special forces soldier was indicted for betting on Maduro's removal "before Trump announced the capture, which paid out over $400,000."
+- The "hair dryer problem": Polymarket settled Paris temperature bets via "an unguarded sensor near the Charles de Gaulle airport." A bettor bought long-shot abnormally-high-temperature contracts; "the sensor saw a four-degree spike before dropping back to normal. No neighboring station registered anything similar. The contract resolved in the bettor's favor." The bettor collected $14,000 then $20,000 again on April 15. France's meteorological agency filed a criminal complaint. Polymarket switched data sources but did not refund.
+- The "bribery escalation" case: Polymarket had $14M wagered on Iran strikes against Israel that day. Reporter Emanuel Fabian was pressured to recharacterize an impact as an interception. "'You have exactly half an hour to correct your attempt at influence,' said one message ... 'You will discover enemies who will be willing to pay anything to make your life miserable.'"
+- John Sides (Good Authority, Dec 18 2025) summarizes Clinton & Huang's analysis of "more than 2,500 political prediction markets" across Iowa Electronic Markets, Kalshi, PredictIt, and Polymarket "during the final five weeks of the 2024 U.S. presidential campaign involving more than two billion dollars in transactions." Headline result: "While 93% of PredictIt markets correctly predicted outcomes better than chance, accuracy fell to 78% on Kalshi and 67% on Polymarket." Sides notes Kalshi's recently inked CNN and CNBC data-partnership deal as the reason caution about these accuracy gaps matters now.
+- Clinton & Huang on (in)efficiency: "prices for identical contracts diverged across exchanges, daily price changes were weakly correlated or negatively autocorrelated, and arbitrage opportunities peaked in the final two weeks before Election Day." Persistent cross-platform arbitrage *increased* into resolution rather than decreasing as one would expect of an efficient market.
+- Andy Hall's hypothetical CNN-CNBC-Kalshi scenario (cited by Sides): a market spike with no apparent news source becomes a self-validating news event, with the manipulation accusation itself becoming part of the narrative · illustrating how *media partnerships* multiply the political cost of even unprovable manipulation.
+- Rajaprabhakaran (FiftyCent Dollars) makes the *opposite* case: "Me putting a few hundred dollars on a prediction market doesn't buy any ads, knock on any doors, or change a single vote." Critics of manipulation, in his account, conflate prediction markets with stocks (which *have* causal channels via cost of capital) and bond markets (which firing Liz Truss "actually undermines her argument" · bonds had a direct causal channel via gilts' yield).
+
+## Notable Quotes
+
+> "We find that prediction markets can be manipulated: the effects of our trades are visible even 60 days after they have occurred. However, as predicted by our model, the effects of the manipulations somewhat fade over time. Markets with more traders, greater trading volume, and an external source of probability estimates are harder to manipulate."
+> — *Rasooly & Rozzi, *How Manipulable Are Prediction Markets?**
+
+> "Wash traders trade with other wash traders in their collusive clique, while market makers neither know nor care who their counterparties are. In fact, market makers seldom trade with other market makers."
+> — *Rajiv Sethi, *The Detection of Wash Trading**
+
+> "These wallets collectively traded over 116 million shares and generated more than 113 million in dollar volume, but ended up with an aggregate loss of just $57.86."
+> — *Sethi, *ibid.**
+
+> "Speculative markets are actually far more resistant to manipulation than other info institutions. When traders expect more efforts to manipulate a price, they respond so that prices on average become MORE accurate."
+> — *Robin Hanson, *On Prediction Market Regulation**
+
+> "The entire squadron is on Polymarket. The entire air force is betting."
+> — *IDF Air Force major, quoted in Gunitsky, *Priced to Kill**
+
+> "Polymarket settled daily temperature bets for Paris using an unguarded sensor near the Charles de Gaulle airport. On April 6, someone bought the long-shot contract for abnormally high temperatures. Later that evening, the sensor saw a four-degree spike before dropping back to normal. No neighboring station registered anything similar."
+> — *Gunitsky, *ibid.**
+
+> "While 93% of PredictIt markets correctly predicted outcomes better than chance, accuracy fell to 78% on Kalshi and 67% on Polymarket."
+> — *Clinton & Huang, summarized by Sides, *The Perils of Election Prediction Markets**
+
+> "Prices for identical contracts diverged across exchanges, daily price changes were weakly correlated or negatively autocorrelated, and arbitrage opportunities peaked in the final two weeks before Election Day."
+> — *Clinton & Huang, quoted in Sides, *ibid.**
+
+> "Markets that resolve on whether someone stays in power, speaks publicly, shows up somewhere, or holds a certain job carry implicit incentives connected to their continued existence. Nearly every such market dealing with control, decision, or public appearance is technically an assassination market."
+> — *Guillory & Zimmermann, quoted in Sethi, *Trading on Violence**
+
+## Where It Matters
+
+Manipulation resistance is the gating constraint for prediction markets being treated as public-good information infrastructure rather than gambling. The same property that makes them useful (prices are read as truth) makes them attractive to attack, and most current architectures (binary order books, optimistic oracles, pseudonymous wallets) have weak structural defenses. The 2024-election data shows that high volume does *not* automatically buy accuracy · Polymarket's largest US-election market was less accurate than PredictIt's much smaller market, and arbitrage opportunities *grew* into the resolution window. For continuous markets like Dekant, the manipulation surface shifts from single-tick attacks to curve-shape distortion · relevant when designing how much capital is needed to move a kernel.
+
+## Related Concepts
+
+- Wash trading · direct subtype (synthetic volume without economic risk)
+- Insider trading · adverse-selection cousin of manipulation
+- Reflexivity · when manipulation actively changes the outcome being predicted
+- Incentive compatibility · manipulation is the negative space of incentive compatibility
+- Oracle design / dispute resolution · resolution manipulation is a distinct attack surface from price manipulation
+- Self-resolving markets · proposed as a structural defense against oracle capture
+
+## Sources
+
+- [Priced to Kill](https://hegemon.substack.com/p/polymarket-is-making-wars-more-dangerous) — Seva Gunitsky · Hegemon Substack
+- [On Prediction Market Regulation](https://www.overcomingbias.com/p/on-prediction-market-regulation) — Robin Hanson · Overcoming Bias
+- [You Don't Hate Prediction Markets. You Hate Capitalism.](https://x.com/noahlitvin/status/2048527516587966660) — Noah Litvin · X
+- [The Hidden Risk Of Prediction Markets: 14 Resolution Failures That Cost $500M](https://x.com/xomarket/status/2046924302952640934) — XO Market · X
+- [When Prediction Markets Need Stake](https://x.com/alanwu/status/2044049214393524393) — alan · X
+- [Are Prediction Markets Decaying or Evolving?](https://x.com/_drNeyo/article/2029205512391172364) — Eniola · X
+- [Trading on Violence](https://rajivsethi.substack.com/p/trading-on-violence) — Rajiv Sethi · Substack
+- [On War Markets](https://x.com/probaaron/article/2027765452689014822) — aaron · X
+- [Polymarket Is Not a Casino. Why Prediction Markets Are Finance, Not Gambling](https://x.com/13_niakris/article/2025614474263093627) — Niakris · X
+- [Leverage Fixes Prediction Markets: The Case for Why 10x Is Safer Than 1x](https://x.com/njokuScript/article/2022351299547689325) — njokuScript · X
+- [Assassination Semantics: Why Every Market Carries the Risk of Violence](https://x.com/BetBreakNews/article/2008587654158340126) — Sean Guillory & Dan Zimmermann · X
+- [The Perils of Election Prediction Markets](https://goodauthority.org/news/the-perils-of-election-prediction-markets/) — John Sides · Good Authority
+- [The Detection of Wash Trading](https://rajivsethi.substack.com/p/the-detection-of-wash-trading) — Rajiv Sethi · Substack
+- [How Manipulable Are Prediction Markets?](https://arxiv.org/abs/2503.03312) — Itzhak Rasooly, Roberto Rozzi · arXiv
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/market-scoring-rules.md b/.qoder/plans/pm-plan/theory_concepts/market-scoring-rules.md
new file mode 100644
index 0000000000..7d3610d01c
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/market-scoring-rules.md
@@ -0,0 +1,61 @@
+# Market Scoring Rules
+
+**Category:** Mechanism Design
+**Source:** [PM Atlas — Market Scoring Rules](https://www.pmatlas.xyz/concepts/market-scoring-rules)
+
+---
+
+## Definition
+
+A class of automated market maker mechanisms that subsidize trade by penalizing a market maker according to a proper scoring rule. Traders profit by moving prices closer to their beliefs, ensuring the market maker absorbs losses in exchange for eliciting honest probability estimates. LMSR is the most widely used instance.
+
+## Key Insights
+
+- Market scoring rules (MSRs) generalize *single-shot* proper scoring rules into a *sequential trading* mechanism: each trader pays to change the market's reported probability, and the market maker's payoff is the proper-scoring-rule difference between consecutive reports.
+- XO Labs (Apr 2026) · *Market Making for Prediction Markets: A Probability-Space Approach.* Adapts the Avellaneda-Stoikov market-making framework to prediction markets. **Classical models fail** because prediction market prices are bounded probabilities (0 to 1), not unbounded asset prices, creating non-constant volatility and guaranteed terminal convergence at 0 or 1.
+- XO Labs iteration sequence: a logit-space transformation lost $1,114 in backtest, then a probability-space engine with inventory-skewed spreads, volatility regime detection, and multi-outcome coordination turned profitable at $453. The piece provides the full mathematical framework and open problems.
+- Jo Lim's Strait of Hormuz analysis (Mar 2026) · binary order-book markets hit an architectural ceiling on granular multi-outcome risk. LMSR / CLMSR (continuous LMSR) offers protocol-native liquidity, coherent pricing, and capital efficiency for events without underlying assets. Walks through a WTI crude oil scenario where scoring-rule markets reward *precise thesis expression* over simple directional bets.
+- The Jo Lim piece is the clearest articulation in the corpus of why MSR/CLMSR is needed: traditional options solve granularity for tradable assets, but markets without underlyings (geopolitical events, weather, election margins) need a protocol-native pricing mechanism · that mechanism is an MSR.
+- Powell, Hanson, Laskey & Twardy (2013) experimentally validate that MSR-based combinatorial structures improve forecasting accuracy and calibration vs flat (single-question) structures. Their DAGGRE platform used LMSR over a Bayesian-network-defined state space with explicit conditional events ("A given B") and Boolean combinations. The paper notes: "Economic theory suggests that the greater expressivity of combinatorial prediction markets should improve accuracy by capturing dependencies among related questions" · confirmed empirically on a murder-mystery task with Bayesian-network ground truth.
+- MSR's key economic property: the market maker's *total loss is bounded* across all trades · for LMSR it's `b · ln(n)` for n outcomes. This is what makes subsidized long-tail markets viable without infinite capital exposure.
+- The classic LMSR cost function `C(q) = b · log(Σ exp(q_i / b))` is identical to the softmax · gemchanger's piece links this to the entire ML / Bayesian regime detection toolkit.
+- An MSR is *not* the same as a CLOB. Polymarket migrated *away* from MSR-based AMMs to CLOB once binary markets had sufficient depth (Melee). The MSR-vs-CLOB question is settled for liquid binaries (CLOB wins) but open for thin, long-tail, and continuous markets.
+- The XO Labs work surfaces an underappreciated subtlety: probability-bounded prices mean *standard market-making intuitions don't transfer* · inventory risk, volatility, and convergence behavior are qualitatively different from FX or equities.
+- Combinatorial MSRs (Hanson 2003+) elicit joint probabilities, not just marginals · but the experimental literature is mostly limited to small numbers of base events because the state space explodes.
+
+## Notable Quotes
+
+> "Classical models fail because prediction market prices are bounded probabilities (0 to 1) rather than unbounded asset prices, creating non-constant volatility and guaranteed terminal convergence."
+> — *XO Labs, *Market Making for Prediction Markets**
+
+> "Automated market scoring rules (LMSR/CLMSR) offer protocol-native liquidity, coherent pricing, and capital efficiency for events without underlying assets."
+> — *Jo Lim, *The World's Biggest Risk Event Just Exposed Prediction Markets' Biggest Gap**
+
+> "Scoring-rule markets reward precise thesis expression over simple directional bets."
+> — *Jo Lim, *ibid.**
+
+> "Economic theory suggests that the greater expressivity of combinatorial prediction markets should improve accuracy by capturing dependencies among related questions."
+> — *Powell, Hanson, Laskey & Twardy, *Combinatorial Prediction Markets: An Experimental Study**
+
+## Where It Matters
+
+Market scoring rules are the *only* mechanism class designed from first principles for prediction markets · every other AMM is borrowed from spot-token DEXes and breaks under bounded probabilities. The category is moving back toward MSRs (CLMSR, kernel scoring rules) for the use cases CLOB cannot serve well: thin markets, long-tail questions, and continuous-distribution outcomes. Dekant's L2-norm CFAMM is in this lineage · a scoring-rule-style AMM for continuous distributions.
+
+## Related Concepts
+
+- Proper scoring rules · the formal foundation MSRs are built on
+- LMSR · the canonical MSR (logarithmic case)
+- Combinatorial prediction markets · MSRs scale to richer outcome spaces
+- Order book · the alternative microstructure (Polymarket post-2022)
+- Market making · MSRs are *automated* market makers
+- Liquidity provision · MSR's bounded loss enables subsidized liquidity
+- Multi-outcome markets · MSRs handle n>2 outcomes natively
+- Adverse selection · informed traders extract from the MSR subsidy
+- Bid-ask spread · MSR has a structural spread tied to the `b` parameter
+
+## Sources
+
+- [Market Making for Prediction Markets: A Probability-Space Approach](https://x.com/xolabs_/status/2045161249588269257) — XO Labs · X
+- [The World's Biggest Risk Event Just Exposed Prediction Markets' Biggest Gap](https://x.com/jolimmmm/article/2036119259420807216) — Jo Lim · X
+- [Combinatorial Prediction Markets: An Experimental Study](https://link.springer.com/chapter/10.1007/978-3-642-40381-1_22) — Powell, Hanson, Laskey & Twardy · Springer
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/market-structure.md b/.qoder/plans/pm-plan/theory_concepts/market-structure.md
new file mode 100644
index 0000000000..bbbf91ed70
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/market-structure.md
@@ -0,0 +1,53 @@
+# Market structure
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — Market structure](https://www.pmatlas.xyz/concepts/market-structure)
+
+---
+
+## Definition
+
+**Quick definition.** The organization and stratification of PM platforms into distinct layers · rails (settlement/execution), wrappers (distribution/user access), and product archetypes · and the competitive dynamics between them.
+
+## Key Insights
+
+- Blocmates' stack thesis (per their *State of Prediction Markets* report) is the canonical articulation: arguing over which of Kalshi or Polymarket "wins" misses the picture · PMs are becoming real infrastructure, and the eventual winners will be decided by who occupies key product archetypes as the stack stratifies, not by a winner-takes-all landslide.
+- The three-layer stratification: (1) Rails · crypto-native (Polymarket) optimize for breadth/speed-to-list; regulated (Kalshi) optimize for settlement credibility. (2) Wrappers · execution venues (Coinbase, Robinhood) turn event contracts into a mainstream instrument shelf. (3) Product archetypes · standardized binaries (Nasdaq, Cboe), continuous markets (Dekant), parlays, perps.
+- TradFi incumbents push standardized binaries that fit existing market structure and compliance regimes. Crypto-native venues push faster experimentation. Wrappers sit between and capture user attention.
+- MO's "Option Markets vs Binary Markets vs Continuous Markets" frames the architectural choice as three competing structures: vanilla options structures (the Black-Scholes era's tool), binary PMs (Kalshi/Polymarket), and continuous PMs (Dekant). Each serves a different range of belief shapes; continuous payout curves replace the workarounds traders use today.
+- The implied market-structure thesis: each archetype lives in a different competitive equilibrium. Binary markets coexist with options because they serve different hedging needs; continuous markets don't yet exist on-chain because the math (partition of unity, smooth kernels, L2-norm CFAMM) is hard.
+- The volume baseline: by Q4 2025, Polymarket and Kalshi had thousands of markets that could absorb a $10K trade within 1% of price impact (blocmates). Compared to early 2024 when a sneeze moved prices 10 cents.
+- Weekly PM volume is now measured in billions across rails. The market structure question is which layer captures the rent: rails (matching engines), wrappers (distribution), or product archetypes (specialized formats).
+- The DCo Robinhood thesis is essentially a market-structure prediction: wrappers eat rails over time, because wrappers control the user. Kalshi and Polymarket survive only if they become the dominant rails layer beneath wrappers, not the consumer destination.
+- Kaviish's value-flow analysis: distribution platforms capture most value as they vertically integrate into exchange infrastructure (Robinhood's Rothera JV is the template). Pure rails lose pricing power; wrappers + integrated rails win.
+- Jake Nyquist's 7 axes (product quality, asset variety, capital efficiency, oracle reliability, liquidity provision, regulatory compliance, vertical vs horizontal strategy) is the differentiation map. Each axis defines a niche within the stack stratification.
+- Probability-as-data is becoming a parallel business line (blocmates section, alluded). PM venues sell market data feeds to media, traders, and AI systems · a B2B revenue stream independent of trading volume.
+- The Dune/Polymarket fast-markets data ($23.7M in taker fees in 83 days via maker-rebate model on 5-min markets) is evidence that PM rails are structurally converging with derivatives exchanges. The market structure is no longer "betting on events" · it's "high-frequency event-conditional derivatives."
+
+## Notable Quotes
+
+> "Option Markets vs Binary Markets vs Continuous Markets"
+> — *title of MO/@embrron's analysis arguing continuous payout curves serve belief shapes that options and binaries each handle only partially.*
+
+> "Blocmates' "stack stratification" thesis (per *The State of Prediction Markets*): the winners are decided by who occupies key product archetypes"
+> — *rails, wrappers, archetypes · not by venue duels. (Quote not directly verified in our fetched body.)*
+
+## Where It Matters
+
+Market structure is the macro frame that contains everything else. Without understanding which layer owns the user, the value flow, and the regulatory exposure, individual product decisions read as tactical noise. For Dekant: continuous-curve markets are a new product archetype, not a new rail or wrapper. The strategic implication is that we should aim to be the dominant continuous-archetype provider that distributes through whatever rails and wrappers win · not to fight Kalshi/Polymarket on their archetype.
+
+## Related Concepts
+
+- Platform competition · the dynamic within market structure
+- Distribution moat · wrappers' structural advantage
+- Event contracts · the inventory that flows through every layer
+- Continuous prediction markets · the archetype Dekant occupies
+- Binary contracts · the archetype Kalshi/Polymarket occupy
+- Price discovery · the function the rails layer performs
+- Regulatory classification · determines which lane each rail lives in
+
+## Sources
+
+- [Option Markets vs Binary Markets vs Continuous Markets](https://x.com/embrron/status/2052489307072729516) — MO · May 7 2026 ·
+- [The State of Prediction Markets](https://stateofpredictionmarkets.blocmates.com/) — blocmates · Apr 1 2026 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/market-surveillance.md b/.qoder/plans/pm-plan/theory_concepts/market-surveillance.md
new file mode 100644
index 0000000000..4ad2b2a81d
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/market-surveillance.md
@@ -0,0 +1,54 @@
+# Market surveillance
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — Market surveillance](https://www.pmatlas.xyz/concepts/market-surveillance)
+
+---
+
+## Definition
+
+**Quick definition.** Systematic monitoring of trading by exchanges/regulators to detect insider trading, manipulation, and abuse. In PMs, surveillance is complicated by pseudonymous blockchain wallets and the absence of issuer-based disclosure obligations.
+
+## Key Insights
+
+- Carter frames insider trading as a structural feature, not a bug. Walks through the DOJ case against Master Sergeant Gannon Ken Van Dyke (made $400k on Polymarket trading the Maduro raid) and prior Israeli reservist arrests, then quotes Mansour/Coplan/Tenev/Hanson on how insider flow is what makes prices accurate. Platforms face a calibration problem: too permissive and noise traders flee perceiving rigging; too strict and informed flow disappears and prices decay into sentiment.
+- Carter predicts Polymarket fully drops pseudonymous trading and ramps surveillance over the next year as enforcement closes in.
+- Mitts and Ofir (Columbia/Haifa, via Harvard CGI): document the Feb 28 2026 US-Israeli Iran strike case · six newly created Polymarket wallets earned ~$1.2M buying 'Yes' shares as low as $0.10; one account ("Magamyman") placed its first trade 71 minutes before the news broke, when the market implied only a 17% probability. Their paper proposes platform-level registration, contract-level restrictions on high-risk categories, and extended misappropriation doctrine to close legal gaps (broader screening figures appear in the underlying SSRN paper, not in our fetched digest body).
+- Jay Sykes (Congressional Research Service): walks through SEC Rule 10b-5, CFTC Rule 180.1, the STOCK Act, and Title 18 criminal statutes. Core gap: existing law requires breach of a duty, but many PM insiders (e.g., a political candidate betting on his own race) may not owe one. Four pending bills in the 119th Congress would close this gap differently.
+- The CFTC's February 2026 advisory references two Kalshi enforcement actions · the first explicit signal that the agency intends to apply insider-trading-like rules to event contracts.
+- Sethi (Trading on Violence): documents recent pattern from $1.2M profits on the timing of US strikes on Iran to trades linked to classified intelligence. Compares Kalshi's KYC-based surveillance and Polymarket's pseudonymous blockchain as fundamentally different enforcement surfaces. Argues platforms should reconsider contract offerings before regulators force the issue.
+- Kalshi has conducted 200+ insider trading investigations, with a dozen cases currently active (Sethi).
+- Sethi's wash-trading paper (Sirolly, Ma, Kanoria, Sethi): uses network analysis to distinguish wash trading from legitimate MM activity on Polymarket. Wash traders exhibit homophily (trading only within their collusive clique); MMs exhibit heterophily (trading indiscriminately with diverse counterparties). The algorithm identified a 200-wallet cluster generating $113M in volume with just $57.86 in aggregate losses.
+- The detection asymmetry: MMs and wash traders both rapidly open/close positions and avoid large directional exposure. Looking only at counterparties is insufficient · you have to look at the counterparties of those counterparties.
+- Surveillance challenge: Kalshi's KYC produces enforceable subjects for state law but is bypassable via offshore proxies; Polymarket's pseudonymity creates surveillance-resistant trades but limits regulatory reach. The two enforcement regimes are mirror-image.
+- The Polymarket Venezuela election dispute is the surveillance failure-mode case study: oracle disputes plus pseudonymous trades plus political stakes produced an outcome that none of the parties · platform, traders, regulators · could fully validate.
+
+## Notable Quotes
+
+> "Six newly created Polymarket wallets collectively earned approximately $1.2 million by purchasing 'Yes' shares … when markets implied only a 17% probability of a strike."
+> — *Joshua Mitts and Moran Ofir, *From Iran to Taylor Swift: Informed Trading in Prediction Markets**
+
+> "One account, operating under the handle 'Magamyman,' placed its first trade seventy-one minutes before the news broke."
+> — *Joshua Mitts and Moran Ofir, *From Iran to Taylor Swift**
+
+## Where It Matters
+
+Surveillance defines the legitimacy gradient: how clean the venue looks to institutions, regulators, and ordinary users. Pseudonymity is a double-edged sword · it's what makes Polymarket compelling but also what triggers enforcement risk. For Dekant: continuous-curve markets generate richer trading signatures (a shape, not a binary leg) that make wash-trading homophily easier to detect statistically, even with pseudonymous wallets.
+
+## Related Concepts
+
+- Insider trading · the primary surveillance target
+- Wash trading · algorithmically detectable via network homophily
+- Adverse selection · informed flow is what surveillance attempts to police
+- Information asymmetry · measurable through trade patterns
+- Federal preemption · determines which agency does the surveilling
+- Regulatory arbitrage · pseudonymous PMs are the arbitrage surveillance can't reach
+
+## Sources
+
+- [Prediction Markets Have An Inescapable Insider Trading Problem](https://x.com/nic_carter/status/2048123008200724599) — Nic Carter · Apr 26 2026 ·
+- [From Iran to Taylor Swift: Informed Trading in Prediction Markets](https://corpgov.law.harvard.edu/2026/03/25/from-iran-to-taylor-swift-informed-trading-in-prediction-markets/) — Joshua Mitts, Moran Ofir · Mar 25 2026 ·
+- [Prediction Markets and Insider Trading Law](https://www.congress.gov/crs-product/LSB11406) — Jay B. Sykes · Mar 18 2026 ·
+- [Trading on Violence](https://rajivsethi.substack.com/p/trading-on-violence) — Rajiv Sethi · Mar 2 2026 ·
+- [The Detection of Wash Trading](https://rajivsethi.substack.com/p/the-detection-of-wash-trading) — Rajiv Sethi · Nov 12 2025 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/minimum-viable-liquidity.md b/.qoder/plans/pm-plan/theory_concepts/minimum-viable-liquidity.md
new file mode 100644
index 0000000000..b5295a151c
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/minimum-viable-liquidity.md
@@ -0,0 +1,69 @@
+# Minimum Viable Liquidity
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Minimum Viable Liquidity](https://www.pmatlas.xyz/concepts/minimum-viable-liquidity)
+
+---
+
+## Definition
+
+**Quick definition.** The threshold of trading volume a prediction market needs to attract informed traders who can correct mispricing. Adhi Rajaprabhakaran formalizes it as **MVL = Cost of Expertise / Price Gap**. Beyond this threshold, additional liquidity does not improve forecast accuracy · implying platforms should optimize for breadth, not depth.
+
+## Key Insights
+
+- Rajaprabhakaran's empirical anchor: **149 quality-filtered Kalshi CPI events from June 2021 through January 2026** (177 total, 28 dropped for insufficient strike density). Trading volume explains **<1% of variance in forecast accuracy** (slope = −0.063, p=0.28, R²=0.008, 95% CI [-0.176, 0.05]). Volume ranged from 7 contracts to 11.4M contracts · a **1.6 million-fold spread**. Median absolute forecast error: 0.093pp. Mean: 0.103pp. 86% of outcomes fell within two standard deviations (vs. theoretical 95% · slight underconfidence).
+- Rajaprabhakaran's MVL formula: **MVL = Cost of Expertise / Price Gap**. Below this floor, the bounty isn't large enough to attract anyone with real information and the price is just noise; above it, additional liquidity doesn't improve accuracy · it just raises the cost the next informed trader has to pay to correct a mispricing.
+- CPI volume timeline: 2022 peak 250k-480k contracts/event when CPI hit 9.1% inflation; "dog days" mid-2024 fell as low as 12,000 contracts/event when CPI fell out of the news cycle; April 2025 Robinhood-integration spike hit 11.4M contracts (816× the dog-days trough). Across the entire range, MAE actually *improved* (0.131pp in 2022 → 0.074pp post-Robinhood) · a **learning curve, not a liquidity curve**.
+- Rajaprabhakaran's metaphor: casual traders' losses were "venture-capital seed funding" for forecasters to build CPI pipelines. Once the sharp traders had built infrastructure (BLS methodology, component data, seasonal adjustment models), marginal cost of expertise collapsed and MVL collapsed with it. A few thousand dollars per event sustained world-class forecasts. The author was personally one of the BLS "super-users" who received the leaked inflation email.
+- Strategic implication: platforms should run **many thin markets** rather than concentrating volume in a few. "100,000 thin markets covering 100,000 questions produces more social and informational value than one deep market on the presidential election or the Super Bowl. The winning platform won't necessarily be the one with the deepest order books. It will be the one that asks the most questions."
+- Subsidy implication: "Subsidies, where they're needed, may only need to be temporary" · venture-capital seed funding for information production, not an ongoing operating expense. CPI needed startup liquidity in 2022 but now self-sustains.
+- Hall & Paschal echo at scale: **98.7% of political prediction markets are "ghost towns"** with wide spreads and no counterparty, yet overall calibration still clusters near the line · confirming MVL's prediction that accuracy and depth decouple above a low threshold.
+- functionSPACE V2: split Polymarket's 18,863 multi-market events into **continuous (price brackets, weather, margin %)** vs. **categorical (teams, candidates)** and re-ran pathology tests. Both concentrate 90% of volume in top 5–6 markets, but **ghost markets are largely a categorical phenomenon**. Continuous events distribute volume more evenly and survive the liquidity cliff longer at high N. Continuous events overtook categorical by event count in **2026 Q1** · making the case for continuous-distribution primitives more urgent.
+- Melee: Kalshi's 23 active MMs with top three providing 70% of election-contract liquidity means MVL in practice is **the threshold those specific firms decide to quote into**. Any market they ignore is dead on arrival.
+- Vaidik Mandloi: Polymarket's headline Brier score of 0.047 masks category failures · **sports markets score 0.325 (worse than coin flip)**, and 99% of volume is in the final hours. PMs only "work" on **roughly 2% of listed contracts**. When CNN/WSJ broadcast illiquid market odds, whale trades on thin books get laundered through credible newsrooms.
+- alexjaniak ("Where Are All the Decision Markets?"): decision/futarchy markets fail precisely because they can't reach MVL · they're idiosyncratic, and token-price-as-KPI is too noisy to incentivize rational trading. Two structural problems: (1) informed-trader scarcity · at private companies almost all relevant context is private, would require "open book" disclosure to be tractable; (2) the conditional-futures architecture means traders predicting a decision's *causal* effect can still lose money if the token moves for unrelated reasons. "The protocol is shorting its own success metric" · if KPI improves, token holders redeem at a higher value, conflict with the goal.
+- alexjaniak's proposed MVL fixes: (1) AI forecasters as AMMs to drop the MVL floor; (2) return to combinatorial markets on the question directly ("Will this KPI increase if we take this decision?") rather than conditional futures on a noisy KPI.
+- functionSPACE ("Information as Supply"): the cost of *producing* real-time probability estimates is collapsing, so PM TAM should be measured by supply (decisions that benefit from forecasts) not demand (trading volume). Scaling to $1T requires **massive breadth in long-tail markets**, not concentrated depth.
+
+## Notable Quotes
+
+> "Trading volume explains less than 1% of variance in forecast accuracy."
+> — *Rajaprabhakaran*
+
+> "Platforms should prioritize breadth over depth, running many thin markets rather than concentrating volume in few contracts."
+> — *Rajaprabhakaran*
+
+> "Prediction markets only work on roughly 2% of listed contracts."
+> — *Vaidik Mandloi*
+
+> "Prediction markets are the cheapest mechanism humanity has ever built for buying information about the future. A few thousand dollars can produce a forecast that outperforms million-dollar polling operations."
+> — *Rajaprabhakaran*
+
+> "The information doesn't require deep liquidity. It requires the right people, the right questions, and just enough money to make it worth their while."
+> — *Rajaprabhakaran*
+
+> "98.7% qualify as ghost towns: wide spreads, almost no one on the other side of the trade. Yet the overall calibration of these markets still clusters near the line."
+> — *Hall & Paschal, cited by Rajaprabhakaran*
+
+## Where It Matters
+
+MVL is the most important strategic-product concept for prediction-market platforms in 2026 because it inverts the dominant assumption. If accuracy plateaus at a relatively low liquidity threshold, the right product strategy is to launch *many small markets* with just-enough MM subsidy to clear MVL, rather than concentrating capital into a handful of high-volume contracts that don't get any more accurate from the marginal dollar. This is the structural argument for permissionless market creation, long-tail markets, AI-generated forecasts, and cross-subsidization across categories.
+
+## Related Concepts
+
+- **Liquidity provision** · MVL is the binding threshold below which liquidity is wasted.
+- **Long-tail markets** · the geometry that makes breadth strategically valuable.
+- **Liquidity fragmentation** · what makes hitting MVL harder when many binaries share a question.
+- **Forecasting accuracy / calibration / Brier score** · the dependent variable MVL governs.
+- **Decision markets / futarchy** · failure mode when MVL is unreachable.
+- **Market making** · supplies the MVL threshold.
+
+## Sources
+
+- [Where Are All the Decision Markets?](https://www.lesswrong.com/posts/bDxqMn3GJEhPeqZey/where-are-all-the-decision-markets) — alexjaniak · May 12, 2026
+- [Binary Events V2: Does Liquidity Trade The Tails?](https://x.com/functionspaceHQ/status/2048536153662636173) — functionSPACE · Apr 27, 2026
+- [The Problem With CLOBs](https://x.com/meleemarkets/status/2046318159225897289) — Melee · Apr 21, 2026
+- [Polymarket Is Not a Truth Machine](https://www.thetokendispatch.com/p/polymarket-is-not-a-truth-machine) — Vaidik Mandloi · Apr 11, 2026
+- [Information as Supply](https://x.com/functionspaceHQ/article/2035959494728176075) — functionSPACE · Mar 23, 2026
+- [Minimum Viable Liquidity](https://fiftycentdollars.substack.com/p/minimum-viable-liquidity) — Adhi Rajaprabhakaran · Feb 24, 2026
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/multi-outcome-markets.md b/.qoder/plans/pm-plan/theory_concepts/multi-outcome-markets.md
new file mode 100644
index 0000000000..4be5707499
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/multi-outcome-markets.md
@@ -0,0 +1,53 @@
+# Multi-Outcome Markets
+
+**Category:** Mechanism Design
+**Source:** [PM Atlas — Multi-Outcome Markets](https://www.pmatlas.xyz/concepts/multi-outcome-markets)
+
+---
+
+## Definition
+
+Prediction markets where an event is divided into multiple mutually exclusive outcome ranges (e.g., price brackets), each tradeable. Contrasts with binary markets that only ask yes/no questions. The natural shape for any question with more than two answers · price, time, magnitude, count.
+
+## Key Insights
+
+- functionSPACE's analysis of 36,777 Polymarket events found that when continuous questions are split into many binaries (the workaround for the lack of native multi-outcome markets), volume follows an extreme Pareto distribution · top 3 markets capture >75% of volume, leaving a fat tail of ghost markets with essentially no liquidity.
+- The $0.01 tick size compounds the multi-outcome problem on stacked binaries: at low probabilities, the tick is a 50%+ relative move · making bracket prices structurally imprecise.
+- Jo Lim (Strait of Hormuz, Mar 2026) explicitly frames multi-outcome markets as the missing architecture: binary order-book markets hit a ceiling on granular, multi-outcome risk. Traditional options solve this for tradable assets, but events without underlying assets need a different mechanism · that mechanism is the **automated market scoring rule (LMSR / CLMSR)**.
+- The Jo Lim WTI crude oil scenario walks through how scoring-rule multi-outcome markets let traders express *precise theses* (e.g., "I think oil ends between $85 and $90") rather than just directional bets ("up or down"). This is the core informational advantage of multi-outcome markets.
+- Multi-outcome markets *are* implementable as either (a) stacked independent binaries (Polymarket pattern · suffers fragmentation), (b) coherent LMSR/CLMSR over partitioned outcome space (preferred for thin markets), or (c) order books over a fixed bracket grid (Kalshi pattern for some markets).
+- Coherent multi-outcome pricing requires that bracket prices sum to ~$1 · independent binary stacks frequently *don't* sum to $1 because each is independently quoted by separate liquidity. This is a price-discovery defect.
+- Capital efficiency: in a coherent multi-outcome MSR, capital deployed by an LP supports *all* brackets simultaneously · you don't need separate capital per bracket. In stacked binaries, capital is partitioned, which is why ghost markets emerge.
+- Continuous-outcome markets (Dekant's category) are the natural extension of multi-outcome markets · instead of N discrete brackets, you have a continuous curve over the outcome space, sampled at any granularity.
+- Semantic tick size · the per-bracket pricing tick reads as a percentage-point probability revision in the binary stack; in a continuous market it reads as a density value, which is harder to UX but more expressive.
+- Multi-outcome markets are the conceptual midpoint between binaries (N=2) and continuous distributions (N=∞). The category is moving in the continuous direction as event types diversify (functionSPACE's V2 piece notes continuous events overtook categorical in Polymarket's 2026Q1 data).
+
+## Notable Quotes
+
+> "Binary order-book prediction markets hit an architectural ceiling when pricing granular, multi-outcome risk."
+> — *Jo Lim, *The World's Biggest Risk Event Just Exposed Prediction Markets' Biggest Gap**
+
+> "Scoring-rule markets reward precise thesis expression over simple directional bets."
+> — *Jo Lim, *ibid.**
+
+> "The $0.01 tick size compounds the problem, creating a rounding tax that makes low-probability contracts structurally imprecise."
+> — *functionSPACE, *Binary Events**
+
+## Where It Matters
+
+Multi-outcome is the unfilled middle of the prediction-market design space. Polymarket's stacked-binary workaround is empirically broken (ghost markets, fragmentation, incoherent price sums), and the natural successor is either coherent CLMSR-style multi-outcome AMMs or continuous-distribution markets like Dekant. The case for moving to continuous is strong: as soon as you have ≥10 brackets, you're already approximating a curve, and the partition-of-unity machinery generalizes cleanly.
+
+## Related Concepts
+
+- Binary contracts · the alternative primitive (and the source of fragmentation)
+- Market scoring rules · the AMM mechanism that handles multi-outcome natively
+- Liquidity fragmentation · what stacked binaries produce
+- Liquidity provision · multi-outcome MSRs unify capital across brackets
+- Order book · Kalshi's pattern for some multi-outcome events
+- Semantic tick size · the tick is per-bracket in stacked binaries
+
+## Sources
+
+- [Binary Events: What Happens When You Split One Market Into Twenty](https://x.com/functionspaceHQ/status/2039554933024776516) — functionSPACE · X
+- [The World's Biggest Risk Event Just Exposed Prediction Markets' Biggest Gap](https://x.com/jolimmmm/article/2036119259420807216) — Jo Lim · X
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/network-effects.md b/.qoder/plans/pm-plan/theory_concepts/network-effects.md
new file mode 100644
index 0000000000..fb9ffd9a8d
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/network-effects.md
@@ -0,0 +1,68 @@
+# Network effects
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — Network effects](https://www.pmatlas.xyz/concepts/network-effects)
+
+---
+
+## Definition
+
+**Quick definition.** When a platform becomes more valuable to each user as more participants join. In PMs: liquidity attracts traders, traders attract attention, attention attracts traders.
+
+## Key Insights
+
+- Polymarket's network effect is "trojan horse via news cycles." Camilo argues escape velocity has been reached: every news cycle drags the platform into the conversation as the price-of-record. Zero trading fees is a growth feature, not a revenue bug.
+- Lydia Wu's wallet/visit ratio: Polymarket has a 166:1 ratio of monthly visits to MAU · suggesting most traffic is information consumers, not traders. The site is effectively a creator-economy / crypto media product wrapped around a trading engine.
+- The TikTok-moment thesis (michaellwy): PMs should shift from edge-seeking tools for professionals to identity-signaling social platforms. A TikTok-style feed where bets are public expressions of belief removes friction-of-intent and transforms markets into social spaces.
+- Joel John (Decentralised.co) frames PMs as predecessors of financialized social networks. Polymarket briefly hit #1 in app stores during the 2024 election as proof that linking user views to financial stakes creates deeper engagement than pure attention capture.
+- Shane Mac argues PMs should be embedded in chat (Base App, World Chat, XMTP), not standalone apps. The competitive advantage belongs to whoever controls both the chat interface and the AI coaching intelligence layer that sits on top.
+- ASXN: 99.2% of Polymarket trading volume concentrated in political markets and two-thirds of cumulative volume came from the last six months pre-election · raising the question of whether the platform's network effects extend beyond election cycles.
+- The Nielsen-moment argument (Mehmet Avci): coordination value beats accuracy. CNN's Kalshi deal and Polymarket's Golden Globes/WSJ partnerships matter because they make the venue the shared reference point. Once embedded, displacement is near-impossible.
+- a16z's institutional adoption framework reinforces the lock-in: once Wall Street starts pulling Kalshi prints for CPI/FOMC forecasting, switching costs are no longer just liquidity but integrated workflow + compliance.
+- Robinhood's distribution moat (DCo) is a network-effect counter-attack from above: 27M existing funded users represent a pre-built network that any standalone PM has to bootstrap from zero.
+- Network effects fight fragmentation. functionSPACE: scaling to $1T requires breadth in long-tail markets, not just concentrated depth · but liquidity-based network effects bias capital toward the few markets that already have it.
+- Sid's advertising framing: prediction markets convert ad spend into liquidity that rewards deep attention. A sponsor seeds a market at $50k–$500k, traders find and research it, then share analysis organically · sustained cognitive engagement at ~$20 per person-hour vs seconds of passive display.
+- Marvellous's pessimistic angle: network effects are now so strong on incumbents that builders should not compete for venue liquidity. The opening is decision-support tools that wrap on top of existing PM liquidity.
+- ARK Invest's $1-5T TAM is implicitly a network-effect bet: once an event-contract category crosses the displacement threshold against traditional derivatives, liquidity should concentrate cleanly on whichever venue gets there first.
+- mph's contra: the lack of cult/community formation (vs NFTs or meme coins) suggests PM network effects are still pre-paradigmatic. Builder programs are nascent; permissionless markets are upcoming.
+
+## Notable Quotes
+
+> "With every news cycle & trending event, the team trojan-horses the platform into the conversation."
+> — *Camilo, *Thoughts on Polymarket Network Effects**
+
+> "Once adopted as the shared reference point, displacement becomes nearly impossible regardless of methodological superiority."
+> — *paraphrased from Mehmet Avci, *The Nielsen Moment for Prediction Markets**
+
+## Where It Matters
+
+Network effects are the moat that makes PM platform competition winner-concentrate-the-spoils within a given lane (crypto-native vs regulated vs wrapper). Whoever becomes the embedded reference price for a category locks in coordination value that pure accuracy cannot dislodge. For Dekant: the implication is that we cannot win as "a better Polymarket for the same questions" · we need either a different mechanism (continuous payouts) or different distribution.
+
+## Related Concepts
+
+- Platform competition · network effects are the engine of dominance
+- Distribution moat · Robinhood imports a pre-built network
+- Long-tail markets · where network effects haven't formed yet
+- Liquidity provision · depth begets more depth begets more traders
+- Polymarket · the canonical network-effect case study
+
+## Sources
+
+- [Why Robinhood Will Eat Kalshi's Lunch](https://x.com/i/article/2052782036688228352) — DCo · May 11 2026 ·
+- [Prediction Markets In Q1 2026: 11 Insights Worth Knowing](https://x.com/Ommiii_/status/2047582772387291361) — Omar · Apr 24 2026 ·
+- [Prediction Markets: They Grow Up So Fast](https://open.substack.com/pub/a16z/p/prediction-markets-they-grow-up-so) — Alex Immerman, Santiago Rodriguez · Apr 16 2026 ·
+- [Are We on the Brink of a Prediction Market Supercycle?](https://x.com/mphrediction/status/2043419029378048164) — mph · Apr 12 2026 ·
+- [Brazil: The Next Frontier for Prediction Markets](https://x.com/Yuri_Gubert/status/2041513296608571791) — Yuri Gubert · Apr 7 2026 ·
+- [The Financialization of Uncertainty](https://x.com/kaviish/status/2041214800731033700) — Kaviish · Apr 6 2026 ·
+- [Information as Supply](https://x.com/functionspaceHQ/article/2035959494728176075) — functionSPACE · Mar 23 2026 ·
+- [Prediction Markets Are the Future of Advertising](https://x.com/sidbharth/article/2027740100214603934) — Sid · Feb 28 2026 ·
+- [The Uncomfortable Truth Prediction Market Builders Must Accept](https://x.com/marvellousdefi/article/2015779074317209696) — Marvellous · Jan 26 2026 ·
+- [The Nielsen Moment for Prediction Markets](https://reachavci.substack.com/p/the-nielsen-moment-for-prediction) — Mehmet Avci · Jan 12 2026 ·
+- [The Future of Prediction Markets Lives in Group Chats](https://blog.shanemac.com/the-future-of-prediction-markets-lives-in-group-chats/) — Shane Mac · Nov 30 2025 ·
+- [The TikTok Moment for Prediction Markets](https://x.com/michael_lwy/status/1948400467886891314) — michaellwy · Jul 24 2025 ·
+- [Thoughts on Polymarket Network Effects](https://x.com/curiouscamilo/status/1948155161827836149) — Camilo · Jul 24 2025 ·
+- [Financialisation of Social Networks](https://www.decentralised.co/p/financialisation-of-social-networks) — Joel John · Oct 23 2024 ·
+- [Unveiling Polymarket: The Positioning, Expansion, and Shadows of Crypto Prediction Markets](https://research.mintventures.fund/2024/10/09/Unveiling-Polymarket-The-Positioning-Expansion-and-Shadows-of-Crypto-Prediction-Markets/) — Lydia Wu · Oct 9 2024 ·
+- [Polymarket and the Proliferation of Prediction Markets](https://www.shoal.gg/p/prediction-markets) — Alex Nardi · Oct 8 2024 ·
+- [Polymarket: An Election-Driven Success Story](https://newsletter.asxn.xyz/p/polymarket-an-election-driven-success) — ASXN · Aug 10 2024 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/no-loss-prediction-markets.md b/.qoder/plans/pm-plan/theory_concepts/no-loss-prediction-markets.md
new file mode 100644
index 0000000000..78c066670d
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/no-loss-prediction-markets.md
@@ -0,0 +1,54 @@
+# No-Loss Prediction Markets
+
+**Category:** Governance & Decisions
+**Source:** [PM Atlas — No-Loss Prediction Markets](https://www.pmatlas.xyz/concepts/no-loss-prediction-markets)
+
+---
+
+## Definition
+
+**Quick definition.** Prediction-market designs where participants risk only the *yield* on their deposit (not the principal itself). Deposits are routed into a yield-bearing position (e.g., aave, lending, staking); the yield is pooled and awarded to winners; principal is returned to all participants regardless of outcome.
+
+## Key Insights
+
+- **Core mechanism**: aggregate participant capital → deploy to yield-bearing protocol (lending, LST staking, stablecoin yield) → at market resolution, return all principal pro-rata to depositors AND distribute accrued yield to the winning side. Losers lose nothing in nominal terms (and only lose the opportunity cost of yield they would have earned independently).
+- **The design rationale** (aaronjmars, *A Small Prediction Market Design Taxonomy*): a major friction for prediction-market adoption is loss aversion. Conventional binary markets force participants to risk principal, which is psychologically and practically a high bar · most casual users won't put $50 at risk to express a view they hold weakly. No-loss markets convert the entry barrier from "principal at risk" to "yield-rate × time × participation."
+- **Trade-off**: the dollar-denominated payouts to winners are small (only the pooled yield, not the pooled principal). This caps the upside and weakens the information-aggregation argument · sophisticated traders won't bother participating because the expected profit per hour of analysis is trivial relative to taking the same view on a normal market.
+- **Information aggregation degrades**: prediction markets work best when informed traders have meaningful capital at stake. No-loss markets, by design, attract uninformed casual participants and repel sophisticated ones. The aggregated price signal is therefore noisier and more reflective of opinion than information. This makes them better suited to **opinion markets / beauty contests / community polling** than to forecasting questions where accuracy matters.
+- **Yield source matters enormously**: the more reliable the yield (e.g., USDC lending vs. risky DeFi yield), the more predictable the prize pool. Volatile yield sources introduce a second layer of uncertainty: participants are betting on the event AND on the yield environment.
+- **Bear-market problem**: in low-yield environments (post-2022 stablecoin lending at ~3%, etc.), the prize pool is too small to attract participation. The mechanism scales with prevailing risk-free rates, making no-loss markets inherently pro-cyclical to the yield environment.
+- **Custody / smart-contract risk** replaces market risk: depositors still take on the risk that the yield protocol blows up (Anchor / UST style), that the prediction-market contract has a bug, or that bridge / oracle infrastructure fails. The marketing claim of "no loss" is true only in nominal terms within the protocol's happy path.
+- **Adoption fit**: no-loss markets work best for high-engagement, low-stakes use cases · community sports pools, fantasy-style markets, brand engagement campaigns, educational/onboarding markets where the goal is participation rather than calibration. PoolTogether's "no-loss lottery" is the canonical primitive that inspired the design pattern.
+- **Combinator pairing**: no-loss markets pair well with decision markets in commitment-device mode · when an organization wants community input on a decision but doesn't need maximum-accuracy price discovery, and wants to minimize the financial barrier to participation. The output is more "weighted poll" than "informed forecast," which is fine for the use case.
+- **No-loss + hyperstition**: a no-loss hyperstition market would let community members coordinate around a target outcome without risking principal. The "manifestation" cost is participants' time and the opportunity cost of yield, which is much lower than direct principal-at-risk. This may be a productive combination for community-coordination use cases.
+- **Existing examples** outside the prediction-market category: PoolTogether (no-loss lottery on aave/compound deposits), Augur's design discussions for low-stakes markets, several Solana-side projects experimenting with LST-yield-funded markets.
+- **Regulatory angle**: no-loss markets are easier to defend as non-gambling because no participant loses principal. They may slip under the gambling regulatory bar in jurisdictions that focus on principal loss as the constitutive feature of gambling. This is an underexplored regulatory wedge.
+- **Liquidity is structurally weaker**: since the prize pool is bounded by yield, market depth that would normally come from informed traders providing liquidity for spread capture is absent. No-loss markets need protocol-provided liquidity (subsidies) or accept thinner books.
+- **Settlement is straightforward**: at resolution, principal returns 1:1 to depositors and yield is distributed by winning-side share. No conditional-token complexity is required for binary outcomes. For multi-outcome no-loss markets, the design choices around tie-handling and partial-winning allocations get more interesting.
+- **The fundamental tension** the design surfaces: prediction markets work because of skin-in-the-game; no-loss markets remove skin-in-the-game; therefore no-loss markets are not really prediction markets in the information-aggregation sense · they're closer to "yield-funded engagement mechanisms." This is fine; just clarifying.
+
+## Notable Quotes
+
+> "Each design optimizes for different goals: accuracy, speed, coordination, or outcome manifestation. [No-loss PMs optimize] for lowering the barrier to entry."
+> — *aaronjmars, *A Small Prediction Market Design Taxonomy**
+
+> "Designs where participants risk only potential yield, not principal, lowering the barrier to entry."
+> — *onprediction.xyz editorial definition*
+
+## Where It Matters
+
+No-loss markets are an answer to one of the category's persistent retail-onboarding problems: principal-at-risk is a much higher bar than the information-aggregation value of any single user's view justifies. They are unlikely to be the venue for serious price discovery, but they may be the right primitive for community engagement, brand campaigns, and decision markets in commitment-device mode. For Dekant specifically, no-loss distribution markets are an interesting future product · letting users draw a curve and only risk the yield is a much more accessible onboarding loop than asking new users to put principal behind a continuous distribution.
+
+## Related Concepts
+
+- **Futarchy** · No-loss decision markets are a low-stakes onramp to futarchic governance, especially for organizations that want community input without forcing principal at risk.
+- **Decision markets** · Particularly fit for the "aggregated opinions" and "commitment device" use cases identified by Janiak.
+- **Hyperstition markets** · No-loss participation lowers the cost of "manifestation by participation," potentially expanding the addressable size of hyperstition coordination.
+- **Parlays** · A no-loss parlay (yield-funded multi-leg bet) is an interesting consumer product idea.
+- **Bonding-trades / bonding curves** · Could pair with no-loss to offer asymmetric upside funded by pooled yield.
+- **Wisdom of crowds** · No-loss markets get less wisdom because they get less skin-in-the-game; this is the fundamental design tension.
+
+## Sources
+
+- [A Small Prediction Market Design Taxonomy](https://x.com/aaronjmars/article/1991975420669915328) — aaronjmars · Nov 22, 2025
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/noise-decomposition.md b/.qoder/plans/pm-plan/theory_concepts/noise-decomposition.md
new file mode 100644
index 0000000000..ef27705842
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/noise-decomposition.md
@@ -0,0 +1,47 @@
+# Noise Decomposition
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Noise Decomposition](https://www.pmatlas.xyz/concepts/noise-decomposition)
+
+---
+
+## Definition
+
+Separating observed price movements into components attributable to genuine new information versus random microstructure noise · used to assess how much of a market's short-term variance reflects real signal. Closely tied to market-microstructure theory (Kyle, Glosten-Milgrom, Roll variance ratios) but specialized for PMs by the binary-payoff and short-horizon structure.
+
+## Key Insights
+
+- "Noisy Traders Are Not Dumb Money" · challenges the smart-vs-dumb dichotomy. Synthesizes Snowberg/Wolfers/Zitzewitz, INSEAD's BIN (Bias-Information-Noise) model, and Wharton's cognitive-search framework. Key argument: noisy traders *fund the probability space* rather than serve as exit liquidity. Compares how binary CLOBs vs continuous probability markets decompose and harness noise *differently* · in a binary market, noise gets absorbed as bid-ask spread costs to liquidity providers; in a distribution market, noise spreads across the surface and can be re-aggregated as a smoothing effect (functionSPACE).
+- "Decomposing Crowd Wisdom" · Bayesian hierarchical model on 292M trades across 327K contracts on Kalshi and Polymarket. Decomposes calibration into four structured components: (1) a universal horizon effect, (2) domain-specific biases, (3) domain-by-horizon interactions, and (4) a trade-size scale effect. Together explains 87.3% of calibration variance on Kalshi. The "dominant pattern is persistent underconfidence in political markets, where prices are chronically compressed toward 50%" (generalises across both exchanges). Trade-size scale effect: Δ = 0.53 [0.29, 0.75] on Kalshi politics; Δ = 0.11 [-0.15, 0.39] on Polymarket · platform-specific microstructure. Bayesian model achieves 96.3% posterior predictive coverage (Nam Anh Le).
+
+## Notable Quotes
+
+> "Noisy traders fund the probability space rather than serve as exit liquidity."
+> — *functionSPACE*
+
+> "Together explain 87.3% of variance on Kalshi."
+> — *Nam Anh Le*
+
+> "Persistent underconfidence in political markets where prices compress toward 50%."
+> — *Nam Anh Le*
+
+## Where It Matters
+
+Noise decomposition is the diagnostic that lets a builder say *honestly* what their market is good at. Aggregate Brier scores treat all variance as forecast quality; decomposition separates "the market correctly updated on news" from "the market thrashed because a 5¢ tick read as a 1pp probability change" (semantic tick size). For PM platforms, this matters in three ways: (1) it lets you exclude microstructure noise from accuracy claims, (2) it identifies categories where noise dominates and informed flow is absent (signaling minimum-viable-liquidity failures), (3) it surfaces the platform-specific microstructure effects (Kalshi's large trades amplify underconfidence; Polymarket's don't) that determine whether a contract behaves like a real probability or a token price. For Dekant, the distribution-market surface should naturally decompose noise across the distribution, with traders rewarded for variance compression (entropy reduction) rather than for being on the right side of a binary threshold.
+
+## Related Concepts
+
+- **Calibration** · what noise decomposition cleans up
+- **Brier score** · the aggregate metric that noise decomposition explodes
+- **Adverse selection** · informed flow is the signal portion
+- **Liquidity provision** · noise is what LPs are paid to absorb
+- **Market making** · bid-ask spread is one mechanism for pricing noise
+- **Semantic tick size** · a specific microstructure noise pattern
+- **Longshot bias** · domain-specific bias surfaced by decomposition
+- **Distribution markets** · proposed mechanism for noise re-aggregation across a surface
+
+## Sources
+
+- [Noisy Traders Are Not Dumb Money](https://x.com/functionspaceHQ/article/2032397043579462028) — functionSPACE · Mar 13, 2026 [FULL_READ]
+- [Decomposing Crowd Wisdom: Domain-Specific Calibration Dynamics in Prediction Markets](https://arxiv.org/abs/2602.19520) — Nam Anh Le · Feb 23, 2026 [FULL_READ · abstract; Bayesian hierarchical decomposition with 96.3% posterior predictive coverage; trade-size scale effect Δ=0.53 on Kalshi politics vs Δ=0.11 on Polymarket]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/nowcasting.md b/.qoder/plans/pm-plan/theory_concepts/nowcasting.md
new file mode 100644
index 0000000000..d28ec4d4b8
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/nowcasting.md
@@ -0,0 +1,47 @@
+# Nowcasting
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Nowcasting](https://www.pmatlas.xyz/concepts/nowcasting)
+
+---
+
+## Definition
+
+Using prediction market prices as real-time proxies for economic indicators that are officially reported with a delay (inflation, employment, GDP). Treats continuously updated market odds as a high-frequency data source · turning monthly econ releases into a live ticker.
+
+## Key Insights
+
+- Tiered framework for evaluating PM reliability: financialized economic indicators rank *highest*, speculative prop bets lowest. Three concrete use cases: (1) triangulating against traditional polls, (2) nowcasting delayed economic data in real time, (3) hedging event risk. Federal Reserve paper validating Kalshi's data quality is cited as anchoring the case. Tetlock's forecasting research grounds the methodology (Isar Bhattacharjee, "How to Use Prediction Markets as a High Quality Info Source").
+- Dan Schwarz analyzes ~13,500 Polymarket contracts: >80% volume in sports/crypto/elections; accuracy on "useful" markets hasn't improved since early 2025. AI chatbots may supersede PMs as the primary forecasting interface, leaving PMs to serve an *epistemic role as common-knowledge infrastructure*. Nowcasting is exactly the kind of "useful" application where the gap between aspiration (real-time econ indicator) and reality (low volume, thin liquidity, narrow contract definitions) is widest (Asterisk).
+- Schwarz full-read on the "news interpretation" / nowcasting category: 1,647 markets with $1.25B total volume · but 85% concentrated in US federal interest rate markets. Median market volume *declined* from $49K (early 2025) to $13K (end 2025). Schwarz's verdict: while predicting interest rates is valuable, "CME futures, Bloomberg consensus, and professional economists already do it. The same is true for other indicators with high trading volume on Polymarket and Kalshi: inflation, unemployment, commodity prices, mortgage rates." The PM advantage is *speed* · on March 11, 2026, FT reported Iran war escalation news → Polymarket odds of inflation ≥2.8% rose to 90%+ in real time, much faster than the Cleveland Fed inflation nowcast (~2-week lag).
+- Bhattacharjee full-read on the practical workflow: "First, it's super easy to access and interpret. If you want to use traditional 'market pricing mechanisms' to estimate data like inflation, you either need proprietary tools or you need to do some calculations to figure out inflation breakeven prices (e.g., TIPS vs Bonds pricing). This is a pretty big faff. For a prediction market, you literally open it up and read the number." Second advantage: continuity. He compares against the Cleveland Fed inflation nowcast · "it's taken around 2 weeks for the inflation nowcast from the Cleveland Fed to catch up with the oil news from Iran. The prediction markets on the other hand are instantaneous."
+- Bhattacharjee identifies hedging-via-nowcasting use cases: short a dollar-store stock + long recession on Polymarket as a covered position. Weather/natural-disaster events provide more structural hedging opportunities. The "more I've looked at these prediction markets, the more hedging opportunities and risk offsetting opportunities start to become apparent."
+- The Feb 2026 Federal Reserve Board study (cited by Bhattacharjee and by Mitts/Ofir): "Our results suggest that Kalshi markets provide a high-frequency, continuously updated, distributionally rich benchmark that is valuable to both researchers and policymakers."
+
+## Notable Quotes
+
+> "Financialized economic indicators rank highest, speculative prop bets lowest."
+> — *Isar Bhattacharjee*
+
+> "Accuracy on 'useful' markets hasn't improved since early 2025."
+> — *Dan Schwarz*
+
+## Where It Matters
+
+Nowcasting is the most respectable application of PMs in the "info finance" pitch · it's what gets you a Federal Reserve citation rather than a CFTC complaint. The Kalshi CPI markets are the canonical example: a continuously updated probability over the next BLS print, available between releases, that could in principle reduce policy lag for traders and central banks alike. The empirical limits are real, though: Adhi Rajaprabhakaran's "Minimum Viable Liquidity" piece (analyzing the same 149 Kalshi CPI markets) found that *volume explains <1% of variance in forecast accuracy* on CPI markets, suggesting that even on the most respectable nowcasting target, the standard "thicker book = better signal" intuition is wrong. For builders, nowcasting is a high-prestige but low-volume contract category · useful as a credibility flag, not as a revenue line.
+
+## Related Concepts
+
+- **Forecasting accuracy** · the metric that determines whether nowcasting is real
+- **Information aggregation** · the underlying mechanism
+- **Endogeneity** · what could break nowcasting if Fed policy reacts to PM prices
+- **Info finance** · the broader umbrella nowcasting sits inside
+- **Probability infrastructure** · nowcasting is the canonical "embed PM odds in another product" use case
+- **Calibration** · nowcasting needs strong calibration on point estimates, not just directional accuracy
+- **Minimum viable liquidity** · the empirical constraint on CPI markets specifically
+
+## Sources
+
+- [Are Prediction Markets Good for Anything?](https://asteriskmag.com/issues/14/are-prediction-markets-good-for-anything) — Dan Schwarz · Apr 1, 2026 [FULL_READ]
+- [How to Use Prediction Markets as a High Quality Info Source](https://uncover.substack.com/p/how-to-use-prediction-markets-as) — Isar Bhattacharjee · Mar 30, 2026 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/opportunity-markets.md b/.qoder/plans/pm-plan/theory_concepts/opportunity-markets.md
new file mode 100644
index 0000000000..90934d50ce
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/opportunity-markets.md
@@ -0,0 +1,66 @@
+# Opportunity Markets
+
+**Category:** Governance & Decisions
+**Source:** [PM Atlas — Opportunity Markets](https://www.pmatlas.xyz/concepts/opportunity-markets)
+
+---
+
+## Definition
+
+**Quick definition.** Private prediction markets where prices are visible *only to the sponsor* during an "opportunity window." Designed by Dave White and Matt Liston (Paradigm, August 2025) so that institutions can crowdsource scouting (artists, researchers, founders, athletes) without giving competitors a free read on the signal. People with information get paid by people with resources; the prediction-market public-goods leak is sealed.
+
+## Key Insights
+
+- **The problem they solve**: in a regular prediction market, sponsors who provide liquidity for "Will we sign Artist X in 2025?" subsidize the same information for all their competitors. The public-goods nature of price discovery destroys the institutional incentive to fund scouting markets. Opportunity markets fix this by keeping prices private from everyone except the sponsor for a window of time.
+- **The other side of the problem**: scouts (fans, researchers, local experts) have information that institutions don't, but there's no clean way to monetize that insight short of becoming a paid scout. Opportunity markets create a permissionless scouting channel where anyone can put skin in the game.
+- **Mechanism by example**: a music label creates a family of markets "Will we sign Artist X in 2025?" for any artist. The label acts as market maker, posting up to $25,000 of "dumb money" liquidity per artist. Scouts who recognize an artist early can buy YES shares at low prices; as the price rises, the sponsor sees this private signal and investigates the artist. If signed, scouts win the dumb-money pool. Effectively, the label has crowdsourced a decentralized scout program with explicit financial incentives.
+- **Privacy is the load-bearing innovation**: only the sponsor sees the current market price. If traders could see fills in real time, they could reconstruct prices by trading and probing. To preserve privacy while still giving traders eventual feedback, opportunity markets use an **opportunity window** · perhaps two weeks · after which traders learn whether their orders filled. This window lets the sponsor investigate before competitors can free-ride on the signal.
+- **Privacy enforcement options**: (1) trusted sponsor (simplest, weakest), (2) AMM running in a Trusted Execution Environment (TEE) that proves the sponsor cannot peek at trader positions beyond the protocol's reveal schedule, (3) cryptographic constructions (zk-rollup-like) that could in principle preserve sponsor-only views with stronger guarantees.
+- **Liquidity provisioning is bounded by useful range**: a sponsor might post liquidity starting around 1% probability (below which the signal is informationally useless) and stopping around 30% probability (above which the signal is redundant · the sponsor already knows). This concentrates liquidity in the band where information has marginal value.
+- **Unlimited markets vs. First-N collateralization**: For opportunity types with a hard cap on actions (a label can only sign so many artists), the sponsor can simply promise payouts on an unlimited number of markets ("Will we sign Artist X?") and ensure their total exposure stays within their action budget. For permissionless setups, fully collateralizing each market is too capital-intensive; instead, structure markets as "Will XYZ be among the first 10 artists we sign in 2025?" · which requires only 10× max liquidity total, not unlimited.
+- **Exploitation risk is real and largely unsolved**: sponsors have private market-state information AND private knowledge of their own pipeline. They could feign interest in artist X to push prices up while aggressively selling, or do the inverse. The paper acknowledges this is hard to address mechanism-design-wise; reputation and trust are doing the heavy lifting in v1 designs.
+- **Post-window reveal design choices**: after the opportunity window closes, sponsors choose what to reveal · all prices and positions publicly, positions only to individual traders, or differentiated rules for large vs. small orders. Each choice trades off information leakage vs. market accountability.
+- **Sophisticated extensions**: limit orders that can be canceled before reveal, trading agents that operate on policy without revealing current positions, and time-decaying privacy where late information eventually becomes public (preserving the public-goods information value while protecting first-mover sponsorship).
+- **Use-case domain** the paper emphasizes: opportunities that take significant resources to evaluate AND to act on, and have a competitive time-limited nature. Music label signings, research lab commercialization, VC sourcing, athletic recruitment, employer talent scouting, retail trend spotting. These are domains where knowing first confers real institutional advantage.
+- **Why scout programs are not enough**: traditional scout programs are limited by trust requirements (the institution must vet each scout) and evaluation costs (each scout's recommendation must be triaged). Scaling beyond a small bench is hard. Opportunity markets are *permissionless* scout programs · anyone can put their judgment to work, and the institution only invests in following up on the strongest market signals.
+- **Why public prediction markets are not enough**: even if institutions subsidized public PMs, competitors free-ride on the price signal. Subsidizing public price discovery is subsidizing your competitors. Privacy is what makes the funding economics work for the sponsor.
+- **The Paradigm framing** aligns this with their broader prediction-market thesis: opportunity markets are one of several "fully-asymmetric-information" mechanisms (alongside Distribution Markets, which Dekant implements) that extend prediction-market mechanics into spaces where standard public markets fail.
+- **Implementation surface**: TEEs are central · running the AMM logic inside a Trusted Execution Environment (Intel SGX, AWS Nitro, etc.) lets sponsors prove they aren't peeking. This is one of the more concrete commercial use cases for TEEs in DeFi.
+- **The "decentralized scout program" tagline** captures the value prop crisply: a music label that today employs 5 scouts each running on personal networks can become a label that has 5,000 informal scouts each putting $50 of capital behind their judgment, with the label only paying out to the scouts who were right.
+
+## Notable Quotes
+
+> "Music labels, research labs, and VCs all want to find the next big thing before the competition. But the people who first spot opportunities often have no institutional connections. Historically, there hasn't been a clean way for these parties to find each other and transact."
+> — *Dave White, Matt Liston, *Opportunity Markets**
+
+> "Even if institutions subsidized liquidity on these markets to benefit from the information, prediction markets as they are usually deployed today offer their information as a public good. Competitors could free-ride on the same signals, eliminating the advantage. This is the core leak opportunity markets seek to address."
+> — *White, Liston*
+
+> "Opportunity markets address this problem by keeping market prices private from everyone but their sponsor… It's like a decentralized scout program where anyone in the world can get skin in the game."
+> — *White, Liston*
+
+> "For traders to know their positions eventually [is necessary]. The solution is an opportunity window"
+> — *perhaps two weeks · after which traders learn whether their orders filled. This gives sponsors time to investigate promising opportunities before the information becomes public." · White, Liston*
+
+> "[Sponsors] have both special information about the market state at any given time, and special knowledge about their own process, which opens the risk of exploitative behavior such as hinting they will take advantage of opportunity X while aggressively selling into that market."
+> — *White, Liston*
+
+## Where It Matters
+
+Opportunity markets are the cleanest worked example of "what does it look like when you fix the public-goods leak in prediction markets?" · a question that hangs over every institutional use case. The privacy-first design unlocks talent scouting, commercialization scouting, deal sourcing, and other domains where public price signals destroy the sponsor's competitive advantage. They are also a useful complement to Dekant's continuous-outcome design: where Dekant prices distributions over outcome variables in public markets, opportunity markets are private discrete markets where the institutional sponsor pays for early information. Both are answers to "what does prediction-market infrastructure unlock beyond elections?" · but they answer to very different buyers.
+
+## Related Concepts
+
+- **Decision markets** · Opportunity markets are decision-adjacent: the sponsor's decision to act on a signal is informed by the private market price.
+- **Distribution markets** · Both are Paradigm-published designs (Distribution Markets · White, 2024; Opportunity Markets · White & Liston, 2025) that extend prediction-market mechanics beyond binary public outcomes.
+- **Liquidity provision** · The sponsor is the dedicated LP; opportunity markets are explicit about LP exposure being bounded by the value of the information signal.
+- **Hedging** · Less relevant; opportunity markets are about scouting, not hedging.
+- **Insider trading** · Sponsors operate with privileged information by design; the design space explicitly accepts this and constrains it via reputation and TEE-style cryptography rather than disclosure law.
+- **Oracle design** · Settlement requires verifying whether the sponsor actually acted (signed the artist, hired the candidate); oracle design here is about provable institutional action rather than external event observation.
+- **Market manipulation** · Sponsors can exploit private price views; the open problem in opportunity-market design.
+- **Network effects** · Sponsors compete to attract the best scout network; a label with a strong reputation for honoring market signals attracts more scouts.
+
+## Sources
+
+- [Opportunity Markets](https://www.paradigm.xyz/2025/08/opportunity-markets) — Dave White, Matt Liston, Paradigm · Aug 18, 2025 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/oracle-design.md b/.qoder/plans/pm-plan/theory_concepts/oracle-design.md
new file mode 100644
index 0000000000..ee16793052
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/oracle-design.md
@@ -0,0 +1,102 @@
+# Oracle Design
+
+**Category:** Oracle & Resolution
+**Source:** [PM Atlas — Oracle Design](https://www.pmatlas.xyz/concepts/oracle-design)
+
+---
+
+## Definition
+
+**Quick definition.** Oracle design is the set of choices a prediction market makes about *how an off-chain outcome becomes an on-chain settlement* · who reports it, who can dispute it, what data sources are canonical, and what economic bonds back honesty. It is the most-cited concept on onprediction.xyz (referenced in 32 articles), because every other property of a prediction market · accuracy, manipulation-resistance, regulatory posture, liquidity · bottlenecks here.
+
+## Key Insights
+
+- **Resolution, not pricing, is the hardest problem.** Andy Hall (a16z): "The hardest problem in prediction markets isn't pricing the future. It's deciding what actually happened." Pricing is solved by liquidity; resolution is solved by mechanism design we haven't shipped yet.
+- **Four properties any resolution mechanism must satisfy at once** (Hall): manipulation-resistance, reasonable accuracy, ex ante transparency (rules visible before the bet), and credible neutrality. Human committees fail at neutrality and scale; token voting fails at manipulation-resistance; centralized operators fail at transparency. Nothing on the market today satisfies all four.
+- **The "Oracle Problem" is misnamed · it's a *definition* problem.** Aradtski catalogued 12+ Polymarket controversies and argues every single one was about ambiguous event wording, not oracle untrustworthiness. The Hayekian price-discovery argument breaks down when the question itself is semantically contested.
+- **There is a resolver trilemma between efficiency, decentralization, and security** (XO Labs). Optimistic oracles with token voting trade security for efficiency; centralized operators trade decentralization for both; AI judges trade some accuracy for transparency. No single mechanism dominates · different markets need different rungs of the ladder.
+- **The corruption-cost vs. value-at-stake gap is the central oracle failure mode.** Repeated case studies (government shutdown, Zelensky suit, Ukraine map, Venezuela invasion, Cardi B halftime, TikTok ban) all share the same shape: the *value at stake on the market exceeded the cost of corrupting the oracle*. Until that ratio inverts, oracle-driven settlement is exploitable in principle on every large market.
+- **Optimistic oracles (UMA) have a known capture surface.** Token-holders who can both vote on resolutions *and* trade the underlying market are structurally conflicted. Frank Muci's Venezuela post-mortem documents UMA holders coordinating to overrule Polymarket's own stated rules. Lou Kerner's 2024 conspiracy walks through how a Trump-favoring UMA whale could profit twice · once on the trade, once on the vote.
+- **Semantic ambiguity is the real adversary.** Matt Levine's framing: "Prediction markets need linguists and philosophers as much as traders." If the contract reads "Will Zelensky wear a suit?", *what is a suit?* is now a $240M question with no objective answer. The market becomes a referendum on definitions, not facts.
+- **Source-of-truth attacks are the next attack surface.** When the resolution criterion points at a single URL (an OPM website, a niche map app, an unguarded thermometer near CDG airport), adversaries attack the data source, not the oracle. Gunitsky's "hair-dryer problem": $14,000 won by warming a single thermometer; the oracle was honest, the underlying signal was not.
+- **AI/LLM judges with on-chain commitment are the leading proposed fix.** Hall's a16z proposal: at market creation, commit the *exact* LLM model version and prompt to chain. Resolution is then deterministic, transparent, and bribery-resistant (you cannot bribe model weights). The model doesn't have to be perfect · *it has to be predictable.* Traders can then price in its biases the way they price in any other contract parameter.
+- **AI judges trade one set of vulnerabilities for another that may be more tractable.** Limitations: hallucination, prompt-engineering failures, source-poisoning attacks against the inputs the model reads, training-data poisoning over long horizons. But none of these involve real-time bribery of a $240M vote.
+- **There are five MEV-style edges around oracles** (st1ne): (1) oracle latency arbitrage · trading on news before UMA updates; (2) resolution arbitrage · front-running settlement; (3) dispute sniping · gaming the UMA dispute liveness window; (4) order-book imbalance exploitation around expiry; (5) conditional probability arbitrage across correlated markets. Polymarket is "a hidden MEV playground" · sophisticated actors extract value from oracle structure, not informational edge.
+- **Decentralization matters most where the data must be transported on-chain.** Sonal/Tabarrok/Kominers (a16z podcast): if the resolution data already lives on-chain (price feeds, smart-contract outputs), you don't need a decentralized oracle for that question. The decentralization budget should be spent on the *off-chain-to-on-chain transmission*, where MNPI extraction and manipulation cluster.
+- **Layered architectures route each market to the security tier it needs** (XO Labs). Low-stakes meme markets can be admin-resolved or AI-resolved cheaply; high-stakes geopolitical markets need expensive decentralized adjudication. Treating resolution as a single mechanism is what produced the $500M tail of resolution failures.
+- **Cross-platform divergence is now its own oracle failure mode.** The Cardi B halftime market resolved YES on Polymarket and NO on Kalshi · same event, two answers, two settlement systems, no reconciliation. michaellwy: prediction markets are *fundamentally non-fungible* because each market is "an event + a referee system," and the referees disagree.
+- **Resolution disputes can themselves become a venue for state-actor manipulation.** Gunitsky: regimes with information-warfare budgets ($billions/year) can manipulate either trading prices *or* the disputed-resolution mechanism for propaganda value. A million-dollar loss on a propaganda operation is rounding error.
+- **Some emerging designs sidestep oracles entirely.** Self-resolving markets (SKC, Monad explainer) use the crowd's own final consensus as the reference outcome. Parimutuel-with-AI-resolver designs (Turf/sdk.markets) let any URL serve as ground truth via an LLM oracle. These are aimed at long-tail/subjective markets that on-chain oracles fundamentally cannot serve.
+- **Proprietary centralized oracles with platform skin-in-the-game may outperform decentralized mercenary capital** (Aradtski). The argument: a centralized operator whose brand dies if it cheats is more aligned than a swing-vote of mercenary token-holders whose downside is bounded by token forfeiture.
+- **Insurance/cover markets are a stress test for oracle design.** Eigenmann (HIP-4): when binary event contracts share margin with the underlying exposure they hedge, oracle quality directly determines whether the contract acts as parametric cover or as a casino. The bar is much higher than for entertainment markets.
+- **The 7 axes of competition include oracle reliability as a first-class differentiator** (Nyquist). New entrants can compete on oracle design itself · credibility tokens, layered resolvers, AI judges · as much as on UX or liquidity.
+
+## Notable Quotes
+
+> "The hardest problem in prediction markets isn't pricing the future. It's deciding what actually happened."
+> — *Andy Hall, *What to Do When Prediction Markets Fail**
+
+> "The model doesn't have to be perfect; it has to be predictable."
+> — *Andy Hall, on LLM-as-judge*
+
+> "Polymarket has descended into sheer arbitrariness. Words are redefined at will, detached from any recognized meaning, and facts are simply ignored."
+> — *Polymarket user, quoted in *Semantics for $10 Million**
+
+> "We've seen that UMA is not a decentralized truth machine, it's just a bunch of people who vote in their own best interests."
+> — *Lou Kerner, *My Polymarket Conspiracy Theory**
+
+> "In our markets, you are absolutely entitled to trade on MNPI that you rightfully own."
+> — *David Miller, CFTC Enforcement Director, March 31 2026 (quoted in michaellwy CFTC comment)*
+
+> "Truth machines go to war."
+> — *Matt Levine framing for Bloomberg Opinion, on Kalshi mentions markets*
+
+## Where It Matters
+
+Oracle design is the load-bearing primitive: it determines whether a prediction market is a price-discovery institution or a venue for extracting value through ambiguity exploitation. Every other technical problem (liquidity, capital efficiency, UX) is downstream · a market with broken resolution attracts toxic flow and bleeds informed traders. The frontier is moving from optimistic-oracle-with-token-voting toward layered architectures (LLM judges for cheap markets, decentralized adjudication for high-stakes, parametric on-chain feeds where possible) and toward defining-the-event-precisely-enough-that-oracle-choice-doesn't-matter.
+
+## Related Concepts
+
+- **dispute resolution** · what happens when the oracle's answer is contested; UMA's DVM voting and similar mechanisms.
+- **resolution criteria** · the contract wording itself; "definition problem" is one tier above oracle choice.
+- **UMA protocol** · the dominant optimistic-oracle implementation behind Polymarket; the most-stressed instance of oracle design.
+- **corruption value multiple** · the quantitative diagnostic: does the value-at-stake exceed the cost of corrupting the oracle?
+- **self-resolving markets** · designs that try to avoid oracles entirely.
+- **information aggregation** · the Hayekian function oracles are supposed to preserve.
+- **incentive compatibility** · whether the oracle's voters/judges are paid for honesty.
+
+## Sources
+
+- [Resolving Prediction Markets: An AI-Driven Layered Approach](https://x.com/xolabs_/status/2055266974259961917) — XO Labs · May 15 2026
+- [Orthogonal Precision in Trepa: A Tunable Second-Order Oracle for High-Frequency Forecasting](https://github.com/TrepaOrg/trepa-research/blob/main/Trepa_Orthogonal_Precision_20260513_Blanco%26Chung%26Meka.pdf) — Blanco, Chung, Meka · May 13 2026
+- [Outcome Markets as a Cover Venue: HIP-4 and Its Traditional Comparables](https://x.com/DeanEigenmann/status/2052734335971713347) — Dean Eigenmann · May 8 2026
+- [The Hidden Risk Of Prediction Markets: 14 Resolution Failures That Cost $500M](https://x.com/xomarket/status/2046924302952640934) — XO Market · Apr 22 2026
+- [Every Opinion Deserves A Market](https://x.com/turfsports_/status/2047030930699854334) — Turf · Apr 22 2026
+- [Truth Machines Go to War](https://www.bloomberg.com/opinion/newsletters/2026-04-06/truth-machines-go-to-war) — Matt Levine · Apr 6 2026
+- [The Economy of Truth: Inside the Decentralized Courtroom of Polymarket & UMA](https://x.com/smaaaaliy/article/2033242239498076190) — Smaliy · Mar 16 2026
+- [Polymarket Is a Hidden MEV Playground. Most Traders Have No Idea.](https://x.com/SolSt1ne/article/2032008002094366899) — st1ne · Mar 13 2026
+- [On War Markets](https://x.com/probaaron/article/2027765452689014822) — aaron · Feb 28 2026
+- [23 Reasons Prediction Markets Are Broken Today](https://x.com/linfluence/status/2026757563518431696) — Alexander Lin · Feb 26 2026
+- [Prediction Markets are the Agentic Bazaar](https://x.com/benfielding/article/2023130119708242317) — Ben Fielding · Feb 16 2026
+- [Prediction Markets: The Path to $1 Trillion and What Needs to Happen Next](https://x.com/a1research__/article/2022340314770375024) — Sakshi Mishra · Feb 14 2026
+- [The 7 Axes of Prediction Markets](https://x.com/Jake_Nyquist/article/2021222519991124343) — Jake Nyquist · Feb 10 2026
+- [What to Do When Prediction Markets Fail](https://a16zcrypto.substack.com/p/how-ai-judges-can-scale-prediction) — Andy Hall · Jan 24 2026 ·
+- [Semantics for $10 Million: When Is an Invasion an Invasion?](https://www.oddchain.com/p/semantics-for-10-million-when-is-an-invasion-an-invasion-polymarket) — OddChain · Jan 23 2026 ·
+- [Assassination Semantics: Why Every Market Carries the Risk of Violence](https://x.com/BetBreakNews/article/2008587654158340126) — Guillory & Zimmermann · Jan 7 2026
+- [Why Prediction Markets Are Where They Are](https://tim0x.substack.com/p/why-prediction-markets-are-where) — Tim.0x · Jan 5 2026 ·
+- [Prediction Markets · Everything You Need to Know](https://a16zcrypto.com/posts/podcast/prediction-markets-explained/) — Chokshi, Tabarrok, Kominers · Sep 25 2025 ·
+- [A Technical Typology of Prediction Markets: Mechanics and Tradeoffs](https://x.com/Baheet_/status/1967186769356488828) — Baheet · Sep 14 2025
+- [Meta Pool: A Unified Infrastructure for Liquidity and Resolution Trust in Prediction Markets](https://x.com/jeff__opinion/article/1957611486991487486) — Jeff Opinion · Aug 19 2025
+- [Next Gen Prediction Markets](https://x.com/socialgraphvc/status/1952808508396552534) — Social Graph Ventures · Aug 6 2025
+- [Prediction Markets: The 'Oracle Problem' Is Not the Problem](https://x.com/aradtski/status/1949977296523145695) — Aradtski · Jul 29 2025
+- [The Risk Behind Arbitrage in Prediction Markets](https://x.com/michael_lwy/status/1925890768654254571) — michaellwy · May 23 2025
+- [10 Predictions About Prediction Markets](https://x.com/michael_lwy/status/1884570344184578081) — michaellwy · Jan 29 2025
+- [The Definitive Guide to Prediction Markets](https://docsend.com/view/atcc6k258s4umq6s) — Four Pillars · Jan 15 2025
+- [Why Prediction Markets Are Broken (And How to Fix Them)](https://x.com/michael_lwy/status/1877211555496079683) — michaellwy · Jan 9 2025
+- [Prediction Markets (II): Spoiling the Election Love Story](https://dirtroads.substack.com/p/64-prediction-markets-ii-spoiling) — Luca Prosperi · Nov 2 2024 ·
+- [My Polymarket Conspiracy Theory](https://medium.com/quantum-economics/my-polymarket-conspiracy-theory) — Lou Kerner · Oct 20 2024
+- [Crypto Prediction Markets](https://dirtroads.substack.com/p/63-crypto-prediction-markets) — Luca Prosperi · Oct 11 2024 ·
+- [Polymarket and the Proliferation of Prediction Markets](https://www.shoal.gg/p/prediction-markets) — Alex Nardi · Oct 8 2024 ·
+- [Mechanisms for Prediction Markets](https://paragraph.com/@mechanism.institute/prediction-markets) — Hadi, Cossar, Shimony · Aug 22 2024 ·
+- [Polymarket Settles Bet Against Its Own Rules](https://frankmuci.substack.com/p/polymarket-settles-bet-against-its) — Frank Muci · Aug 8 2024 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/order-book.md b/.qoder/plans/pm-plan/theory_concepts/order-book.md
new file mode 100644
index 0000000000..e5211562c8
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/order-book.md
@@ -0,0 +1,83 @@
+# Order Book
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Order Book](https://www.pmatlas.xyz/concepts/order-book)
+
+---
+
+## Definition
+
+**Quick definition.** A list of outstanding buy and sell orders at various prices, showing available liquidity at each level. The central limit order book (CLOB) is the dominant trading mechanism on Polymarket and Kalshi after the industry abandoned LMSR AMMs in 2022.
+
+## Key Insights
+
+- Polymarket's late-2022 migration from LMSR AMM to CLOB was the moment the industry recognized binary outcomes break AMM economics · impermanent loss becomes *permanent* because the pool ends up holding worthless losing-side shares (Melee).
+- CLOBs solved capital destruction but introduced a new pathology: passive participation is impossible; **only professional MMs can quote** (Melee).
+- Human Invariant on FCFS matching: all four major orderbook PMs (Polymarket, Kalshi, Opinion, Limitless) use FCFS with off-chain matching; FCFS creates a latency war ("co-locate with the matching engine") and forces MMs to widen spreads defensively. Priority batch auctions (cancels → makers → takers) shift competition from latency to price accuracy.
+- Pantera/Sui empirical comparison (NFL 2025/26 season, 282 games, $1.7B notional across both venues): Kalshi total volume $1.3B, Polymarket $359M; Kalshi avg ~19,024 trades/game vs. Polymarket 2,718; Kalshi avg bet $224.83 vs. Polymarket $469.88. **Kalshi leads price adjustments by a median 7 seconds in ~80% of large moves** (85.7% for 20pp+ swings). Polymarket's median Kyle-implied LD is 10⁶·⁹⁶ vs. Kalshi's 10⁶·⁴² · **3–4× more notional needed to move Polymarket prices over a 60s horizon**.
+- Pantera/Sui hypothesis: Polymarket's ~3-second delay on marketable orders in live sports reduces LP adverse selection, supporting deeper concentrated liquidity near the prevailing price; Kalshi's immediate execution exposes resting liquidity to information shocks, forcing more conservative quoting.
+- Ranger Global on CLOB microstructure: YES/NO minting and merging invariant lets depth expand whenever matched counterparties exist; probability-scaled dynamic fees shrink near 0 and 1; latency <100 ms now captures **73% of arbitrage profits**.
+- @allquantor on 600M+ datapoints: surface symmetry at top-of-book hides systematic **ask-side skew at deeper levels**; medium-to-large orders hit liquidity cliffs.
+- st1ne identifies five MEV-style edges on Polymarket: oracle latency arbitrage, resolution arbitrage, dispute sniping, orderbook imbalance exploitation, conditional probability arbitrage. Polymarket is "a hidden MEV playground."
+- Sam Schneider (Technically) on 203M Kalshi trades / $41.7B volume: fee = 0.07 × C × P × (1-P) · fees peak at 50/50, incentivizing trading near the meaningful middle. **Sports = 82% of total volume**. (Paywalled past intro.)
+- Will Howard ("Many PMs Would Be Better Off as Batched Auctions"): there is no practical social benefit to sub-second reaction times in PMs; batching would redirect trader effort from speed to substance. Concrete claim: "for almost all markets there is no social utility to having a reaction time faster than about 1 second, and for many markets the socially useful reaction time could be a day or more." When Biden dropped out in 2024, Polymarket jumped *the moment* he tweeted · a system someone built solely because the PM existed; no broader benefit to that 500ms.
+- Howard on what batch auctions would unlock: (1) effort reallocated to longer timescales improves accuracy, (2) high-frequency tomfoolery is filtered, (3) ergonomics: average trader can submit, walk away, and trust a fair clearing price emerges, (4) easier to read a single batch-cleared price than a noisy mid.
+- Cryptonomads (10,000 automated trades): most PM terminals fail through "execution mirages, non-synthesizing research layers, and strategy tabs that are deck screenshots." Only institutional API rails and trader-native terminals survive.
+- Jay Malavia: exchange order books deliver Betfair's ~3% overround vs. ~12% for traditional bookmakers · peer-to-peer order books welcome winners; sportsbooks limit them.
+- Niakris's case that PMs are finance: peer-to-peer CLOB mechanics, skin-in-the-game pricing, hedging use cases, gambling-suppressive UX.
+- Roan: Polymarket's CLOB creates "renewable structural arbitrage by design" · five-point diagnostic, three trader archetypes.
+- Pantera/Sui design matrix: Polymarket runs off-chain matching with on-chain settlement on Polygon PoS (~2s blocks) and a ~3s marketable-order delay in live sports; Kalshi runs fully off-chain matching with immediate settlement. Both have public limit-order books but only Polymarket has on-chain transparency of flow.
+
+## Notable Quotes
+
+> "Kalshi reportedly has 23 active market makers with the top three providing 70% of election-contract liquidity, meaning any market those firms ignore is dead on arrival."
+> — *Melee*
+
+> "Sub-second reaction times serve no social benefit."
+> — *Will Howard*
+
+> "Polymarket is a hidden MEV playground."
+> — *st1ne*
+
+> "Kalshi leads price adjustments by a median of ~7 seconds, exhibiting faster and more volatile reactions to in-game information shocks. Polymarket, despite lower volume and open interest, consistently exhibits higher implied liquidity depth."
+> — *Pantera Research Lab*
+
+> "The information environment doesn't benefit for knowing that Joe Biden stepped down 500ms sooner."
+> — *Will Howard*
+
+> "FCFS creates a latency war to update prices as close to real time as possible."
+> — *Human Invariant*
+
+## Where It Matters
+
+The CLOB is currently the consensus microstructure for prediction markets · but every empirical study finds the same downside: it concentrates power in a tiny set of pro market makers, leaves long-tail markets empty, and rewards latency arbitrage in ways that look indistinguishable from MEV. Alternatives (LMSR, parimutuel, batched auctions, scoring rules, covariance markets) all exist precisely as reactions to CLOB pathologies, and the design space for "post-CLOB prediction markets" is the most active frontier in 2026.
+
+## Related Concepts
+
+- **Continuous double auction** · the canonical CLOB matching rule.
+- **Batched auctions** · leading alternative to the CLOB's FCFS matching.
+- **Liquidity provision / market making** · order books are the venue.
+- **Bid-ask spread** · the headline output.
+- **Execution quality** · what CLOBs are graded on.
+- **LMSR / market scoring rules** · pre-CLOB AMM mechanisms.
+- **Cross-platform arbitrage / orderflow arbitrage** · strategies that live on top of order books.
+
+## Sources
+
+- [Why Every Prediction-Market Terminal Will Fail (and the Two That Won't)](https://x.com/0xCryptoNomads/status/2048664550225170605) — cryptonomads · Apr 27, 2026
+- [Polls Are Dead. Long Live Prediction Markets.](https://x.com/CalBlockchain/status/2047460674532790499) — Blockchain at Berkeley · Apr 23, 2026
+- [The Problem With CLOBs](https://x.com/meleemarkets/status/2046318159225897289) — Melee · Apr 21, 2026
+- [Anatomy Of A New Asset Class I: How Markets Turn Capital Into Probability](https://x.com/Ranger_Global/status/2046547375649427572) — Ranger Global · Apr 21, 2026
+- [Why AMMs Failed Prediction Markets](https://x.com/meleemarkets/status/2043766417342755259) — Melee · Apr 13, 2026
+- [The World's Biggest Risk Event Just Exposed Prediction Markets' Biggest Gap](https://x.com/jolimmmm/article/2036119259420807216) — Jo Lim · Mar 24, 2026
+- [Polymarket Is a Hidden MEV Playground. Most Traders Have No Idea.](https://x.com/SolSt1ne/article/2032008002094366899) — st1ne · Mar 13, 2026
+- [What's Kalshi's Revenue? Analyzing All 203 Million Trades on Kalshi.](https://read.technically.dev/p/whats-a-prediction-market) — Sam Schneider · Mar 12, 2026 (paywalled, intro only)
+- [Polymarket Doesn't Have a Money Problem. It Has a Plumbing Problem.](https://x.com/ZEITFinance/article/2031500989576949770) — @allquantor · Mar 11, 2026
+- [Polymarket Is Not a Casino. Why Prediction Markets Are Finance, Not Gambling](https://x.com/13_niakris/article/2025614474263093627) — Niakris · Feb 23, 2026
+- [Why Prediction Markets Aren't Gambling? (The Math)](https://x.com/RohOnChain/article/2020565633453412751) — Roan · Feb 9, 2026
+- [The Super Bowl of Prediction Markets: Kalshi and Polymarket's Battle for Price vs Liquidity](https://panteraresearchlab.xyz/research/the-super-bowl-of-prediction-markets-kalshi-and-polymarkets-battle-for-price-vs-liquidity/) — Ally Zach, Danning Sui · Feb 5, 2026
+- [Liquidity in Prediction Markets and the Rise of a New Asset Class](https://x.com/Ranger_Global/status/2008177990971122003) — Ranger Global · Jan 5, 2026
+- [Who Are You Really Playing Against?](https://x.com/thejay/article/1968392782998835518) — Jay Malavia · Sep 18, 2025
+- [Many Prediction Markets Would Be Better Off as Batched Auctions](https://antidiluvian.substack.com/p/many-prediction-markets-would-be) — Will Howard · Aug 2, 2025
+- [Mechanisms for Prediction Markets](https://paragraph.com/@mechanism.institute/prediction-markets) — Raye Hadi, Sofia Cossar, Ori Shimony · Aug 22, 2024
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/orderflow-arbitrage.md b/.qoder/plans/pm-plan/theory_concepts/orderflow-arbitrage.md
new file mode 100644
index 0000000000..792b05470c
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/orderflow-arbitrage.md
@@ -0,0 +1,42 @@
+# Orderflow Arbitrage
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Orderflow Arbitrage](https://www.pmatlas.xyz/concepts/orderflow-arbitrage)
+
+---
+
+## Definition
+
+**Quick definition.** Profiting from temporary price dislocations caused by large orders that push the market away from fair value before it mean-reverts. The PM-specific version exploits **orderbook imbalance** · fading orders that consume one side of the book and reverting to mid before slower traders catch up.
+
+## Key Insights
+
+- st1ne identifies **orderbook imbalance exploitation** as one of five MEV-style edges on Polymarket. Sophisticated actors extract value from structural inefficiencies rather than informational edges.
+- Nekt0 lists orderflow arbitrage as one of **eight distinct PM arbitrage strategies** (alongside classic YES+NO mispricing, cross-platform, range, conditional, time, hedged, resolution). Each comes with concrete dollar examples and risk factors.
+- The implicit mechanism: when a large taker order hits one side of a thin PM order book, the price overshoots true value. A fast actor with capital on the other side can take the dislocated quote and ride it back to mid · this is the equivalent of equity-microstructure "liquidity-providing" market making but executed event-by-event rather than as a continuous quoting strategy.
+- Co-located with @allquantor's semantic-tick-size finding: **~70% of one-cent price moves don't continue** · orderflow noise is the largest single contributor to those reversal events.
+- Co-located with sybilpm's "Sniper's Tax": orderflow arbitrage and toxic-flow sniping look similar at the trade level (both are large orders hitting thin books) but diverge in intent. Orderflow arb assumes the move is uninformed; sniping assumes it's informed and acts before the MM can update.
+
+## Notable Quotes
+
+> "Sophisticated actors extract value from structural inefficiencies rather than informational edges."
+> — *st1ne*
+
+## Where It Matters
+
+Orderflow arbitrage is one of the cleanest profit pools for sophisticated PM participants, and it's invisible to retail because it only shows up in fill-quality analysis and reversal patterns. Microstructure-aware platform design (batched auctions, minimum resting times, hidden orders) directly compresses this edge · which is why the practitioners writing about it tend to be also writing about preserving the CLOB status quo.
+
+## Related Concepts
+
+- **Order book** · the venue.
+- **Execution quality** · what orderflow arb sells.
+- **Adverse selection** · orderflow arb is the *non*-adverse subset of order-impact trading.
+- **Semantic tick size** · the source of the reversal pattern.
+- **Arbitrage** · parent concept.
+- **Cross-platform / time / resolution arbitrage** · sibling strategies.
+
+## Sources
+
+- [Polymarket Is a Hidden MEV Playground. Most Traders Have No Idea.](https://x.com/SolSt1ne/article/2032008002094366899) — st1ne · Mar 13, 2026
+- [All Types of Arbitrage on Prediction Markets](https://x.com/Nekt_0/article/2019107985079816395) — Nekt0 · Feb 5, 2026
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/parimutuel-markets.md b/.qoder/plans/pm-plan/theory_concepts/parimutuel-markets.md
new file mode 100644
index 0000000000..9da26513c7
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/parimutuel-markets.md
@@ -0,0 +1,54 @@
+# Parimutuel Markets
+
+**Category:** Mechanism Design
+**Source:** [PM Atlas — Parimutuel Markets](https://www.pmatlas.xyz/concepts/parimutuel-markets)
+
+---
+
+## Definition
+
+A betting structure where all wagers pool together and payouts are split proportionally among winners after the event resolves. Bootstraps liquidity without market makers · making them useful for niche or long-tail prediction markets · but faces challenges around locked positions, timing, and real-time price readability.
+
+## Key Insights
+
+- **Origin:** Parimutuel betting was invented for French horse racing in 1867. The mechanism is older than every modern prediction-market AMM and has been continuously deployed in horse racing for 150+ years (Melee, *Parimutuel Prediction Markets*).
+- **Core economic property:** Parimutuel pools have *zero* market-maker risk · the platform doesn't take any side, just routes pool capital to winners. This makes them ideal for cold-start markets where no LP wants to take inventory risk.
+- **Three friction points** identified by Melee: 1. **Locked positions** · capital is committed until resolution, no secondary market 2. **Timing constraints** · odds shift continuously as more bets land, so "betting early" carries unknown final price 3. **Price readability** · implied probabilities derive from pool ratios, which aren't intuitive to display in real time
+- **The "wait and see" sniping problem:** Late bettors observe the pool composition and snipe · they get last-look on probability information without contributing to early price discovery.
+- **Turf / sdk.markets (Apr 2026)** launched a parimutuel toolkit on Base addressing the sniping problem with three design choices: (1) short answer windows, (2) snapshot locking, (3) DPM-style (Dynamic Parimutuel Market) pricing.
+- Turf offers three resolution modes: single admin, multi-admin consensus, and an **AI oracle that resolves from arbitrary URLs** · pointing toward an emerging design where parimutuel pairs with cheap, low-trust resolution.
+- DPM (Dynamic Parimutuel Market) · a Pennock-style mechanism that combines parimutuel's no-counterparty property with continuous price quotation; effectively the bridge between traditional parimutuel and LMSR.
+- Parimutuel is the natural fit for *opinion* markets (Turf's pitch: "Every Opinion Deserves A Market") because CLOB infrastructure overcomplicates thin, low-conviction questions where there's no natural counterparty.
+- Parimutuel is the *only* mechanism class that can bootstrap a market with zero LP capital · every other mechanism requires either subsidy (LMSR) or matched orders (CLOB).
+- The downside is severe for *long-dated* markets: capital is locked for the entire duration, which is why parimutuel is mostly used for short-duration events (a single horse race, a single sports game) and rarely for multi-month policy questions.
+
+## Notable Quotes
+
+> "CLOB infrastructure does not fit thin community markets… parimutuel pools are simpler and fairer when there is no natural counterparty."
+> — *Turf, *Every Opinion Deserves A Market**
+
+> "Parimutuel pools solve the cold start problem by bootstrapping liquidity without market makers."
+> — *Melee, *Parimutuel Prediction Markets**
+
+> "Three friction points that need upgrading: locked positions, timing constraints, and price readability."
+> — *Melee, *ibid.**
+
+## Where It Matters
+
+Parimutuel is the right mechanism for the *long tail* of prediction markets where conviction is thin and no LP wants to commit capital. The new wave (Turf, DPMs, AI-oracle pairing) suggests parimutuel is becoming the layer-1 mechanism for community/opinion markets while CLOB stays the layer-1 for high-volume liquid markets. Both layers can coexist on the same platform · and the toolkit/SDK packaging is the operational innovation.
+
+## Related Concepts
+
+- Market making · parimutuel has *no* market maker (key differentiator)
+- Liquidity provision · parimutuel uses pooled trader capital instead of LP capital
+- Long-tail markets · parimutuel's natural home
+- Platform competition · Turf et al. are competing on parimutuel infrastructure
+- Oracle design · Turf's AI URL oracle is a specific resolution design
+- Resolution criteria · parimutuel needs clean criteria because there's no counterparty to enforce
+- Self-resolving markets · Turf's AI oracle is a low-trust resolution mechanism
+
+## Sources
+
+- [Every Opinion Deserves A Market](https://x.com/turfsports_/status/2047030930699854334) — Turf · X
+- [Parimutuel Prediction Markets](https://x.com/meleemarkets/status/2041244791716110409) — Melee · X
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/parlays.md b/.qoder/plans/pm-plan/theory_concepts/parlays.md
new file mode 100644
index 0000000000..d5dde550ac
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/parlays.md
@@ -0,0 +1,53 @@
+# Parlays
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — Parlays](https://www.pmatlas.xyz/concepts/parlays)
+
+---
+
+## Definition
+
+**Quick definition.** Multi-leg bets combining outcomes across several events. Higher payoffs at lower probability, sportsbook-grade product feature that PMs are racing to replicate without fragmenting liquidity.
+
+## Key Insights
+
+- Sakshi Mishra (a1research) lists parlays among the five infrastructure unlocks to reach $1T: liquidity sustainability (subsidized MM transitioning to self-sustaining), discovery/UX, trade expressiveness (leverage faces unique gap risk in binary markets), permissionless market creation, and multi-tier oracle resolution.
+- Mason Nystrom's GTM thesis: successful PMs will focus on three characteristics · high leverage (parlays, perps, intraday events), highly frequent markets (retention), and high market outcome values (capital attraction). "Prediction market wars have only begun."
+- Chicken (voliti.co) proposes covariance markets as the cleanest parlay solution: instead of creating separate markets for all AND/OR combinations (which fragments liquidity), one covariance market between two base markets enables all 8 joint combinations while keeping liquidity concentrated.
+- Mike (mikhryc0x) frames parlays through the derivatives-on-stocks analogy: PMs are evolving a second layer of risk instruments. Covers three hedging use cases (crypto risk hedging via binary price markets, attention markets like Trendle as sentiment hedges, cross-platform hedging enabled by DeFi composability · Gondor lending against PM positions, DFlow tokenizing Kalshi contracts as SPL tokens).
+- Tim.0x's local-maxima diagnosis: Polymarket and Kalshi have PMF but are stuck. Three barriers: insufficient liquidity (small trades materially reprice markets), lack of parity with sportsbooks on parlays, and inability to resolve complex outcomes (Time Person of the Year resolving to "other").
+- aaronjmars taxonomy: 14 PM mechanism types beyond standard binary, including covariance markets and quantum markets ("capital-efficient parallel conditionals" · a different architectural answer to the parlay problem).
+- The DraftKings/FanDuel parlay edge is the implicit benchmark: sportsbooks charge fat parlay overrounds (a parlay of two 50% events sometimes priced as if it were 18% rather than 25%). PMs that match parlay UX while preserving fair pricing could capture sharp money from sportsbooks.
+- mph's supercycle thesis acknowledges parlays as a critical product gap. Polymarket's pricing edge over sportsbooks on single events doesn't extend to multi-leg bets without dedicated parlay infrastructure.
+
+## Notable Quotes
+
+> "Successful prediction markets will focus on three characteristics: high leverage (parlays, perps, intraday events), highly frequent markets, and high market outcome values."
+> — *paraphrased from Mason Nystrom, *Prediction Markets GTM Approaches**
+
+> "The prediction market wars have only begun."
+> — *Mason Nystrom, *Prediction Markets GTM Approaches**
+
+## Where It Matters
+
+Parlays are the most strategically important product gap between PMs and sportsbooks. They're how casual users express conviction across multiple correlated bets (same-game parlays, "Trump wins AND inflation > 3%"), and they're how platforms monetize square retail. Solving parlays without fragmenting liquidity is one of the standing design challenges. For Dekant: continuous markets implicitly support a richer expression than parlays · drawing a curve over a joint distribution is itself a multi-leg parametric bet · but the UX framing would need to translate.
+
+## Related Concepts
+
+- Covariance markets · the proposed liquidity-preserving parlay architecture
+- Liquidity fragmentation · the failure mode parlay design must avoid
+- Conditional tokens · Gnosis CTF natively supports parlay-like position combinations
+- Gap risk · binary parlays compound jump risk multiplicatively
+- Hedging · parlays as multi-leg directional or correlation bets
+- Cross-platform arbitrage · parlay legs can be arbed across venues with different pricing
+
+## Sources
+
+- [Are We on the Brink of a Prediction Market Supercycle?](https://x.com/mphrediction/status/2043419029378048164) — mph · Apr 12 2026 ·
+- [Prediction Markets: The Path to $1 Trillion and What Needs to Happen Next](https://x.com/a1research__/article/2022340314770375024) — Sakshi Mishra · Feb 14 2026 ·
+- [How Prediction Markets Turn Into Risk Instruments](https://x.com/mikhryc0x/article/2020802941091741972) — Mike · Feb 9 2026 ·
+- [Why Prediction Markets Are Where They Are](https://tim0x.substack.com/p/why-prediction-markets-are-where) — Tim.0x · Jan 5 2026 ·
+- [A Small Prediction Market Design Taxonomy](https://x.com/aaronjmars/article/1991975420669915328) — aaronjmars · Nov 22 2025 ·
+- [Prediction Markets GTM Approaches](https://x.com/masonnystrom/status/1950395287975186715) — Mason Nystrom · Jul 31 2025 ·
+- [How to Offer Prediction Market Parlays Without Fragmenting Liquidity](https://x.com/voliti_co/status/1946232456459255869) — Chicken · Jul 18 2025 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/peer-prediction.md b/.qoder/plans/pm-plan/theory_concepts/peer-prediction.md
new file mode 100644
index 0000000000..3eaa56052b
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/peer-prediction.md
@@ -0,0 +1,58 @@
+# Peer Prediction
+
+**Category:** Mechanism Design
+**Source:** [PM Atlas — Peer Prediction](https://www.pmatlas.xyz/concepts/peer-prediction)
+
+---
+
+## Definition
+
+Methods for eliciting honest subjective reports by comparing respondents against each other statistically. When there is no ground truth to score against, peer-prediction mechanisms use the *correlation structure between reporters* to incentivize truth-telling.
+
+## Key Insights
+
+- Peer prediction is the mechanism-design answer to the *unverifiable outcome* problem: how do you reward honest reporting when there's nothing to verify the report against?
+- The canonical formulation (Miller, Resnick, Zeckhauser 2005, not on-page but background): each reporter's payment depends on the reports of a reference peer, with a Bayesian-truth-serum-like scoring rule that makes truthful reporting a Bayesian-Nash equilibrium.
+- Yiling Chen & David Pennock's Harvard survey (Jan 2025) is the on-page canonical reference, treating peer prediction systems as a distinct class alongside scoring rules and market scoring rules. Their framing: "Prediction markets elicit forecasts for events with a clear, objective outcome that can be reliably discerned after the fact ... Many information-aggregation tasks do not conform to this requirement, either because the outcome is subjective · the quality of a movie · or unmeasurable · the extinction of the human race. Peer prediction systems operate by evaluating each agent's prediction not against an objective reality but against the other agents' predictions."
+- Chen & Pennock's key incentive claim: "Remarkably, under certain conditions such systems can induce truth telling in equilibrium, meaning that if others are playing honestly the best response is to play honestly as well, yielding aggregate assessments of subjective or unmeasurable outcomes."
+- Srinivasan, Karger & Chen (SKC, 2023) · *Self-Resolving Prediction Markets for Unverifiable Outcomes* · extends peer-prediction logic into a *market* (vs. survey) setting. From the abstract: "We present a novel incentive-compatible prediction market mechanism to elicit and efficiently aggregate information from a pool of agents without observing the outcome, by paying agents the negative cross-entropy between their prediction and that of a carefully chosen reference agent."
+- The SKC reference-agent insight: "A reference agent with access to more information can serve as a reasonable proxy for the ground truth ... The final agent is chosen as the reference agent since they observe the full history of market forecasts, and thus have more information by design." This is the bridge from peer prediction (any peer) to self-resolving markets (the *most informed* peer).
+- Random termination is the SKC mechanism's anti-strategic-timing device. Monad's explainer: "this random termination scheme makes the length of the game unpredictable. No one can be sure they're the final trader or how many more will come after them, which, as discussed in the paper, helps keep everyone honest."
+- Monad's reward structure summary: "All participants except the last k agents are rewarded based on how close their reported probability was to q(T). The specific rule is the negative cross-entropy market scoring rule." The last k agents receive a fixed reward R as a participation prize.
+- The signature trade-off in peer-prediction mechanisms: they only work if reporters' beliefs are *correlated through the underlying signal*. If reporters can collude or coordinate on a non-truth-correlated equilibrium, the mechanism breaks.
+- Peer prediction generalizes beyond prediction markets into peer review, content moderation, crowdsourced labeling, and DAO governance · anywhere subjective reports need incentive-compatible elicitation.
+- The relationship to proper scoring rules: peer prediction *is* a proper scoring rule, but the "outcome" is another reporter's report rather than a ground-truth event. This shifts the equilibrium analysis from individual best-response to Bayesian-Nash among reporters.
+- For prediction markets specifically, peer prediction is the foundation of every "self-resolving" market mechanism · the SKC paper is the bridge from peer-prediction theory to deployable on-chain markets.
+- Open practical questions: minimum number of reporters required for incentive compatibility (typically n ≥ 3), robustness to Sybil attacks (a single attacker controlling multiple reporters breaks the mechanism), and how to handle uninformed reporters who report priors rather than signals.
+
+## Notable Quotes
+
+> "Peer prediction systems operate by evaluating each agent's prediction not against an objective reality but against the other agents' predictions. Remarkably, under certain conditions such systems can induce truth telling in equilibrium."
+> — *Chen & Pennock, *Designing Markets for Prediction**
+
+> "We present a novel incentive-compatible prediction market mechanism to elicit and efficiently aggregate information from a pool of agents without observing the outcome, by paying agents the negative cross-entropy between their prediction and that of a carefully chosen reference agent."
+> — *Srinivasan, Karger & Chen, *Self-Resolving Prediction Markets for Unverifiable Outcomes**
+
+> "A reference agent with access to more information can serve as a reasonable proxy for the ground truth."
+> — *SKC, *ibid.**
+
+> "Markets resolve using crowd consensus as the outcome, with delta-based scoring rewarding participants for moving markets toward final consensus."
+> — *michaellwy, *Explainer on Self-Resolving Prediction Markets**
+
+## Where It Matters
+
+Peer prediction is the *only* mechanism class that lets prediction markets cover questions without ground truth · opinion markets, taste markets, subjective forecasts, "is this true?" markets about contested information. Polymarket and Kalshi avoid these entirely (they require verifiable resolution); the next wave of prediction-market design (self-resolving markets, on-chain truth markets) is being built on peer-prediction foundations. The SKC mechanism is the most likely on-chain instantiation.
+
+## Related Concepts
+
+- Self-resolving markets · the deployment surface for peer prediction
+- Proper scoring rules · the formal foundation
+- Incentive compatibility · the property peer prediction delivers
+- LMSR · peer prediction is a sibling, not a subset
+- Information aggregation · peer prediction is for aggregating *subjective* information
+
+## Sources
+
+- [Explainer on Self-Resolving Prediction Markets](https://blog.monad.xyz/blog/self-resolving-prediction-markets) — michaellwy · Monad Blog
+- [Designing Markets for Prediction](https://bpb-us-e1.wpmucdn.com/sites.harvard.edu/dist/b/845/files/2025/01/aim10.pdf) — Yiling Chen, David M. Pennock · Harvard
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/platform-competition.md b/.qoder/plans/pm-plan/theory_concepts/platform-competition.md
new file mode 100644
index 0000000000..510764d016
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/platform-competition.md
@@ -0,0 +1,94 @@
+# Platform competition
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — Platform competition](https://www.pmatlas.xyz/concepts/platform-competition)
+
+---
+
+## Definition
+
+**Quick definition.** How prediction market platforms differentiate, compete for users, and defend market share. The fight stretches across rails (settlement), wrappers (distribution), and product archetypes · not a single winner-takes-all venue.
+
+## Key Insights
+
+- The sector is stratifying into product archetypes, not converging on one winner. Crypto rails (Polymarket) optimize breadth and speed-to-list; regulated rails (Kalshi) optimize settlement credibility; execution wrappers (Robinhood, Coinbase) turn events into a mainstream instrument shelf alongside stocks, options, crypto, and futures.
+- Robinhood's distribution moat is existential for standalone PM platforms: 27M funded users, vertical integration via the Rothera JV (buying exchange infrastructure), and the ability to cross-sell event contracts. Regulatory threats to sports contracts hurt specialists far more than multi-asset brokers (DCo).
+- Volume is now massive but lopsided. Kunal Doshi documents Kalshi's transformation into a sports trading venue (specific monthly-volume and March Madness notional figures are in his tweet but not in our fetched digest body). Sam Schneider's *What's Kalshi's Revenue?* analyzes 203 million Kalshi trades (figure confirmed from the article subtitle; body paywalled).
+- Polymarket is reaching network-effect escape velocity. Zero trading fees aren't a bug · they're a growth wedge that lets the platform trojan-horse itself into every news cycle. Camilo argues Polymarket is on a path to becoming vital financial infrastructure.
+- a16z (Immerman/Rodriguez) describes a three-stage institutional adoption framework: markets as data sources -> markets in compliance workflows -> active risk hedging. The bottleneck for institutions is full-notional collateral, which Kalshi is solving via margin trading licenses. Entertainment, crypto, and culture retain users better than sports.
+- The "square vs sharp" sportsbook framing (4casters) maps onto PMs: Kalshi and Polymarket function as square markets monetizing price-insensitive retail takers, while sharp PMs optimizing for trading efficiency will likely thrive outside the US. (Specific fee comparisons and the 4casters 0.5%→0.75% no-impact experiment are in their tweet body, not in our fetched digest.)
+- Sealaunch's wallet analysis (per their *Is Polymarket a Retail Product or a Pro Trading Venue?* article) argues a small fraction of Polymarket wallets · high-frequency, high-capital · generates the bulk of platform volume; crypto markets are dominated by algorithmic execution while politics markets are casual event-driven. Optimizing for user growth vs volume growth therefore requires fundamentally different product decisions. (Specific 2%/90% wallet split figures not in our fetched digest body.)
+- Pantera Research Lab argues Kalshi reprices faster while Polymarket has deeper liquidity · a price discovery vs liquidity-depth tradeoff between centralized and on-chain order-book architectures (specific median-lead and volume-multiple figures appear in the original Pantera post, which was not fetched into our digest set).
+- Per Kaviish, valuations price an information-infrastructure future while revenue still rests heavily on sports · and distribution platforms (Robinhood, Coinbase) will capture most value as they vertically integrate into exchange infrastructure. (The 83%-of-volume figure is in Kaviish's article but not in our fetched digest body.)
+- Builders should pick lanes carefully. Marvellous's "uncomfortable truth": challengers face a binary choice · compete for venue liquidity against incumbents or build decision-support tools (analytics, conviction-sizing, mispricing detection) for power users. The most valuable layer may not be the marketplace itself.
+- Jake Nyquist's 7 axes of differentiation: product quality, asset variety, capital efficiency, oracle reliability, liquidity provision, regulatory compliance, and vertical vs horizontal strategy. Polymarket = horizontal; Kalshi = vertical.
+- Median PM user has -8% ROI; median sportsbook user is -5%. Only traders above $500K in volume achieve +2.6% positive returns (Jordan Bender, Wall Street equity research). PMs attract sharper competition than regulated sportsbooks, creating worse outcomes for casual retail.
+- The Nielsen Moment thesis (Mehmet Avci): coordination value matters more than accuracy. Polymarket's Golden Globes/WSJ partnerships and Kalshi's CNN deal mean these markets are becoming the shared reference point. Once embedded as the institutional yardstick, displacement becomes near-impossible.
+- mph argues the space is still pre-cult: no NFT/meme-coin-style community formation yet, nascent builder programs, permissionless markets still upcoming. Polymarket already prices most sports events more aggressively than traditional sportsbooks.
+- HIP-4 from Hyperliquid is a fee-bearing options layer rather than a competing PM venue (Pink Brains). Unified margin engine and on-chain token value capture differentiate it from Kalshi/Polymarket positioning.
+- Yuri Gubert argues Brazil is a large serviceable PM opportunity (5th-largest betting market and 5th in crypto adoption globally, per his tweet body which is not in our fetched digest). CVM classification (derivative vs gambling) determines whether institutional capital can plug in.
+- Gouker's DFS parallel: duopoly dynamics, state regulatory pushback, industry self-regulation attempts, casino industry opposition · all four pattern-match to the 2013-2015 DFS boom. The script for prediction markets may already be written.
+
+## Notable Quotes
+
+> "No trading fees and no revenue isn't a bug, it's a feature in service of growth and deeper liquidity."
+> — *Camilo, *Thoughts on Polymarket Network Effects**
+
+> "The polymarket network effect has reached escape velocity. … polymarket is becoming the market's common denominator, connected in one way or another to every asset class."
+> — *Camilo, *Thoughts on Polymarket Network Effects**
+
+> "We all want to be on the other side of the public; that's the dream. Being a market maker is highly attractive."
+> — *professional bettor cited in Jordan Bender, *Prediction Markets vs. Sports Betting**
+
+## Where It Matters
+
+Platform competition is the macro game inside which every micro mechanism (oracles, liquidity, fees) gets evaluated. Once a venue locks in as the institutional reference (Nielsen moment), distribution moats compound and challengers have to go orthogonal. For Dekant specifically: the verdict is that pure venue competition against Polymarket/Kalshi is brutal · wedges like continuous payouts, AI tooling, or specific verticals (Brazil, long-tail) are the openings.
+
+## Related Concepts
+
+- Network effects · why Polymarket's news-cycle visibility compounds
+- Distribution moat · Robinhood's cross-asset advantage
+- Market structure · rails vs wrappers stratification
+- Regulatory classification · determines which lane a platform plays in
+- Long-tail markets · where competition is still open
+- Polymarket · the dominant crypto-native venue
+
+## Sources
+
+- [Prediction Markets As Follow-Up To DFS: The Parallels Are Strikingly Similar](https://www.ingame.com/prediction-markets-daily-fantasy-sports-parallels/) — Dustin Gouker · May 14 2026 ·
+- [Why Robinhood Will Eat Kalshi's Lunch](https://x.com/i/article/2052782036688228352) — DCo · May 11 2026 ·
+- [Benchmarks Are Key to Scale Prediction Markets Institutionally. Question: Which Ones Can Deliver?](https://x.com/lzminsky/status/2051741765682508073) — Lauris · May 5 2026 ·
+- [HIP-4 Is Not a Prediction Market - It's the Options Layer: A Full Guide](https://x.com/PinkBrains_io/status/2051312783074226606) — Pink Brains · May 4 2026 ·
+- [Prediction Markets In Q1 2026: 11 Insights Worth Knowing](https://x.com/Ommiii_/status/2047582772387291361) — Omar · Apr 24 2026 ·
+- [Good Forecasts, Bad Products](https://mo-el.xyz/blog/good-forecasts-bad-products/) — Mohamed Elrashid · Apr 24 2026 ·
+- [Polls Are Dead. Long Live Prediction Markets.](https://x.com/CalBlockchain/status/2047460674532790499) — Blockchain at Berkeley · Apr 23 2026 ·
+- [Prediction Markets: They Grow Up So Fast](https://open.substack.com/pub/a16z/p/prediction-markets-they-grow-up-so) — Alex Immerman, Santiago Rodriguez · Apr 16 2026 ·
+- [From Betting to Trading: How Kalshi Is Reshaping Sports Markets](https://x.com/Kunallegendd/status/2044780671839952949) — Kunal Doshi · Apr 16 2026 ·
+- [Are We on the Brink of a Prediction Market Supercycle?](https://x.com/mphrediction/status/2043419029378048164) — mph · Apr 12 2026 ·
+- [The Two Kinds of Prediction Markets](https://x.com/4castersbet/status/2042268072266920213) — 4casters · Apr 9 2026 ·
+- [How Prediction Markets Are Blurring the Line Between Trading and Betting](https://www.bloomberg.com/news/articles/2026-04-09/how-prediction-markets-are-blurring-the-line-between-trading-and-betting) — Christopher Beam · Apr 9 2026 ·
+- [Brazil: The Next Frontier for Prediction Markets](https://x.com/Yuri_Gubert/status/2041513296608571791) — Yuri Gubert · Apr 7 2026 ·
+- [Parimutuel Prediction Markets](https://x.com/meleemarkets/status/2041244791716110409) — Melee · Apr 6 2026 ·
+- [The Financialization of Uncertainty](https://x.com/kaviish/status/2041214800731033700) — Kaviish · Apr 6 2026 ·
+- [The State of Prediction Markets](https://stateofpredictionmarkets.blocmates.com/) — blocmates · Apr 1 2026 ·
+- [Is Polymarket a Retail Product or a Pro Trading Venue?](https://x.com/sealaunch_/article/2037228250116522082) — sealaunch intelligence · Mar 27 2026 ·
+- [Information as Supply](https://x.com/functionspaceHQ/article/2035959494728176075) — functionSPACE · Mar 23 2026 ·
+- [Prediction Markets vs. Sports Betting: Market Dynamics, ROI by Cohorts, and Competitive Implications](https://citizens.bluematrix.com/docs/pdf/3beb4505-5a8b-49da-89df-ebdb104ebea9.pdf) — Jordan Bender · Mar 23 2026 ·
+- [What's Kalshi's Revenue? Analyzing All 203 Million Trades on Kalshi.](https://read.technically.dev/p/whats-a-prediction-market) — Sam Schneider · Mar 12 2026
+- [Are Prediction Markets Decaying or Evolving?](https://x.com/_drNeyo/article/2029205512391172364) — Eniola · Mar 4 2026 ·
+- [Prediction Markets Are the Future of Advertising](https://x.com/sidbharth/article/2027740100214603934) — Sid · Feb 28 2026 ·
+- [Prediction Markets: The Path to $1 Trillion and What Needs to Happen Next](https://x.com/a1research__/article/2022340314770375024) — Sakshi Mishra · Feb 14 2026 ·
+- [Building the Truth Machine](https://freesystems.substack.com/p/building-the-truth-machine) — Andy Hall, Elliot Paschal · Feb 13 2026 ·
+- [The 7 Axes of Prediction Markets](https://x.com/Jake_Nyquist/article/2021222519991124343) — Jake Nyquist · Feb 10 2026 ·
+- [The Super Bowl of Prediction Markets: Kalshi and Polymarket's Battle for Price vs Liquidity](https://panteraresearchlab.xyz/research/the-super-bowl-of-prediction-markets-kalshi-and-polymarkets-battle-for-price-vs-liquidity/) — Ally Zach, Danning Sui · Feb 5 2026 ·
+- [Prediction Markets as an Asset Class](https://x.com/akshayraj_v0/article/2016909929920233802) — Akshay · Jan 29 2026 ·
+- [The Uncomfortable Truth Prediction Market Builders Must Accept](https://x.com/marvellousdefi/article/2015779074317209696) — Marvellous · Jan 26 2026 ·
+- [The Shape of Prediction Markets to Come](https://www.galaxy.com/insights/research/prediction-markets-leverage-ai-agents-defi) — Will Owens · Jan 19 2026 ·
+- [The Nielsen Moment for Prediction Markets](https://reachavci.substack.com/p/the-nielsen-moment-for-prediction) — Mehmet Avci · Jan 12 2026 ·
+- [Dopamine Markets: 2025 Annual Letter](https://www.dopaminemarkets.com/p/dopamine-markets-2025-annual-letter) — Shreyas Hariharan · Jan 8 2026 ·
+- [The Cambrian Explosion of Prediction Markets](https://x.com/klos/status/1954094865991500104) — klos · Aug 9 2025 ·
+- [Next Gen Prediction Markets](https://x.com/socialgraphvc/status/1952808508396552534) — Social Graph Ventures · Aug 6 2025 ·
+- [Prediction Markets GTM Approaches](https://x.com/masonnystrom/status/1950395287975186715) — Mason Nystrom · Jul 31 2025 ·
+- [Thoughts on Polymarket Network Effects](https://x.com/curiouscamilo/status/1948155161827836149) — Camilo · Jul 24 2025 ·
+- [The Definitive Guide to Prediction Markets](https://docsend.com/view/atcc6k258s4umq6s) — Four Pillars · Jan 15 2025 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/polymarket.md b/.qoder/plans/pm-plan/theory_concepts/polymarket.md
new file mode 100644
index 0000000000..c2bdac3e46
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/polymarket.md
@@ -0,0 +1,56 @@
+# Polymarket
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — Polymarket](https://www.pmatlas.xyz/concepts/polymarket)
+
+---
+
+## Definition
+
+**Quick definition.** A crypto-based prediction market platform on Polygon, enabling peer-to-peer trading of binary event contracts settled via trusted oracles (UMA + Fox News for political markets in 2024).
+
+## Key Insights
+
+- Polymarket is the canonical crypto-native PM. Built on Polygon, uses Gnosis Conditional Token Framework, UMA oracle for resolution disputes. ~1.6M cumulative unique users and $9B+ in election volume during the 2024 cycle (per Galaxy and ASXN).
+- Gouker's DFS parallel positions Polymarket and Kalshi as duopoly mirrors of DraftKings and FanDuel circa 2014. State regulatory pushback, industry self-regulation attempts, casino industry opposition · all four DFS-era patterns now apply.
+- The Dudley & Magdaleno paper directly tests Polymarket's forecasting accuracy in a non-political domain: Polymarket forecasts for weekly cumulative US flu hospitalizations and monthly measles cases (2025–2026) fail to outperform standard benchmarks · for flu they are dominated by the CDC FluSight ensemble (the optimal combination puts "zero weight on the markets"), and for measles they are outperformed by simple statistical baselines. Two failure modes diagnosed: "placement of probability mass on impossible outcomes (e.g., decreasing values in cumulative forecasts) and low trading volume."
+- The infectious disease failure undermines the "PMs as truth machines" framing outside their high-volume sports/politics core · speaks to Sakshi Mishra's category-by-category sustainability question.
+- Polymarket structural metrics (cross-referenced across cluster articles): 99.2% of trading volume in political markets historically (ASXN); 166:1 visit/MAU ratio (Lydia Wu); 2% of wallets generate ~90% of volume (sealaunch); 1.3% of political markets liquid enough to be manipulation-resistant (Hall & Paschal).
+- Dune analysis of Polymarket's fast crypto markets (Sep 2025 - Mar 2026): 5-minute contracts overtook 15-minute in weeks ($2.3B vs $795M notional); bots control 55-62% of volume across fast markets; Bitcoin drives 77% of turnover. $23.7M in taker fees collected in 83 days via maker-rebate model · structurally converged with a derivatives exchange.
+- Camilo: Polymarket has reached network-effect escape velocity. Zero trading fees is a growth feature, not a revenue bug. Every news cycle trojan-horses the platform into the conversation.
+- The 2024 election surfaced manipulation pathology (Prosperi): four coordinated accounts controlled 23% of open interest, ~41% of volume appeared to be wash trading. The Detection of Wash Trading (Sethi/Sirolly/Ma/Kanoria) algorithmically identified a 200-wallet cluster generating $113M in volume with $57.86 aggregate losses.
+- Lou Kerner's conspiracy: Polymarket's oracle architecture is the manipulation surface. Fox News chosen as a settlement oracle for the 2024 race; UMA token holders could sway disputed resolution votes given UMA's small market cap.
+- The Venezuela election dispute (Hall, a16z crypto) is the canonical Polymarket resolution failure: $6M+ traded on the outcome, but when votes were counted the government declared Maduro the winner while opposition + international observers alleged fraud. The platform faced an unresolvable epistemic choice.
+- Ukraine map manipulation and the government shutdown contract are two more Hall-cited Polymarket resolution failures, showing that adversarial inputs and OPM-website-as-oracle both fail at scale.
+- Polymarket is the offshore arbitrage target (Sethi, Information Contagion): pseudonymous trades that show up on Polymarket get extracted by quant funds and acted on in KYC venues like CME · a cross-platform information leak the legal framework can't reach.
+- Lydia Wu reframes Polymarket as crypto media/creator economy rather than pure event trading. Users are older and less focused on maximizing risk-reward than typical crypto traders.
+
+## Notable Quotes
+
+> "Across both settings, prediction markets fail to outperform standard benchmarks."
+> — *Carson Dudley & Reiden Magdaleno, *Prediction Markets Underperform Simple Baselines For Infectious Disease Forecasting**
+
+> "Even when we combine market forecasts with the ensemble, the best combination puts zero weight on the markets."
+> — *Dudley & Magdaleno (on Polymarket vs CDC FluSight for flu)*
+
+> "Polymarket not only becoming the future of news, but also vital infrastructure for the future of financial markets."
+> — *Camilo, *Thoughts on Polymarket Network Effects**
+
+## Where It Matters
+
+Polymarket is both the proof-of-concept that crypto-native PMs scale and the lab where every PM design pathology shows up first (resolution disputes, wash trading, ghost markets, regulatory enforcement). It's the de facto comparison every new platform measures itself against. For Dekant: Polymarket on Polygon is the direct competitor for crypto-native flow · but its binary-only architecture leaves the continuous-payout lane open, and its UMA-oracle design is the manipulation surface Dekant's smooth-kernel resolution avoids.
+
+## Related Concepts
+
+- Platform competition · Polymarket vs Kalshi as the prevailing duopoly
+- Network effects · Polymarket as the escape-velocity case
+- Election markets · Polymarket's volume engine in 2024
+- Oracle design · UMA-based dispute resolution is its mechanism
+- Market manipulation / wash trading · heavily documented on Polymarket data
+- Information aggregation · both proof and counterexample depending on domain
+
+## Sources
+
+- [Prediction Markets As Follow-Up To DFS: The Parallels Are Strikingly Similar](https://www.ingame.com/prediction-markets-daily-fantasy-sports-parallels/) — Dustin Gouker · May 14 2026 ·
+- [Prediction Markets Underperform Simple Baselines For Infectious Disease Forecasting](https://arxiv.org/abs/2605.11220) — Carson Dudley, Reiden Magdaleno · May 11 2026 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/position-collateralization.md b/.qoder/plans/pm-plan/theory_concepts/position-collateralization.md
new file mode 100644
index 0000000000..d85f761377
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/position-collateralization.md
@@ -0,0 +1,57 @@
+# Position Collateralization
+
+**Category:** Mechanism Design
+**Source:** [PM Atlas — Position Collateralization](https://www.pmatlas.xyz/concepts/position-collateralization)
+
+---
+
+## Definition
+
+Using prediction market positions as collateral to borrow capital, unlocking liquidity without selling. Addresses the capital lock-up problem in long-dated markets and enables composability with the broader DeFi ecosystem · but is structurally hard because binary outcomes resolve instantly, breaking standard liquidation engines.
+
+## Key Insights
+
+- DWF Ventures (Apr 2026) frames prediction markets as evolving into a *financial derivatives asset class.* Structural barriers to leverage and collateral lending: jump risk, binary valuation, regulatory uncertainty. Emerging solutions surveyed: epoch-based fee models, perpetual futures on outcomes, tokenized positions on Solana.
+- Darren / 0xMims (Apr 2026) landscape report · **four leverage models**: (1) lending pools (Gondor / Morpho-style), (2) prime brokers (Ultramarkets), (3) synthetic desks (CFD counterparties), (4) perpetual futures (dYdX TRUMPWIN).
+- Fee revenue opportunity sized at **$15M base case to $50.7M bull case**, with **87% driven by financing revenue on open interest** · *not* trading fees. This is a structurally different revenue model from spot prediction markets.
+- All four leverage models share a *structural dependency on CLOB venue architecture* that degrades during jump events. This is the gap-risk problem at its sharpest: liquidation engines assume continuous price paths, but binary resolution is a discontinuous jump to 0 or 1.
+- keshav (Sep 2025) · the canonical "next level" piece. Proposes position lending as a DeFi primitive that solves capital lock-up in long-dated markets, corrects persistent mispricings (e.g., longshot bias) by enabling capital-efficient counter-positioning, and opens composability. Explicitly flags the liquidation risks unique to binary outcomes.
+- Long-dated markets are the killer use case for collateralization: a position held for 6+ months is dead capital unless it can be margined.
+- Longshot bias correction is mechanistically interesting · if traders could borrow against their "no" positions on longshots, more capital would flow against overpriced longshots, tightening prices toward truth.
+- Gap risk is the architectural problem all collateral schemes must engineer around. The dYdX TRUMPWIN perp on election night 2024 is the canonical failure case (sophisticated safeguards still broke under real conditions; referenced in Ruzicka's leverage piece under Binary Contracts).
+- Adverse selection compounds the lending problem: if informed traders prefer leveraged longs, lenders face systematic loss exposure.
+- Regulatory classification is a hard gate · collateralized prediction-market positions look like derivatives to regulators, which triggers a different (and stricter) compliance regime than the prediction-market-as-event-contract framing.
+- The DWF piece flags **tokenized positions on Solana** as a specific emerging solution · relevant context for Dekant since positions can be represented as composable tokens given Solana's account model.
+
+## Notable Quotes
+
+> "87% of the fee revenue opportunity is driven by financing revenue on open interest rather than trading fees."
+> — *Darren / 0xMims, *Leverage in Prediction Markets**
+
+> "All four models share a structural dependency on CLOB venue architecture that degrades during jump events."
+> — *Darren / 0xMims, *ibid.**
+
+> "Collateralization solves the capital lock-up problem in long-dated markets."
+> — *keshav, *Prediction Markets: The Next Level**
+
+## Where It Matters
+
+Collateralization is what turns prediction markets from a closed-loop betting venue into a *capital market.* The economics of leverage suggest financing revenue (87% of opportunity) dwarfs trading fees, which means the platform that solves gap-risk-aware liquidation has a much larger TAM than the spot-volume leaderboard suggests. For Dekant, continuous outcomes are *more* friendly to liquidation engines than binaries (the curve doesn't discretely jump at settlement · it's progressively revealed), so the design surface for collateralization on continuous markets is unexplored.
+
+## Related Concepts
+
+- Gap risk · the central architectural problem
+- Liquidity provision · collateralization recycles locked capital into new liquidity
+- Arbitrage · collateral capital can finance arbitrage between platforms
+- Longshot bias · collateralization is a proposed corrective mechanism
+- Adverse selection · lenders face informed-trader exposure
+- Market making · leveraged market makers vs spot market makers
+- Regulatory classification · leverage triggers derivatives treatment
+- Event contracts · the legal wrapper that collateralization sits inside
+
+## Sources
+
+- [A Second Identity: Prediction Markets as Financial Derivatives](https://x.com/i/article/2049414664946323456) — DWF Ventures · X
+- [Leverage in Prediction Markets](https://x.com/0xMims/status/2041498106181627918) — Darren · X
+- [Prediction Markets: The Next Level](https://x.com/0xkeshav_/article/1970163909224169626) — keshav · X
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/price-discovery.md b/.qoder/plans/pm-plan/theory_concepts/price-discovery.md
new file mode 100644
index 0000000000..77d2ed4791
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/price-discovery.md
@@ -0,0 +1,96 @@
+# Price Discovery
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Price Discovery](https://www.pmatlas.xyz/concepts/price-discovery)
+
+---
+
+## Definition
+
+The process through which trading activity reveals the fair value or true probability of an event. In prediction markets, price discovery is *higher-bandwidth* than in equities because contracts reference explicit, time-bounded events · but several papers in 2025–26 show that PM prices systematically diverge from true probabilities even with rational traders.
+
+## Key Insights
+
+- Market prices are NOT real probabilities. Three structural reasons prices diverge from true probabilities even with rational participants: favorite-longshot bias from Kelly betting, risk-premium distortion from market correlation, and risk-neutral forward pricing in long-dated contracts. Markets still outperform individuals because they weight *capital-backed* beliefs (Lihong).
+- LessWrong "Will Jesus Return in an Election Year?" (FULL_READ) sharpens this: Polymarket's 3% trade price for Jesus 2025 wasn't about belief · it was about the *time value of Polymarket cash*. "No" sellers needed cash for other markets during 2024 election cycle. Harris-in-Kentucky shot from 0.3% to 1.5% on election day not because the underlying probability changed but because traders needed liquidity. The "correct" no-arb price for the Jesus market actually reflects the implied carry cost · replacing it with "This Market Will Resolve No At The End Of 2025" would price the time value alone.
+- Prediction markets make information *legible* in a way stocks/options do not · when someone bets big on an attack on Maduro, everyone immediately knows what the bet is about. Legibility is a feature even when it surfaces uncomfortable implications (Andrew Courtney).
+- CLOBs beat constant-product AMMs for binary events. The YES/NO minting and merging invariant lets depth expand whenever matched counterparties exist; probability-scaled dynamic fees shrink near 0 and 1 (Ranger Global). Regression of PM midpoints against BTC spot finds PM traders systematically underreact to spot moves by 10–20%, and latency under 100ms now captures 73% of arbitrage profits.
+- Binary prediction markets are consumer-wrapped binary options. Minsky's "vega wedge" is the structural overcharge binary hedgers pay when they replicate via vanilla options (a structural overcharge (magnitude varies by asset; the specific ~4.8%/7-20% figures are unverified)). PMs can undercut this tax in deep-volume categories but lack a Black-Scholes-equivalent pricing language (0xturbanurban).
+- LMSR-based AMMs structurally failed for prediction markets: in a binary market that resolves to 0 or 1, impermanent loss becomes permanent · the pool inevitably holds worthless shares on the losing side, and trading fees cannot offset a guaranteed structural loss. Polymarket migrated from LMSR AMM to CLOB in late 2022 (Melee).
+- Roughly 3% of accounts drive most price discovery (Gomez-Cram et al.). PM accuracy is informed-minority-driven, not crowd-driven.
+- Information contagion: insider trades on Polymarket may leak into regulated oil and stock futures markets · quant funds extract signal from pseudonymous crypto trades and act on it in KYC venues, without breaking existing laws (Sethi).
+- "Semantic tick size": Polymarket orderbook analysis on 600M datapoints · ~70% of one-cent price moves do not continue in the same direction. The minimum price increment doubles as a *narrative unit* because each penny reads as a 1pp probability change, creating overreactions that contrarian fade strategies can profitably harvest (allquantor).
+- Hedge-fund alpha hides nowhere in prediction markets: binary resolution eliminates the unobservable noise that obscures strategy quality in traditional finance. LMSR's mathematical identity with softmax bridges quant finance and PM pricing (gemchanger).
+- "Minimum viable liquidity": 149 CPI markets on Kalshi (2021–26) · trading volume explains <1% of variance in forecast accuracy. MVL = Cost of Expertise / Price Gap. Platforms should prioritize *breadth over depth*, running many thin markets rather than concentrating volume in few (Adhi Rajaprabhakaran).
+- Polymarket vs Kalshi NFL markets (2025): Kalshi reprices faster (median 7-second lead); Polymarket has deeper liquidity (3–4x more volume needed to move prices comparably). Kyle-style market-impact analysis (Pantera Research Lab).
+- Prediction markets don't bend reality. Unlike stock markets, PMs lack causal mechanisms through which odds could influence the events they forecast · they're thermometers, not thermostats (Adhi Rajaprabhakaran response to Kyla Scanlon).
+- AI underperforms humans in PMs because edge comes from *embodied, local knowledge* (monitoring flights, calling embassies) · not synthesizing public information. 5 of 6 AI models in Kalshi "Prediction Arena" are underwater (Mehmet Avci).
+- 72M Kalshi trades · three persistent biases: longshot bias (5c contracts win 4.18%), maker-taker asymmetry (makers outperform at 80/99 price levels), YES/NO asymmetry (YES buyers -1.02% vs NO buyers +0.83%). Finance markets are most efficient (0.17% spread); crypto least (2.69%) (Ranger Global).
+- Election market accuracy varies dramatically across platforms: PredictIt 93%, Kalshi 78%, Polymarket 67%. Cross-platform price divergences near Election Day are large; CNN/CNBC media partnerships create incentives for sensational coverage of thin markets (John Sides).
+- Polymarket arbitrage: ~$40M in profits extracted through within-market and cross-market mispricings (Saguillo, Ghafouri, Kiffer, Suarez-Tangil 2025).
+- Batched auctions > CLOBs: no practical social benefit from sub-second reaction times; batching redirects trader effort to meaningful questions while reducing zero-sum speed competition (Will Howard).
+- Exchange model beats sportsbook: Betfair's ~3% overround vs bookmakers' ~12% · peer-to-peer produces fairer pricing and welcomes all winners, unlike sportsbooks that limit successful bettors (Jay Malavia).
+- 817-market field experiment: prices can be manipulated with effects persisting for months, though they gradually fade. Markets with more traders, higher volume, and external probability anchors prove more resistant (Rasooly & Rozzi 2025).
+- Will Jesus return? Polymarket at 3% with $100k wagered · buying No to 1% requires locking $1M for 1% annual return (worse than Treasuries). The piece's bigger insight is the carry-trade interpretation of long-dated "obviously wrong" markets: they price the implied interest rate on PM dollars (LessWrong).
+- Pantera Research NFL study (FULL_READ): "notional trading volume has expanded by an order of magnitude" 2025→2026, "weekly aggregate volume across major prediction market platforms now regularly reaches billions of dollars." Polymarket = offchain order book + onchain settlement; Kalshi = fully off-chain. Critically, Polymarket applies a special rule for live sports markets that distinguishes its market microstructure from Kalshi's.
+- Kevin Heavey's *Futarchy as Trustless Joint Ownership* (FULL_READ): reframes futarchy not as decision-making, but as the only mechanism that gives minority DAO shareholders enforceable rights. Decision markets prevent majority looting because conditional ABC-if-proposal-passes prices fall, so the proposal fails. Coin price is "the fairest and most elegant objective function." Token voting DAOs without futarchy provide only "the illusion of joint ownership."
+
+## Notable Quotes
+
+> "70% of one-cent price moves do not continue in the same direction."
+> — *allquantor, "Prediction Markets Have a Semantic Tick Size"*
+
+> "Trading volume explains less than 1% of variance in forecast accuracy."
+> — *Adhi Rajaprabhakaran, "Minimum Viable Liquidity"*
+
+> "Prediction markets lack causal mechanisms through which odds could influence the events they forecast"
+> — *thermometers rather than thermostats." · Adhi Rajaprabhakaran*
+
+## Where It Matters
+
+Price discovery is the product. The CLOB-beat-AMM result (Melee) is the canonical reason Polymarket migrated and why anyone building a binary PM today defaults to order books · *but the same argument doesn't apply to distribution markets*, where multiple correlated outcomes share a liquidity surface and LMSR/L2-norm CFAMM-style invariants are again viable. The "semantic tick size" and "minimum viable liquidity" findings should reset what builders chase: thicker books don't improve signal; better contract specification and informed-trader recruitment do. For Dekant: the curve-drawing primitive is partly an answer to "the binary market flattens 8 bits to 1" · a richer surface gives informed minorities more dimensions on which to express their edge.
+
+## Related Concepts
+
+- **Information aggregation** · price discovery is the surface; aggregation is the process
+- **Forecasting accuracy** · measures price-discovery quality
+- **Market making / LMSR** · the mechanism that produces discovery in AMMs
+- **Adverse selection / Toxic flow** · the cost MMs pay for the discovery service
+- **Longshot bias / Yes bias** · systematic distortions to discovery
+- **Legibility / Endogeneity** · second-order effects of discovered prices on the underlying event
+- **Distribution markets** · argued as higher-dimensional discovery
+- **Minimum viable liquidity** · counterintuitive limit on how much depth helps
+- **Semantic tick size** · the narrative-unit distortion specific to binary PMs
+
+## Sources
+
+- [Market Probabilities Are NOT Real Probabilities](https://x.com/Lihong062/status/2051017075758686622) — Lihong · May 3, 2026 [FULL_READ]
+- [Legibility](https://whirligigbear.substack.com/p/legibility) — Andrew Courtney · Apr 27, 2026 [FULL_READ]
+- [Predictions Are The New Expression](https://x.com/abhitejxyz/status/2047353485700825546) — Abhitej · Apr 24, 2026 [FULL_READ]
+- [Anatomy Of A New Asset Class I: How Markets Turn Capital Into Probability](https://x.com/Ranger_Global/status/2046547375649427572) — Ranger Global · Apr 21, 2026 [FULL_READ]
+- [The Bane Of Binaries: What Prediction Markets Are Missing](https://x.com/0xturbanurban/status/2044433520806830101) — 0xturbanurban · Apr 15, 2026 [FULL_READ]
+- [Why AMMs Failed Prediction Markets](https://x.com/meleemarkets/status/2043766417342755259) — Melee · Apr 13, 2026 [FULL_READ]
+- [Prediction Market Accuracy: Crowd Wisdom Or Informed Minority?](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6617059) — Gomez-Cram, Guo, Jensen, Kung · Apr 1, 2026 [FULL_READ]
+- [Information Contagion](https://rajivsethi.substack.com/p/information-contagion) — Rajiv Sethi · Mar 31, 2026 [FULL_READ]
+- [Prediction Markets Have a Semantic Tick Size](https://x.com/ZEITFinance/article/2034314079599243694) — allquantor · Mar 19, 2026 [FULL_READ]
+- [Ahead of the Headlines: Prediction Markets and the Collective Mind](https://jprz1321.substack.com/p/ahead-of-the-headlines-prediction) — JP · Feb 25, 2026 [FULL_READ]
+- [Your Hedge Fund's Sharpe Ratio Is a Lie. Prediction Markets Are the Only Place It Can't Hide.](https://x.com/gemchange_ltd/article/2026300422483271733) — gemchanger · Feb 25, 2026 [FULL_READ]
+- [Minimum Viable Liquidity](https://fiftycentdollars.substack.com/p/minimum-viable-liquidity) — Adhi Rajaprabhakaran · Feb 24, 2026 [FULL_READ]
+- [Polymarket Is Not a Casino. Why Prediction Markets Are Finance, Not Gambling](https://x.com/13_niakris/article/2025614474263093627) — Niakris · Feb 23, 2026 [FULL_READ]
+- [The Super Bowl of Prediction Markets: Kalshi and Polymarket's Battle for Price vs Liquidity](https://panteraresearchlab.xyz/research/the-super-bowl-of-prediction-markets-kalshi-and-polymarkets-battle-for-price-vs-liquidity/) — Ally Zach, Danning Sui · Feb 5, 2026 [FULL_READ]
+- [Prediction Markets Don't Bend Reality](https://fiftycentdollars.substack.com/p/prediction-markets-dont-bend-reality) — Adhi Rajaprabhakaran · Feb 3, 2026 [FULL_READ]
+- [Is AI Any Good at Predicting?](https://reachavci.substack.com/p/is-ai-any-good-at-predicting) — Mehmet Avci · Feb 2, 2026 [FULL_READ]
+- [Prediction Market Biases Revealed in 72 Million Trades](https://x.com/Ranger_Global/article/2016867581743747447) — Ranger Global · Jan 29, 2026 [FULL_READ]
+- [Information Vectors: An Intro to Composable Beliefs](https://x.com/functionspaceHQ/article/2014809461647671570) — functionSPACE · Jan 24, 2026 [FULL_READ]
+- [Manifesto: Make Precision Pay](https://x.com/TideMarkets/article/2008508916616098086) — Tide · Jan 6, 2026 [FULL_READ]
+- [The Perils of Election Prediction Markets](https://goodauthority.org/news/the-perils-of-election-prediction-markets/) — John Sides · Dec 18, 2025 [FULL_READ]
+- [Who Are You Really Playing Against?](https://x.com/thejay/article/1968392782998835518) — Jay Malavia · Sep 18, 2025 [FULL_READ]
+- [Unravelling the Probabilistic Forest: Arbitrage in Prediction Markets](https://arxiv.org/abs/2508.03474) — Saguillo, Ghafouri, Kiffer, Suarez-Tangil · Aug 5, 2025 [FULL_READ · abstract: empirically finds ~$40M of executed profits from Market Rebalancing and Combinatorial Arbitrage on Polymarket using on-chain order-book data]
+- [Many Prediction Markets Would Be Better Off as Batched Auctions](https://antidiluvian.substack.com/p/many-prediction-markets-would-be) — Will Howard · Aug 2, 2025 [FULL_READ]
+- [Will Jesus Christ Return in an Election Year?](https://www.lesswrong.com/posts/LBC2TnHK8cZAimdWF/will-jesus-christ-return-in-an-election-year) — LessWrong · Mar 25, 2025 [FULL_READ]
+- [How Manipulable Are Prediction Markets?](https://arxiv.org/abs/2503.03312) — Rasooly & Rozzi · Mar 5, 2025 [FULL_READ · abstract]
+- [Futarchy as Trustless Joint Ownership](https://www.umbraresearch.xyz/writings/futarchy) — Kevin Heavey · Oct 28, 2024 [FULL_READ]
+- [Unveiling Polymarket: The Positioning, Expansion, and Shadows of Crypto Prediction Markets](https://research.mintventures.fund/2024/10/09/Unveiling-Polymarket-The-Positioning-Expansion-and-Shadows-of-Crypto-Prediction-Markets/) — Lydia Wu · Oct 9, 2024 [FULL_READ]
+- [Deep Dive #8 | Decentralized Prediction Markets](https://ampbura.substack.com/p/deep-dive-8-decentralized-prediction) — Amp Burapachaisri · Feb 23, 2024 [FULL_READ]
+- [Prediction Markets Explained](https://alternativeassets.substack.com/p/prediction-markets-explained) — Stefan von Imhof · Feb 4, 2024 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/probability-infrastructure.md b/.qoder/plans/pm-plan/theory_concepts/probability-infrastructure.md
new file mode 100644
index 0000000000..c001d2d555
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/probability-infrastructure.md
@@ -0,0 +1,53 @@
+# Probability Infrastructure
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Probability Infrastructure](https://www.pmatlas.xyz/concepts/probability-infrastructure)
+
+---
+
+## Definition
+
+The concept of prediction market mechanisms as a general-purpose layer that embeds live probability signals into decision surfaces beyond trading · attention, credibility, demand. The "endgame" framing where PMs become invisible plumbing, not destination products.
+
+## Key Insights
+
+- Forecasting accuracy has outpaced product design. A decade inside the US intelligence community produced zero complaints about forecast *quality*, yet forecasting firms remain niche while *simulation* startups (Aaru, Simile) raise nine-figure rounds. Gap: failure to embed forecasts into institutional workflows. Recommendation: forecasting companies hire deployment managers to transform probabilities into artifacts clients can act on (Mohamed Elrashid, "Good Forecasts, Bad Products").
+- Dan Schwarz analyzes ~13,500 Polymarket contracts: >80% volume in sports/crypto/elections; accuracy on "useful" markets hasn't improved since early 2025. AI chatbots may supersede PMs as the primary forecasting interface, leaving markets to serve an *epistemic role as common-knowledge infrastructure* · a Schelling-point shared probability, not a personal-use tool (Asterisk).
+- Probability layers thesis: PMs are a proof-of-concept for a broader shift · probability as infrastructure. Three layers beyond trading: attention markets (price content virality forward), credibility markets (trust as continuously updated score), demand markets (consumer intent before production). Endgame: probability signals embedded invisibly into every decision surface on the internet (Aggie).
+- Elrashid (FULL_READ) specifics: Cultivate Labs ran a decade-long PM inside US IC, decommissioned in 2020 for institutional reasons. "In 10+ years, I can't remember a single time that someone told us they had issues with the forecasts not being accurate enough." Open Philanthropy / Coefficient Giving has put $50M+ across 30+ grants into forecasting field; the field has not changed how important decisions get made. Good Judgment Inc., Hypermind (Paris, 2014), INFER/RAND Forecasting Initiative · all respected, all "muted" impact.
+- Elrashid on the simulation alternative: Aaru built a synthetic panel of 3,600 respondents that replicated a 6-month wealth research survey in a day for EY, sometimes more accurately than the original surveys. Aaru raised a $50M Series A in Dec 2025 at ~$1B valuation (customers: Accenture, EY, IPG, political campaigns). Simile raised $100M Series A Feb 2026 from Index, building on Joon Park's generative-agents work (customers: CVS · 9,000 stores shelf placement, Gallup, Wealthfront). Mantic, focused on probability briefs, raised $4M pre-seed in same period. Implication: the format of the output determines the market, not the underlying forecast quality.
+- Elrashid's prescription: forecasting companies need to hire "Deployment Managers and Forward Deployed Engineers" (Palantir-style) · turn probabilities into the shape the customer's decision loop accepts. Target buyer profiles: scenarios teams at energy majors, hedge funds, Lloyd's-style specialty insurance, foreign ministries, policy planning staffs.
+- Schwarz (FULL_READ) on the substitution risk: ChatGPT/Claude/Gemini already serve as primary forecasting interfaces for casual users, providing all 5 use-categories (risk monitoring, news interpretation, policy outcomes, accountability, novel info) implicitly. Schwarz's bet: "before [PMs deliver Vitalik's info-finance vision], Claude will be the only forecaster anyone will ever want to ask about the future." But there's an institutional channel for PMs: "By accelerating the adoption of probabilities by mainstream media, prediction markets help build common knowledge." PMs win not on accuracy but on visibility.
+
+## Notable Quotes
+
+> "Probability signals embedded invisibly into every decision surface on the internet."
+> — *Aggie*
+
+> "Forecasting accuracy has outpaced product design."
+> — *Mohamed Elrashid*
+
+> "AI chatbots may supersede prediction markets as the primary forecasting interface, leaving markets to serve an epistemic role as common knowledge infrastructure."
+> — *Dan Schwarz*
+
+## Where It Matters
+
+"Probability infrastructure" is the most ambitious end-state framing for the PM industry. It says the product isn't the trading UI · it's the *probability output* surfaced inside other products (a price ticker, a CMS, an ad-buying tool, a content recommender). Three things follow: (1) PM platforms become picks-and-shovels for other apps, not destination apps themselves; (2) the moat shifts from UX to API + data + canonicalization; (3) accuracy stops being the only KPI · *adoption and embeddability* matter more. For Dekant, this framing supports a thesis where the distribution-market output (a full belief curve) is more *valuable to embed* than a single binary price, because downstream apps can integrate the whole shape.
+
+## Related Concepts
+
+- **Info finance** · Vitalik's umbrella term for the same idea
+- **Credibility markets** · one of three named probability layers
+- **Demand markets** · another named probability layer
+- **Attention markets** · third named probability layer
+- **Nowcasting** · embeds PM prices into econ data workflows
+- **Forecasting accuracy** · the input that probability infrastructure exposes
+- **Decision markets** · adjacent embedding (probabilities → governance choices)
+- **Platform competition** · moat shifts from app to API
+
+## Sources
+
+- [Good Forecasts, Bad Products](https://mo-el.xyz/blog/good-forecasts-bad-products/) — Mohamed Elrashid · Apr 24, 2026 [FULL_READ]
+- [Are Prediction Markets Good for Anything?](https://asteriskmag.com/issues/14/are-prediction-markets-good-for-anything) — Dan Schwarz · Apr 1, 2026 [FULL_READ]
+- [The Probability Layers Are Coming](https://x.com/BlondiePredicts/status/2038595927225622569) — Aggie · Mar 30, 2026 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/proper-scoring-rules.md b/.qoder/plans/pm-plan/theory_concepts/proper-scoring-rules.md
new file mode 100644
index 0000000000..ef1d00c9d5
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/proper-scoring-rules.md
@@ -0,0 +1,71 @@
+# Proper Scoring Rules
+
+**Category:** Mechanism Design
+**Source:** [PM Atlas — Proper Scoring Rules](https://www.pmatlas.xyz/concepts/proper-scoring-rules)
+
+---
+
+## Definition
+
+Incentive-compatible functions that reward forecasters most when they report their true beliefs honestly. The mathematical foundation underneath every market scoring rule, LMSR, and self-resolving market mechanism in the prediction-market literature.
+
+## Key Insights
+
+- A scoring rule `S(p, x)` is *proper* if a forecaster maximizing expected score is required to report her true subjective probability `p` · any deviation strictly reduces expected reward. This is the formal condition for incentive compatibility under truthful reporting.
+- The **logarithmic** scoring rule `S(p, x) = log p_x` is the canonical proper scoring rule for prediction markets. It is the *only* proper scoring rule that is both local and additive · the property that makes LMSR's cost function tractable.
+- Other classic proper scoring rules: quadratic (Brier), spherical, beta. Each makes different trade-offs between risk sensitivity, calibration weight, and tail behavior.
+- Chen & Pennock's Harvard survey (Jan 2025) is the canonical academic reference and frames scoring rules as "the simplest prediction mechanism ... a payment to a single expert in return for her information. The payment amount depends on the expert's prediction and the actual outcome in a way that motivates the expert to be honest." Their objectives hierarchy: expressiveness, liquidity, incentive compatibility, computational tractability, individual rationality.
+- The Srinivasan, Karger & Chen (2023) result extends proper scoring rules into the *unverifiable outcome* regime: rewards use the last reporter's prediction as a reference point with the *negative cross-entropy market scoring rule* between each agent's report and the final consensus `q(T)`. From the abstract: "Truthful reporting is a perfect Bayesian equilibrium ... Although primarily of interest for unverifiable outcomes, this design is also applicable for verifiable outcomes."
+- Monad's explainer of SKC names the mechanism as "delta-based scoring": "the payoff is not based on your final absolute closeness to the reference number, it's based on your marginal improvement toward it." Formally: each agent's payment is the cross-entropy delta `S(reportᵢ, q(T)) - S(reportᵢ₋₁, q(T))` · exactly the proper-scoring-rule market mechanism shifted from ground truth to consensus.
+- Powell, Hanson, Laskey & Twardy (2013) · the DAGGRE combinatorial-markets experiment · used LMSR (which inherits properness from the log scoring rule) to enable bets on conditional events ("A given B") and Boolean combinations. Result: greater expressivity from a proper-scoring-rule foundation improved both accuracy and calibration vs flat structures.
+- Baheet's game-theory thread: builders need economists and game-theory experts. LMSR works *because* it's a proper scoring rule; truth-telling is the dominant strategy. This is not a UX problem.
+- The Trepa paper (Blanco, Chung & Meka 2026) is a critique-and-extension of standard proper-scoring-rule *contests*. They prove formally that with γ=6 (steep accuracy weights) and a median-error cutoff, "the unique symmetric equilibrium satisfies w = 0 (pure herding) whenever the risk-aversion coefficient ρ exceeds a finite threshold." Their fix: an orthogonal precision multiplier `Uᵢ = A(eᵢ)·(1 + λDᵢ)` where `Dᵢ = |xᵢ - E[x̄_-i | Iᵢ]|` rewards forecasts that are both accurate *and* decorrelated from consensus.
+- The Trepa paper's contribution explicitly distinguishes between *first-order* properness (against ground truth, classical) and *second-order* properness (against the contest itself): "Section 11 compares Trepa's mechanism with proper scoring rules." Their construction is a tunable second-order oracle that elicits private information while retaining the rank-based incentive structure.
+- Trepa shows there's a phase transition: only when `λ > λc = κ/C` does an interior equilibrium with `w∗ > 0` emerge. Below the threshold, even a small orthogonality bonus is dominated by the herding pressure created by risk aversion + the median cutoff.
+- The Trepa mechanism is significant for prediction-market design because it shows the standard proper scoring rule is not enough to elicit private information in *contest* (vs. market) settings, and that second-order corrections (rewarding orthogonality, not just accuracy) are needed.
+- Proper scoring rules generalize naturally to continuous outcomes via the *continuous ranked probability score* (CRPS), which is what Dekant's continuous market settlement is structurally related to · though not explicitly cited in the on-page corpus.
+
+## Notable Quotes
+
+> "Truthful reporting is a perfect Bayesian equilibrium."
+> — *Srinivasan, Karger & Chen, *Self-Resolving Prediction Markets for Unverifiable Outcomes**
+
+> "Markets resolve using crowd consensus as the outcome, with delta-based scoring rewarding participants for moving markets toward final consensus."
+> — *michaellwy, *Explainer on Self-Resolving Prediction Markets**
+
+> "The simplest prediction mechanism is a scoring rule, or payment to a single expert in return for her information. The payment amount depends on the expert's prediction and the actual outcome in a way that motivates the expert to be honest."
+> — *Chen & Pennock, *Designing Markets for Prediction**
+
+> "For λ = 0 and ρ > 0, the unique symmetric equilibrium satisfies w = 0 (pure herding) whenever the risk-aversion coefficient ρ exceeds a finite threshold."
+> — *Blanco, Chung & Meka, *Orthogonal Precision in Trepa* (Theorem 3.1, KBC Equilibrium)*
+
+> "The orthogonal precision multiplier ... rewards accurate forecasts decorrelated from the consensus, transforming Trepa into a tunable second-order oracle."
+> — *Blanco, Chung & Meka, *ibid.**
+
+## Where It Matters
+
+Proper scoring rules are *the* mathematical primitive of mechanism design for prediction. Every market scoring rule (including LMSR), every self-resolving mechanism, and every forecasting-contest reward function is a proper scoring rule in some dress. The frontier work is in extending properness: into unverifiable outcomes (SKC), into contests where reporters can collude (Trepa orthogonal precision), and into continuous outcomes (CRPS, the unstated cousin behind Dekant's settlement math).
+
+## Related Concepts
+
+- LMSR · the cost-function dual of the log scoring rule
+- Market scoring rules · the class LMSR belongs to
+- Incentive compatibility · properness is the formal condition
+- Self-resolving markets · built on proper scoring rules without an oracle
+- Peer prediction · proper scoring rule for inter-respondent comparison
+- Keynesian Beauty Contest · the equilibrium that breaks naive proper-scoring contests
+- Information aggregation · proper scoring rules are the engine
+- Combinatorial prediction markets · exploit log-rule decomposability
+- Forecasting accuracy · what proper scoring rules optimize for
+- Oracle design · proper scoring rules underpin many oracle proposals
+- Reflexivity · properness can fail under reflexive settings
+
+## Sources
+
+- [Orthogonal Precision in Trepa: A Tunable Second-Order Oracle for High-Frequency Forecasting](https://github.com/TrepaOrg/trepa-research/blob/main/Trepa_Orthogonal_Precision_20260513_Blanco%26Chung%26Meka.pdf) — Ilich Blanco, Jong-Chan Chung, Leon Meka · GitHub
+- [Explainer on Self-Resolving Prediction Markets](https://blog.monad.xyz/blog/self-resolving-prediction-markets) — michaellwy · Monad Blog
+- [The Game Theory Behind Prediction Markets](https://x.com/Baheet_/status/1965758390430208066) — Baheet · X
+- [Designing Markets for Prediction](https://bpb-us-e1.wpmucdn.com/sites.harvard.edu/dist/b/845/files/2025/01/aim10.pdf) — Yiling Chen, David M. Pennock · Harvard
+- [Self-Resolving Prediction Markets for Unverifiable Outcomes](https://arxiv.org/abs/2306.04305) — Siddarth Srinivasan, Ezra Karger, Yiling Chen · arXiv
+- [Combinatorial Prediction Markets: An Experimental Study](https://link.springer.com/chapter/10.1007/978-3-642-40381-1_22) — Powell, Hanson, Laskey & Twardy · Springer
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/reflexivity.md b/.qoder/plans/pm-plan/theory_concepts/reflexivity.md
new file mode 100644
index 0000000000..f1f7cfd820
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/reflexivity.md
@@ -0,0 +1,79 @@
+# Reflexivity
+
+**Category:** Mechanism Design
+**Source:** [PM Atlas — Reflexivity](https://www.pmatlas.xyz/concepts/reflexivity)
+
+---
+
+## Definition
+
+When market prices influence the very outcomes they predict, creating feedback loops between beliefs and reality. Borrowed from Soros; for prediction markets it is the operational risk that the act of forecasting becomes the act of causing.
+
+## Key Insights
+
+- Reflexivity is contested as a real risk in prediction markets. Adhi Rajaprabhakaran (responding directly to Kyla Scanlon's NYT op-ed) argues prediction markets lack the *causal channel* that stock markets have. His Seahawks example: "Millions of dollars flow into a contract on whether the Seahawks will win on Sunday. The price rises from 45 cents to 52 cents. Does this make the Seahawks more likely to win? Of course not. The game will be determined by athletic performance, coaching decisions, and random bounces of an oblong ball. Russell Wilson doesn't throw tighter spirals due to a price on a prediction market."
+- Rajaprabhakaran's core distinction: stocks reward higher prices with cheaper capital, better hiring, equity-based M&A · "the whole point of the stock market is to reward companies and investors for being better businesses." Prediction markets have no such channel. The Liz Truss / UK gilts example "actually undermines" Scanlon's argument because "those higher yields made her borrowing plans literally unfinanceable" · a *real* causal channel that prediction markets don't have. Concedes the *narrower* claims: "Markets incorporate information faster than democratic institutions. Media outlets report prediction market odds without sufficient context. Both observations are true." Calls the remedy "better reporting, not different market structure."
+- Lebron (*Predicting Our Own Demise*) makes the *opposite* case for large-scale markets. His sunspot example is the clean baseline (no reflexivity), but he argues that once a prediction market on, say, "Will Trump run for reelection in 2028?" carries billions of dollars of volume, "it's now a vibe. Large amounts of societal resources are being deployed to argue for or against it. This feedback cycle directly increases the probability of the rare, changeable events through this deployment of resources." His structural argument: "By creating an economic incentive to manipulate probabilities, the vibes can actually become true if they're even remotely manipulable."
+- Lebron's salience asymmetry: "Salience, or vibes, of bad things have a much larger natural audience. The profitability of betting that a rare event will happen has a much higher upside. So prediction markets will naturally converge much more on 'will there be a civil war by 2030' types of questions than on 'will we discover anti-gravity by 2030' questions." Negative reflexivity dominates positive reflexivity.
+- Lebron also links reflexivity to *adverse selection*: the no-trade theorem means without hedgers, "a mature prediction market is full of people with the same risk preferences" · sharps and noise traders only · which is the demand floor for reflexivity to scale.
+- aaronjmars proposes a taxonomy of three manipulation vectors that reflexivity intersects with: (1) information asymmetry (insider knows outcome), (2) reflexivity (price signal influences outcome), (3) social coercion (participant directly causes outcome). The taxonomy frames manipulation as "what kind of edge are you trading on?"
+- HYPERSTITIONS reframes reflexivity as a *feature*, not a bug: hyperstition markets ask "what can we make happen?" rather than "what will happen?" Betting YES means coordinating action toward manifestation; the market discovers the price of coordination through dynamic subsidies. Positioned as futarchy with execution built in.
+- Seva Gunitsky's "Priced to Kill" extends reflexivity into geopolitics: conflict markets create propaganda advantages for autocratic regimes (who can manipulate outcomes and resolution) and effectively underwrite military insider trading at scale. Gunitsky's bettor-as-constituency formulation: "If you're long on 'Iran strikes Israel' then you want that strike to happen. Aggregated across thousands of bettors, this creates a constituency for escalation."
+- Gunitsky's "manipulation as propaganda" mechanism: "When odds become authoritative they also become worth manipulating ... The propaganda value is extracted immediately and the headline ('Polymarket odds of NATO intervention surge to 35%') is published the moment the price spikes. Market manipulation in this context doesn't have to be sustainable to be newsworthy."
+- Alexander Lin's 23 failure modes (Reforge) groups reflexivity with adverse selection, oracle governance, and capital-efficiency issues as structural reasons prediction markets cannot scale institutionally under current designs.
+- Trepa's orthogonal-precision mechanism (Blanco, Chung & Meka, May 2026) directly addresses a *forecaster-level* reflexivity. Their model: under λ=0 the unique symmetric equilibrium "satisfies w = 0 (pure herding) whenever the risk-aversion coefficient ρ exceeds a finite threshold" · agents ignore private signals and herd to the public consensus θ. Their fix introduces an orthogonal distance term `Di = |xi - E[x̄_-i | Ii]| = w|si - E[Y|si]|` that vanishes under pure herding and grows linearly with the weight on private signal.
+- Trepa's phase transition: "for λ ≤ λc, the unique equilibrium is w∗ = 0; for λ > λc, an interior equilibrium emerges with w∗ > 0 increasing in λ, bounded above by wmax = E[|si-θ|]/(E[|si-θ|]+noise) < 1." A minimal `λc = κ/C` is required to overcome the herding barrier created by risk aversion and the median cutoff.
+- Trepa's stability claim about herding equilibria: "Because ties lose, perfect herding is not an absorbing state. Any agent has an incentive to deviate infinitesimally to break the tie, creating a structural tension that reinforces the need for orthogonal precision as a stabilizing mechanism."
+- Trepa proves equilibrium existence via potential game theory: `Φ(x) = -Σᵢ ϕ(eᵢ) + λ Σᵢ ψ(|xᵢ-x̄_-i|) - (κ/2)Σᵢ(xᵢ-θ)²` is continuously differentiable and concave under appropriate parameter choice, with a compact strategy set · guaranteeing a pure Nash equilibrium.
+- Pure information markets (without action linkage) are less reflexive than decision markets or futarchy, where the *purpose* of the market is to alter behavior · the design intent itself is reflexivity.
+- Hyperstition is the design opposite of self-resolving markets: self-resolving uses crowd consensus *as ground truth*; hyperstition uses crowd consensus *to summon ground truth*.
+
+## Notable Quotes
+
+> "Unlike stock markets, prediction markets lack causal mechanisms through which odds could influence the events they forecast, making them thermometers rather than thermostats."
+> — *Adhi Rajaprabhakaran, *Prediction Markets Don't Bend Reality**
+
+> "Russell Wilson doesn't throw tighter spirals due to a price on a prediction market."
+> — *Rajaprabhakaran, *ibid.**
+
+> "By creating an economic incentive to manipulate probabilities, the vibes can actually become true if they're even remotely manipulable."
+> — *Agustin Lebron, *Predicting Our Own Demise**
+
+> "Once a prediction market becomes large enough, it transitions from being truth-seeking to being tautology-seeking."
+> — *Lebron, *ibid.**
+
+> "Where prediction markets ask 'what will happen?', hyperstition markets ask 'what can we make happen?'"
+> — *HYPERSTITIONS, *Stop Predicting. Start Manipulating.**
+
+> "If you're long on 'Iran strikes Israel' then you want that strike to happen. Aggregated across thousands of bettors, this creates a constituency for escalation."
+> — *Seva Gunitsky, *Priced to Kill**
+
+> "Trepa's current design (median error cutoff, steep accuracy weights) induces a Keynesian beauty contest equilibrium in which private information is underweighted."
+> — *Blanco, Chung & Meka, *Orthogonal Precision in Trepa**
+
+> "Because ties lose, perfect herding is not an absorbing state. Any agent has an incentive to deviate infinitesimally to break the tie, creating a structural tension that reinforces the need for orthogonal precision as a stabilizing mechanism."
+> — *Blanco, Chung & Meka, *ibid.**
+
+## Where It Matters
+
+Reflexivity is the question that determines whether prediction markets are infrastructure or instrument · whether they merely reveal the world or shape it. The empirical question (does the price actually move the outcome?) is unsettled · Rajaprabhakaran says no, Lebron says yes-once-volume-is-large, Trepa shows yes-at-the-forecaster-level. The design question (build reflexivity in as hyperstition? build it out as the SKC self-resolving primitive? engineer around it with orthogonal-precision scoring?) is where the next generation of mechanisms is being built. For Dekant, the continuous curve makes reflexivity *more* legible because a distorted curve is visible as a shape, but also expands the manipulation surface from a point to a distribution.
+
+## Related Concepts
+
+- Keynesian Beauty Contest · the forecaster-level analog of reflexivity (predicting others' predictions)
+- Hyperstition markets · reflexivity as feature
+- Self-resolving markets · reflexivity as structural problem to design away
+- Futarchy / decision markets · designs that intentionally close the reflexivity loop
+- Market manipulation · direct cousin (and often confused for) reflexivity
+- Proper scoring rules · the formal apparatus for designing truth-telling under reflexive pressure
+
+## Sources
+
+- [Orthogonal Precision in Trepa: A Tunable Second-Order Oracle for High-Frequency Forecasting](https://github.com/TrepaOrg/trepa-research/blob/main/Trepa_Orthogonal_Precision_20260513_Blanco%26Chung%26Meka.pdf) — Ilich Blanco, Jong-Chan Chung, Leon Meka · GitHub
+- [Priced to Kill](https://hegemon.substack.com/p/polymarket-is-making-wars-more-dangerous) — Seva Gunitsky · Hegemon Substack
+- [23 Reasons Prediction Markets Are Broken Today](https://x.com/linfluence/status/2026757563518431696) — Alexander Lin · X
+- [Prediction Markets Don't Bend Reality](https://fiftycentdollars.substack.com/p/prediction-markets-dont-bend-reality) — Adhi Rajaprabhakaran · Substack
+- [Stop Predicting. Start Manipulating.](https://x.com/hyperstiti0ns/article/1994422160790536299) — HYPERSTITIONS · X
+- [Risks of Prediction Markets](https://x.com/aaronjmars/article/1993094222195400863) — aaronjmars · X
+- [Predicting Our Own Demise](https://reducibleerrors.com/prediction-markets/) — Agustin Lebron · Reducible Errors
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/regulatory-arbitrage.md b/.qoder/plans/pm-plan/theory_concepts/regulatory-arbitrage.md
new file mode 100644
index 0000000000..519e0e6e3f
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/regulatory-arbitrage.md
@@ -0,0 +1,61 @@
+# Regulatory arbitrage
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — Regulatory arbitrage](https://www.pmatlas.xyz/concepts/regulatory-arbitrage)
+
+---
+
+## Definition
+
+**Quick definition.** Exploiting differences in PM regulation across jurisdictions to offer otherwise restricted products. The dominant US case: sports betting bans in 30+ states funnelling traders onto Kalshi/Polymarket as "event contracts."
+
+## Key Insights
+
+- ARK Invest frames sports PM volume as primarily regulatory arbitrage. States without legal online sports betting funnel that demand onto federally-regulated event-contract venues · the real disruption beyond this is unbundling risk from traditional derivatives for retail.
+- Information contagion (Sethi, sourced · full body unverified in fetch): argues quant funds can extract signals from pseudonymous Polymarket trades and act on them in KYC venues without breaking existing laws. Cross-platform information leakage is a structural arbitrage where the offshore/onshore split exists.
+- Brazil opportunity (Yuri Gubert): 5th-largest betting market ($4.1B GGR, 25M bettors) + 5th in crypto adoption ($6-8B monthly, 90% stablecoins). No domestic PM. CVM classification (derivative vs gambling) will determine institutional viability.
+- The Bloomberg framing (Beam): billions in volume have attracted enforcement from multiple states, insider trading concerns, and media partnerships with CNN/CNBC/sports leagues. The unresolved question is whether platforms are legitimate financial infrastructure or disguised betting operations.
+- Mitts and Ofir (Columbia/Haifa, Harvard CGI) document the Feb 28 2026 US-Israeli Iran strike case: six newly created Polymarket wallets earned ~$1.2M buying 'Yes' shares at prices as low as $0.10; one account ("Magamyman") placed its first trade 71 minutes before the news broke when the market implied just a 17% probability. They argue pseudonymous wallets transacting on material non-public information are an arbitrage current insider-trading law cannot reach, and propose platform-level registration, contract-level restrictions, and an extended misappropriation doctrine.
+- Hariharan: the swap-vs-gambling fight is itself a regulatory arbitrage question · if event contracts are swaps, the CFTC preempts state law and operators escape the 50-state licensing trap.
+- Cassar (Oxford): European Member States classify PM contracts inconsistently (gambling vs MiFID II derivative vs other). Proposes a Malta-style "Prediction Test" using exclusion to categorize, removing the arbitrage opportunity by harmonizing.
+- Schwartz (Morgan Lewis): under the CEA, "industry consensus is what matters" · since DCMs, FCMs, brokers, and DCOs all treat sports event contracts as swaps, CFTC has exclusive jurisdiction and state gaming laws are preempted.
+- Jake Nyquist's 7 axes include "regulatory compliance" as a competitive lever · challenger venues differentiate by being more or less regulated than incumbents, depending on which market they target.
+- njokuScript (J11): PMs are crypto's first truly native financial primitive precisely because they couldn't scale on TradFi rails due to regulatory chokepoints. The "gambling -> infrastructure" pattern is recurring (LIBOR, futures, options all went through it).
+- Kaviish: distribution platforms (Robinhood, Coinbase) will capture most value as they vertically integrate into exchange infrastructure · partly because they're already regulated and don't need fresh arbitrage to plug in.
+- McCluskey (vintage 2006 piece): proposes PMs aimed at informing voters operate as nonprofits, because valuable political info rarely correlates with profitable trading and charitable structures face less scrutiny. An older flavor of regulatory arbitrage proposal.
+
+## Notable Quotes
+
+> "Prediction markets will not disrupt sports betting … their real potential will come from their instantiation as the future of financial infrastructure."
+> — *ARK Invest, *Prediction Markets: The Potential Multi-Trillion Dollar Asset Class**
+
+> "Six newly created Polymarket wallets collectively earned approximately $1.2 million by purchasing 'Yes' shares … when markets implied only a 17% probability of a strike."
+> — *Joshua Mitts and Moran Ofir, *From Iran to Taylor Swift: Informed Trading in Prediction Markets**
+
+## Where It Matters
+
+Regulatory arbitrage is the trade keeping today's PM volumes alive (sports flow from non-betting states) and the moat for the next wave of platforms (offshore crypto rails as a hedge against US enforcement). For Dekant: Solana / devnet posture is itself a regulatory arbitrage stance · building outside the US compliance perimeter while keeping the option to onshore if classification clarifies favorably.
+
+## Related Concepts
+
+- Federal preemption · the legal mechanism that closes intra-US arbitrage
+- Regulatory classification · the category determines the regime
+- Event contracts · the asset under regulatory dispute
+- Market surveillance · pseudonymous wallets enable cross-venue arbitrage
+- Information contagion / insider trading · pseudonymous PM signals leak to regulated venues
+
+## Sources
+
+- [Prediction Markets: The Potential Multi-Trillion Dollar Asset Class Hiding In Plain Sight](https://www.ark-invest.com/articles/analyst-research/prediction-markets-potential-multi-trillion-dollar-asset-class) — Nicholas Grous, Varshika Prasanna, Raye Hadi · Apr 22 2026 ·
+- [How Prediction Markets Are Blurring the Line Between Trading and Betting](https://www.bloomberg.com/news/articles/2026-04-09/how-prediction-markets-are-blurring-the-line-between-trading-and-betting) — Christopher Beam · Apr 9 2026 ·
+- [Brazil: The Next Frontier for Prediction Markets](https://x.com/Yuri_Gubert/status/2041513296608571791) — Yuri Gubert · Apr 7 2026 ·
+- [The Financialization of Uncertainty](https://x.com/kaviish/status/2041214800731033700) — Kaviish · Apr 6 2026 ·
+- [States vs. Prediction Markets: The Fight Over the Meaning of 'Swap'](https://www.dopaminemarkets.com/p/prediction-markets-vs-states-the) — Shreyas Hariharan · Apr 6 2026 ·
+- [Information Contagion](https://rajivsethi.substack.com/p/information-contagion) — Rajiv Sethi · Mar 31 2026 ·
+- [Regulating Prediction Markets in Europe Requires a 'Prediction Test'](https://blogs.law.ox.ac.uk/oblb/blog-post/2026/03/regulating-prediction-markets-europe-requires-prediction-test) — Terence Cassar · Mar 31 2026 ·
+- [From Iran to Taylor Swift: Informed Trading in Prediction Markets](https://corpgov.law.harvard.edu/2026/03/25/from-iran-to-taylor-swift-informed-trading-in-prediction-markets/) — Joshua Mitts, Moran Ofir · Mar 25 2026 ·
+- [Federal Preemption in Sports Prediction Market Litigation: This Shouldn't Be a Jump Ball](https://www.morganlewis.com/-/media/files/publication/outside-publication/article/2026/federal-preemption-in-sports-prediction-market-litigation-this-shouldnt-be-a-jump-ball-futures-and-derivatives-law-report.pdf) — Rob Schwartz · Mar 1 2026 ·
+- [The 7 Axes of Prediction Markets](https://x.com/Jake_Nyquist/article/2021222519991124343) — Jake Nyquist · Feb 10 2026 ·
+- [Prediction Markets: A New Kind of Asset Class](https://x.com/njokuScript/article/2010080717729046613) — njokuScript · Jan 11 2026 ·
+- [Should Prediction Markets Be Charities?](https://www.overcomingbias.com/p/should_predictihtml) — Peter McCluskey · Dec 11 2006 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/regulatory-classification.md b/.qoder/plans/pm-plan/theory_concepts/regulatory-classification.md
new file mode 100644
index 0000000000..80845aa2c0
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/regulatory-classification.md
@@ -0,0 +1,64 @@
+# Regulatory classification
+
+**Category:** Business & Platforms
+**Source:** [PM Atlas — Regulatory classification](https://www.pmatlas.xyz/concepts/regulatory-classification)
+
+---
+
+## Definition
+
+**Quick definition.** The legal categorization of PM contracts as gambling, financial derivatives, or a distinct product class. The category determines which regulator applies and what compliance falls on operators.
+
+## Key Insights
+
+- The dominant axis is gambling vs derivative. Hariharan argues: if event contracts are "swaps" under the Commodity Exchange Act, CFTC's exclusive jurisdiction preempts state gambling law and platforms can operate in all 50 states; if not, they collapse into state-by-state gambling licensing (commercially fatal for venture-backed PMs).
+- Park's timeline: Iowa Electronic Markets' 1988 no-action letter (academic carve-out) -> Intrade's 2012 collapse (CFTC enforcement) -> binary options fraud era (2014-2018) -> Kalshi's court win establishing "gaming" does not cover financial contracts on uncertain outcomes -> Bitwise PredictionShares ETF launch as the mainstream tipping point.
+- The Feb 2026 jurisdictional standoff: CFTC Chairman Mike Selig vs Utah Governor Cox over whether the state can ban Kalshi's contracts. Park argues federal preemption under the CEA will hold against state AGs.
+- DWF Ventures: PMs are evolving into a financial-derivatives asset class. Structural barriers (jump risk, binary valuation, regulatory uncertainty) drive new architectures · epoch-based fee models, perpetual futures on outcomes, tokenized positions on Solana.
+- Michael Li's CFTC comment proposes a four-dimensional classification framework: information structure, manipulation economics, social utility, repugnance. Lumping all event contracts under one regime is either too restrictive (suppressing election/macro forecasting) or too permissive (perverse contracts with no countervailing benefit).
+- Robin Hanson: PMs deserve the same regulatory treatment as journalism and academia · both are information institutions facing the same six common harms (insider trading, manipulation, etc). Should be approved by default, restricted only on clear evidence of specific harm. The information value justifies a lighter touch than gambling law.
+- Noah Litvin: the legal line between gambling and investing collapses under scrutiny. Standard objections (manipulation, slot-machine durations, accredited-investor rules) all have larger-scale analogs in TradFi (LIBOR, the $950M oil ceasefire trades on CME, the dollar debasement). PMs are simply a more legible version of dynamics already accepted everywhere else.
+- Cassar (Oxford): EU has a parallel classification crisis. Contracts could be gambling, MiFID II derivatives, or "something else entirely," and different Member States treat them differently. Proposes a Malta-style "Prediction Test" that systematically categorizes through exclusion.
+- Blocmates' stack thesis (per their *State of Prediction Markets* report): crypto rails like Polymarket optimize for breadth and speed-to-list; regulated rails like Kalshi optimize for settlement credibility and distribution compatibility. Classification choice maps directly to product strategy.
+- Dune's anatomy of Polymarket's fastest markets: with $23.7M in taker fees collected in 83 days through a maker-rebate model, Polymarket has structurally converged with a derivatives exchange · strengthening the "this is a swap, not gambling" classification argument.
+- mph: regulatory clarity matters less than narrative framing. Builders should embrace the trading-vs-gambling distinction while acknowledging insider trading is both a problem and a signal source.
+- Gouker's DFS parallel: state regulatory pushback, industry self-regulation attempts, and casino opposition all rhyme with DFS 2013-2015. Classification fights followed the same arc · eventual federal/state compromise.
+
+## Notable Quotes
+
+> "Prediction markets deserve the same regulatory treatment as other information institutions like journalism and academia."
+> — *paraphrased from Robin Hanson, *On Prediction Market Regulation**
+
+> "The legal line between gambling and investing collapses under scrutiny."
+> — *paraphrased from Noah Litvin, *You Don't Hate Prediction Markets. You Hate Capitalism.**
+
+> "A regulatory approach that treats all event contracts identically risks being either too restrictive or too permissive."
+> — *Michael Li, *Why Prediction Markets Are Hard to Regulate**
+
+## Where It Matters
+
+Classification is the categorical decision that sets the regulator, the cost structure, the available product surface (margin, options, hedging), and ultimately the addressable market. For Dekant: a continuous-payout market structured as a basket of swap-classified contracts has a cleaner regulatory story than one indistinguishable from a binary lottery ticket.
+
+## Related Concepts
+
+- Event contracts · the unit under classification
+- Federal preemption · what happens once classification settles
+- Regulatory arbitrage · the gap between classification regimes is the trade
+- Platform competition · venues stake out classification-driven lanes
+- Market structure · rails vs wrappers map onto classification choices
+
+## Sources
+
+- [Prediction Markets As Follow-Up To DFS: The Parallels Are Strikingly Similar](https://www.ingame.com/prediction-markets-daily-fantasy-sports-parallels/) — Dustin Gouker · May 14 2026 ·
+- [A Second Identity: Prediction Markets as Financial Derivatives](https://x.com/i/article/2049414664946323456) — DWF Ventures · Apr 30 2026 ·
+- [Why Prediction Markets Are Hard to Regulate](https://michaellwy.substack.com/p/my-comment-to-cftcs-prediction-markets) — Michael Li · Apr 30 2026 ·
+- [On Prediction Market Regulation](https://www.overcomingbias.com/p/on-prediction-market-regulation) — Robin Hanson · Apr 29 2026 ·
+- [You Don't Hate Prediction Markets. You Hate Capitalism.](https://x.com/noahlitvin/status/2048527516587966660) — Noah Litvin · Apr 27 2026 ·
+- [Prediction Markets In Q1 2026: 11 Insights Worth Knowing](https://x.com/Ommiii_/status/2047582772387291361) — Omar · Apr 24 2026 ·
+- [Prediction Markets: They Grow Up So Fast](https://open.substack.com/pub/a16z/p/prediction-markets-they-grow-up-so) — Alex Immerman, Santiago Rodriguez · Apr 16 2026 ·
+- [Faster, Shorter, More Automated: Anatomy of Polymarket's Fastest Markets](https://x.com/Dune/status/2044045952730726863) — Dune · Apr 14 2026 ·
+- [Are We on the Brink of a Prediction Market Supercycle?](https://x.com/mphrediction/status/2043419029378048164) — mph · Apr 12 2026 ·
+- [The State of Prediction Markets](https://stateofpredictionmarkets.blocmates.com/) — blocmates · Apr 1 2026 ·
+- [Regulating Prediction Markets in Europe Requires a 'Prediction Test'](https://blogs.law.ox.ac.uk/oblb/blog-post/2026/03/regulating-prediction-markets-europe-requires-prediction-test) — Terence Cassar · Mar 31 2026 ·
+- [The Truth Machine Era Is Here](https://x.com/dgt10011/status/2024228182178595247) — Jeff Park · Feb 19 2026 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/relative-value-trading.md b/.qoder/plans/pm-plan/theory_concepts/relative-value-trading.md
new file mode 100644
index 0000000000..1e37c82a19
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/relative-value-trading.md
@@ -0,0 +1,60 @@
+# Relative Value Trading
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Relative Value Trading](https://www.pmatlas.xyz/concepts/relative-value-trading)
+
+---
+
+## Definition
+
+**Quick definition.** A strategy that bets on the *relationship* between two correlated prediction-market prices rather than the direction of either one. Profits when a mispriced spread converges · equivalent to equity pair-trading or fixed-income spread trading, transposed into discrete-event space.
+
+## Key Insights
+
+- Jon Turek frames RV as a direct lift from macro trading: "relative value trading is a very big macro trading strategy that plays on the idea that the spread or correlation between two securities has some sort of fair value". Simply put: "betting on the relationship between two prices rather than the direction of either one individually".
+- The reformulation Turek offers: "Instead of saying 'I think X will go up,' you're saying 'I think X is mispriced relative to Y, and that gap will close.'"
+- Turek's leverage framing: RV is also "a way to add leverage to an existing view". If you think outcome X is interesting and you believe outcome Y is highly correlated to X, you can structure correlated bets that "juice your returns relative to the payout of just betting on your underlying view".
+- Canonical example with full data: **30–35% Polymarket recession odds** (matches Goldman; baseline economist estimate is 15–20%); **65% chance unemployment rises to 5%, 35% chance to 5.5%**; yet Fed-cut market prices only **20% chance of more than two cuts**. The six historical episodes of unemployment rising +1.2% in a year (1974–75, 1980–82, 1990–91, 2001, 2007–09, 2020) averaged **seven Fed cuts each**.
+- Concrete trade construction: long "YES" on Fed rate cuts + short "NO" on unemployment outcomes. "Could offer attractive payouts in either macroeconomic outcomes" because the legs hedge each other where economic logic says they should be correlated.
+- Macro context Turek frames the trade with: ~92k jobs shed last month, ~6 months of weak payroll growth, falling JOLTs job openings, and energy shock from war in Iran · all factors that should push the unemployment-to-Fed-cuts correlation up, not down.
+- Turek closes with the recurring meta-observation: "we have a lot of listed markets that are being priced idiosyncratically but many of them have an implied correlation to them. I think this is creating a lot of really compelling relative value trades."
+- Unlike directional bets, RV positions are partially insulated from the binary-resolution gap risk that wrecks ordinary leveraged PM strategies: if both legs move with the spread, the directional risk cancels.
+- The strategy benefits disproportionately from the structural fact that PM contracts are **priced idiosyncratically** with no built-in cross-market arbitrageur.
+- Closely related to:
+- **Implied correlation** (the underlying mispricing)
+- **Covariance markets** (the proposed venue to express RV cleanly)
+- **Cross-platform arbitrage** (an RV trade across venues rather than across events)
+- **Conditional probability arbitrage** (st1ne's MEV-style edge: trading correlated PMs as a portfolio)
+- The natural participants are macro-aware hedge funds and AI agents capable of scanning thousands of event pairs; retail is structurally locked out by the analytical lift.
+
+## Notable Quotes
+
+> "Relative value trading is betting on the relationship between two prices rather than the direction of either one individually. Instead of saying 'I think X will go up,' you're saying 'I think X is mispriced relative to Y, and that gap will close.'"
+> — *Jon Turek*
+
+> "Correlation can create leverage or arbitrage, and I think both of these things are relevant in the space of prediction markets right now."
+> — *Jon Turek*
+
+> "I think this presents a really attractive relative value trade between 'NO' on unemployment rate outcomes and 'YES' on Fed rate cuts."
+> — *Jon Turek*
+
+> "Cross-market mispricings like this are common and create attractive relative value trades."
+> — *Jon Turek*
+
+## Where It Matters
+
+Relative-value trading is the leverage-tolerant strategy class for prediction markets · and possibly the largest untapped category for institutional capital. Because RV trades hedge most of the directional binary risk, they can carry size that pure directional positions can't, and they exploit a structural inefficiency (idiosyncratic pricing) that incumbents are not solving. The bottleneck is tooling: explicit covariance markets, cross-market scanners, and capital-efficient margining of paired positions.
+
+## Related Concepts
+
+- **Implied correlation** · the upstream concept.
+- **Covariance markets** · the cleanest venue.
+- **Arbitrage** · parent category.
+- **AI agents** · scale enabler.
+- **Position collateralization** · improves RV capital efficiency.
+- **Gap risk** · mostly hedged out by paired positions.
+
+## Sources
+
+- [Prediction Markets and Implied Correlation](https://cheapconvexity.substack.com/p/prediction-markets-and-implied-correlation) — Jon Turek · Mar 24, 2026
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/resolution-criteria.md b/.qoder/plans/pm-plan/theory_concepts/resolution-criteria.md
new file mode 100644
index 0000000000..5fbb47aee8
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/resolution-criteria.md
@@ -0,0 +1,82 @@
+# Resolution Criteria
+
+**Category:** Oracle & Resolution
+**Source:** [PM Atlas — Resolution Criteria](https://www.pmatlas.xyz/concepts/resolution-criteria)
+
+---
+
+## Definition
+
+**Quick definition.** Resolution criteria are the *pre-defined, natural-language rules* specifying exactly what counts as YES, NO, or VOID for a given prediction market. They are the contract · every other layer (oracle, dispute system, AI judge) is just enforcing this text. Aradtski's claim that "the Oracle Problem isn't the problem · definition is" is the central thesis of this concept: most cataloged failures trace to the criteria, not the resolver.
+
+## Key Insights
+
+- **"The Oracle Problem is actually a definition problem."** Aradtski catalogued 12+ Polymarket controversies and argued each one was about ambiguous event wording, not oracle untrustworthiness. The oracle did exactly what the rules said; the rules were the bug.
+- **Bet on the rules, not on reality.** OddChain (the most-cited resolution-criteria essay) frames the trader's job: *"You're not necessarily taking a position based on the outcome of the question; you're taking a position based on the rules of resolution for that market."* The Ginsburg-on-hot-dogs analogy applies · give me the definition first, then I'll tell you the answer.
+- **Vague terms are the dominant failure pattern.** XO Market's 14-resolution-failure catalog groups them into four buckets · and **vague criteria** is the top category by dollar volume. Specifically:
+- "Suit" (Zelensky $240M)
+- "Invasion" (Venezuela $10M+)
+- "Ban" (TikTok $120M)
+- "Performed" (Cardi B halftime $47M)
+- "Word" (Kalshi mentions markets)
+- **Even legal and academic sources don't agree on these terms.** OddChain's invasion case study: UN Resolution 3314, Rome Statute, Geneva Conventions, US Constitution, and Black's Law Dictionary all use the word "invasion" but none defines it precisely. The market wrote an underdefined word into a $10M contract and was surprised to have a $10M dispute.
+- **The "consensus of credible sources" clause is the most-abused resolution criterion.** It pushes the definitional fight from the event itself onto *which sources count* and *what counts as consensus*. Polymarket's Zelensky-suit market had 40+ outlets calling it a suit; UMA still found no "consensus of credible reporting." OddChain on Venezuela: US wire services said "raid," foreign governments said "invasion," and the discrepancy *itself* was the resolution.
+- **Polymarket's mid-game "clarifications" are functionally rule changes.** OddChain on Venezuela: a clarification was issued Jan 4 · after the Jan 3 raid and after Trump's "we will run the country" statement · narrowing what counted. From a trader's view, this is changing the rules after positions are locked. Hall (a16z) calls this "violating the basic compact between platform and participant."
+- **Resolution-source pinning is its own attack surface.** When the criterion says "resolves based on X website" or "X media outlet," the *X* becomes the attack target. The OPM-website shutdown failure is the cleanest case: the website was a day late and the market resolved against reality. The Polymarket Paris-temperature/hair-dryer (Gunitsky) is the same pattern with a different sensor.
+- **The "hair dryer problem" is the criteria-as-attack-surface principle in extremis.** Gunitsky: when resolution criteria point at an unguarded thermometer, $20,000 in winnings goes to the guy with a hair dryer. The fix isn't a better oracle · it's better criteria (multi-source resolution, integrity checks, statistical outlier filters).
+- **Criteria can also encode moral hazard.** michaellwy CFTC comment: a market on whether a CEO says a specific phrase on an earnings call gives the CEO an incentive to say (or not say) the phrase. Brian Armstrong literally admitted this on a Coinbase earnings call in October 2025. The market's *existence* changed the speaker's behavior · and the criteria made it economical to do so.
+- **Markets on individual humans implicitly encode assassination risk** (Guillory & Zimmermann). Any market with a "void if subject dies" rule incentivizes losing bettors to harm the subject; any market *without* a void-on-death rule prices in the assassination probability. There's no neutral wording · the criteria themselves are politically loaded.
+- **Resolution-source biased oracles** (Lou Kerner). The 2024 Polymarket presidential market named AP, Fox News, and NBC as resolution sources. Kerner argues choosing Fox News was structurally biased · Fox would never be the first to call against Trump · meaning a Trump-loss outcome could *never* resolve cleanly through the criteria as written. The "neutrality" of the criteria masks the encoded asymmetry.
+- **michaellwy's "1c trick" exposes a notional-volume criterion problem.** Resolution criteria are tied to "market volume" for some Polymarket features; that volume can be inflated by trading low-probability shares at 1¢. takers account for ~41-47% of YES volume *within the 1-10¢ longshot bucket* (Becker) - not 41% of lifetime volume. The criterion was honest; the input was gameable.
+- **The Time "Person of the Year · Other" failure illustrates open-set criteria.** Tim.0x: "Person of the Year 2025" was given to "Architects of AI." Polymarket had no "Architects of AI" outcome listed, so the market resolved to "Other." Traders who correctly predicted AI-related winners lost on a criterion technicality. Synthetic data / generative outcomes are being proposed to handle this class of failures.
+- **Defensive criteria-reading is now a trader strategy.** OddChain's playbook: (1) Read the criteria, not the title. (2) Identify the hinge words. (3) Understand what "consensus of credible sources" means in this jurisdiction. (4) Avoid markets with ambiguity. (5) Model the resolver, not reality.
+- **The "regulated event contract" pathway forces precision.** Kalshi's CFTC-regulated contracts have to define everything precisely up-front, and the CFTC reviews them. michaellwy CFTC comment: this is the strongest argument for federal preemption · at least the rules can't be changed mid-game on regulated venues.
+- **Insurance use cases require parametric criteria, not narrative ones.** Eigenmann HIP-4: when an outcome contract acts as parametric cover for a DeFi exploit (like the $292M Kelp DAO rsETH bridge case), the criteria *must* be machine-readable triggers (on-chain TVL drops, oracle-fed thresholds), not "consensus of credible reporting." The narrative-resolution mode doesn't scale to parametric insurance.
+- **The criteria-precision tradeoff with social engagement.** Tighter criteria = fewer disputes but also fewer surprising/popular markets. The most viral Polymarket markets (Zelensky suit, Venezuela invasion) are *precisely* the loosely-worded ones. The platform faces a structural choice: precision-or-engagement.
+
+## Notable Quotes
+
+> "A tricky aspect of prediction markets is that you're not necessarily taking a position based on the outcome of the question; you're taking a position based on the rules of resolution for that market."
+> — *OddChain*
+
+> "If you tell me what an invasion is, I'll tell you if what happened in Venezuela was one."
+> — *OddChain (paraphrasing the Ginsburg "you tell me what a sandwich is" framing)*
+
+> "When resolution criteria are clear, prediction markets can surface the truth. But when the contract relies on vague or contested terminology, we're no longer predicting reality, we're predicting whether reality aligns with someone else's definition."
+> — *OddChain*
+
+> "Read the resolution criteria, not the title… Model the resolver, not reality. The question isn't 'What do I think happened?' but 'What would people applying these specific rules to these specific sources conclude?'"
+> — *OddChain*
+
+> "[The Oracle Problem] is actually a definition problem, not a trustlessness problem."
+> — *Aradtski*
+
+## Where It Matters
+
+Resolution criteria are the layer where the prediction market's truth-claim either holds or collapses. Sloppy criteria attract volume (the ambiguity is fun) and produce disputes (the ambiguity is exploitable). Every serious oracle-design proposal ultimately routes back to "write better criteria" · AI judges can interpret consistently, layered oracles can route by criteria type, parametric markets eliminate criteria interpretation entirely. The platforms that scale will be the ones that solve criteria precision without killing market diversity.
+
+## Related Concepts
+
+- **oracle design** · the layer that *enforces* the criteria.
+- **dispute resolution** · the layer that *adjudicates ambiguous* criteria.
+- **event contracts** · the legal/regulatory wrapper around the criteria.
+- **UMA protocol** · the resolver that has stressed criteria precision most.
+- **self-resolving markets** · designs that route around criteria by using crowd consensus.
+- **AI agents** · proposed criteria-interpretation substrate.
+
+## Sources
+
+- [Resolving Prediction Markets: An AI-Driven Layered Approach](https://x.com/xolabs_/status/2055266974259961917) — XO Labs · May 15 2026 ·
+- [Outcome Markets as a Cover Venue: HIP-4 and Its Traditional Comparables](https://x.com/DeanEigenmann/status/2052734335971713347) — Dean Eigenmann · May 8 2026 ·
+- [Why Prediction Markets Are Hard to Regulate](https://michaellwy.substack.com/p/my-comment-to-cftcs-prediction-markets) — Michael Li (michaellwy) · Apr 30 2026 ·
+- [The Hidden Risk Of Prediction Markets: 14 Resolution Failures That Cost $500M](https://x.com/xomarket/status/2046924302952640934) — XO Market · Apr 22 2026 ·
+- [Every Opinion Deserves A Market](https://x.com/turfsports_/status/2047030930699854334) — Turf · Apr 22 2026 ·
+- [Truth Machines Go to War](https://www.bloomberg.com/opinion/newsletters/2026-04-06/truth-machines-go-to-war) — Matt Levine · Apr 6 2026 ·
+- [The Economy of Truth: Inside the Decentralized Courtroom of Polymarket & UMA](https://x.com/smaaaaliy/article/2033242239498076190) — Smaliy · Mar 16 2026 ·
+- [All Types of Arbitrage on Prediction Markets](https://x.com/Nekt_0/article/2019107985079816395) — Nekt0 · Feb 5 2026 ·
+- [What to Do When Prediction Markets Fail](https://a16zcrypto.substack.com/p/how-ai-judges-can-scale-prediction) — Andy Hall · Jan 24 2026 ·
+- [Semantics for $10 Million: When Is an Invasion an Invasion?](https://www.oddchain.com/p/semantics-for-10-million-when-is-an-invasion-an-invasion-polymarket) — OddChain · Jan 23 2026 ·
+- [The Measles Market on Kalshi Is One of the Dumbest and Most Tragic Markets We've Seen This Year](https://www.oddchain.com/p/the-measles-market-on-kalshi-for-2025-is-one-of-the-dumbest-and-most-tragic-markets-we-ve-seen-this) — OddChain · Dec 30 2025 ·
+- [A Technical Typology of Prediction Markets: Mechanics and Tradeoffs](https://x.com/Baheet_/status/1967186769356488828) — Baheet · Sep 14 2025 ·
+- [Prediction Markets: The 'Oracle Problem' Is Not the Problem](https://x.com/aradtski/status/1949977296523145695) — Aradtski · Jul 29 2025 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/retail-flow.md b/.qoder/plans/pm-plan/theory_concepts/retail-flow.md
new file mode 100644
index 0000000000..003ebce196
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/retail-flow.md
@@ -0,0 +1,86 @@
+# Retail Flow
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Retail Flow](https://www.pmatlas.xyz/concepts/retail-flow)
+
+---
+
+## Definition
+
+**Quick definition.** Trading activity from non-professional participants · generally less informed, more predictable, and the desired counterparty for market makers. Retail flow is the economic engine that makes the rest of prediction-market microstructure work; without it, MMs can't profitably absorb adverse selection from informed traders.
+
+## Key Insights
+
+- Kunal Doshi on Polymarket fast crypto: **19 algo addresses extract consistent profits**; **69% of retail traders lose money**. Fast markets account for 16% of volume but ~40% of fees.
+- Isaac Rose-Berman ("Kalshi's Favorite Lie"): the exchange model still depends on retail losing. Every Kalshi trade has a maker (offers price) and a taker (accepts price); Kalshi takes a fee from both sides. Because fee = 0.07 × C × P × (1-P) peaks at 50/50, and retail almost always crosses the spread as the taker, a fair coin-flip bet becomes a **3.4% expected loss after fees** to the retail taker. Maker dominance is "structural, not stylistic" · only the fastest systems and best models can profitably make.
+- Rose-Berman: Kalshi's pitch ("we don't care who wins") is true but misleading · Kalshi profits from trading volume, and trading volume requires retail to keep losing slow and coming back. The marketing claim that PMs are fundamentally different from sportsbooks is "a much bigger fiction." Sportsbook = casino; Kalshi = casino-funder model.
+- Momin: Polymarket data shows **70% of 1.7M addresses lost money**; the **top 0.04% captured >70% of $3.7B realized profits**. Platforms predictably funnel retail into informed counterparties, including platform-operated MM desks at Kalshi and Crypto.com.
+- Jordan Bender (Wall Street equity research): median PM user ROI is **-8%**, worse than sports bettors (**-5%**); only **>$500K volume traders** hit positive returns (+2.6%). PMs attract sharper competition than regulated sportsbooks, creating *worse* outcomes for casual retail.
+- functionSPACE: noisy traders fund the probability space · they are not exit liquidity but the necessary fuel for MM economics.
+- Becker (72.1M Kalshi trades, $18.26B volume): systematic taker→maker wealth transfer of **1.12% excess returns on each side**; takers disproportionately buy YES longshots, accepting -64 pp worse returns. The transfer only emerged after October 2024 when algorithmic MMs arrived.
+- Becker on category-level retail vulnerability: Finance gap 0.17pp (efficient · "filters out emotional bettors"); Sports 2.23pp; Crypto 2.69pp; Weather 2.57pp; Entertainment 4.79pp; Media 7.28pp; World Events 7.32pp. The retail loss rate scales directly with the category's emotional engagement.
+- Becker on the YES/NO asymmetry: at 1-cent prices, YES contracts have a -41% expected return while NO contracts have +23% · a **64-percentage-point gap** at "identical" prices. Takers account for 41-47% of YES volume at 1-10¢ but only 23% of NO volume at 99¢. Retail is paying for the framing.
+- Della Vedova (222M Polymarket trades): **forecasting accuracy does not predict profitability**. Even retail who pick the right side lose because they arrive late at unfavorable prices. Automated traders profit by paying **2.52¢ less per contract** despite near-random directional skill.
+- functionSPACE on yes-bias: traders buy whichever token is cheaper, not whichever is labeled YES · the apparent yes bias is a *compound effect of longshot preference channeled through Polymarket's "Will X happen?" framing*.
+- sealaunch: **2% of users generate ~90% of platform volume**. Crypto markets dominated by algo execution; politics markets by event-driven casual participants. Different user-growth vs. volume-growth product strategies.
+- 4casters/sportsbook square-vs-sharp: Kalshi (3.5% fee) and Polymarket (1.5%+) monetize price-insensitive retail takers. Raising fees from 0.5% → 0.75% on 4casters had no volume impact · sports bettors are price-insensitive.
+- Hariharan Dopamine 2025 letter: retail financial speculation has permanently shifted from investment to entertainment; **0DTE options = 59% of options volume in 2025**. Retail participation went from a trivial share to **100M+ monthly active traders** over the past decade.
+- Hariharan on the cultural shift: "the smartphone has put a casino in every pocket" · the path is "to a billion active traders." Investing has converged with entertainment; the SEC has been replaced by a technology-forward permissive regime; BREAKING news now comes with live odds.
+- Ranger Global biases: longshot (5¢ contracts win only 4.18% of the time), maker-taker asymmetry (makers outperform at 80 of 99 price levels), YES/NO asymmetry (YES buyers -1.02% vs. NO buyers +0.83%).
+- Whitaker & Mazlish on retail's structural fit: gamblers want immediate resolution (**42% of 2020 election volume traded in the final week**); long-term investors prefer compounding; MMs need consistent retail flow that only materializes for elections/sports. Outside those niches, retail demand collapses and the markets stay illiquid.
+
+## Notable Quotes
+
+> "70% of 1.7 million addresses lost money on Polymarket; the top 0.04% captured over 70% of the $3.7B in realized profits."
+> — *Momin*
+
+> "Forecasting accuracy does not predict profitability."
+> — *Della Vedova*
+
+> "Whales are not the sharpest participants."
+> — *Deleep et al.*
+
+> "Retail financial speculation has permanently shifted from investment to entertainment."
+> — *Hariharan*
+
+> "Kalshi uses those truths to sell a much bigger fiction: that because it profits from trading volume rather than directly from user losses, its business does not depend on users losing money."
+> — *Rose-Berman*
+
+> "Takers exhibit negative excess returns at 80 of 99 price levels. Makers exhibit positive excess returns at the same 80 levels."
+> — *Becker*
+
+> "The smartphone has put a casino in every pocket, and people can't help but spin the wheel."
+> — *Hariharan*
+
+## Where It Matters
+
+Retail flow is the precondition for everything else · without it, there's no spread to harvest and no liquidity to lean against. But every microstructure study agrees the modal retail trader loses, often more than they would at a regulated sportsbook, because PMs attract sharper sophisticated competition. This makes consumer-protection regulation, fee design, and category mix (politics vs. sports vs. entertainment) decisions about *how much retail is allowed to lose, and to whom*.
+
+## Related Concepts
+
+- **Market making / adverse selection** · retail flow is the LP's profit; informed flow is the LP's loss.
+- **Toxic flow** · the opposite of retail flow.
+- **Execution quality** · what separates retail from pros.
+- **Longshot bias / yes bias** · empirical retail-flow behavioral footprints.
+- **Bid-ask spread** · the tax retail pays for immediacy.
+- **Platform competition** · design choices about how to attract or shield retail.
+- **Regulatory classification** · determines what consumer protections apply.
+
+## Sources
+
+- [A Game of Volatility](https://x.com/Kunallegendd/status/2054191944944038084) — Kunal Doshi · May 12, 2026
+- [Kalshi's Favorite Lie](https://howgamblingworks.substack.com/p/kalshis-favorite-lie) — Isaac Rose-Berman · Apr 29, 2026
+- [The Prediction Market Epidemic: Who's Actually Winning](https://x.com/mominsaqib/status/2046551020231385529) — Momin · Apr 21, 2026
+- [What Happens When Institutional Liquidity Enters Prediction Markets](https://x.com/DaedalusRsch/status/2046219948452979151) — Daedalus Research · Apr 20, 2026
+- [Faster, Shorter, More Automated: Anatomy of Polymarket's Fastest Markets](https://x.com/Dune/status/2044045952730726863) — Dune · Apr 14, 2026
+- [The Two Kinds of Prediction Markets](https://x.com/4castersbet/status/2042268072266920213) — 4casters · Apr 9, 2026
+- [The Yes Bias Might Not Exist](https://x.com/functionspaceHQ/article/2037374271278989409) — functionSPACE · Mar 27, 2026
+- [Is Polymarket a Retail Product or a Pro Trading Venue?](https://x.com/sealaunch_/article/2037228250116522082) — sealaunch intelligence · Mar 27, 2026
+- [Prediction Markets vs. Sports Betting: Market Dynamics, ROI by Cohorts, and Competitive Implications](https://citizens.bluematrix.com/docs/pdf/3beb4505-5a8b-49da-89df-ebdb104ebea9.pdf) — Jordan Bender · Mar 23, 2026
+- [How Wise Is the Crowd? Bias and Edge in Prediction Markets](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6322678) — Deleep, Lee, Bai, Suresh, Dhawan · Feb 28, 2026
+- [Prediction Markets Are Not Good Markets (Yet)](https://murmurationstwo.substack.com/p/prediction-markets-are-not-good-markets) — Nic Carter · Feb 21, 2026
+- [Who Profits from Prediction Markets? Execution, Not Information](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6191618) — Joshua Della Vedova · Feb 7, 2026
+- [Prediction Market Biases Revealed in 72 Million Trades](https://x.com/Ranger_Global/article/2016867581743747447) — Ranger Global · Jan 29, 2026
+- [The Microstructure of Wealth Transfer in Prediction Markets](https://www.jbecker.dev/research/prediction-market-microstructure) — Jonathan Becker · Jan 18, 2026
+- [Dopamine Markets: 2025 Annual Letter](https://www.dopaminemarkets.com/p/dopamine-markets-2025-annual-letter) — Shreyas Hariharan · Jan 8, 2026
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/self-resolving-markets.md b/.qoder/plans/pm-plan/theory_concepts/self-resolving-markets.md
new file mode 100644
index 0000000000..67e033be0c
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/self-resolving-markets.md
@@ -0,0 +1,72 @@
+# Self-Resolving Markets
+
+**Category:** Oracle & Resolution
+**Source:** [PM Atlas — Self-Resolving Markets](https://www.pmatlas.xyz/concepts/self-resolving-markets)
+
+---
+
+## Definition
+
+**Quick definition.** Self-resolving markets settle *without* an external oracle. Two distinct families exist: (1) markets where outcome data is intrinsically on-chain or automatically verifiable (asset prices, smart-contract outputs), and (2) markets for *unverifiable* outcomes that resolve to the crowd's own final consensus, using mechanisms like the Srinivasan-Karger-Chen (SKC) cross-entropy scoring rule. The first family avoids the oracle problem; the second redefines it.
+
+## Key Insights
+
+- **Two distinct meanings · both real.** Type 1 = "on-chain ground truth" (the resolution data lives in a smart contract or a verifiable on-chain price feed; no human or oracle needs to opine). Type 2 = "no ground truth exists at all" (the question is fundamentally subjective; the market resolves to its own crowd's terminal consensus). The first solves the oracle problem by eliminating the off-chain step; the second solves it by abandoning the idea of external truth.
+- **SKC mechanism** (Srinivasan, Karger, Chen, arXiv 2306.04305, 2023, last revised Feb 2025): proposes a *negative-cross-entropy* scoring rule where every agent except the last few is paid based on how much closer their reported probability is to the *final reporter's* probability. The protocol terminates with probability α after each report, and the last k agents receive a flat fee. The key theorem: under standard rational-agent assumptions, *truthful reporting is a perfect Bayesian equilibrium.*
+- **The SKC key insight.** "A reference agent with access to more information can serve as a reasonable proxy for the ground truth." The final reporter has seen the full history of forecasts, so their report is the most informed consensus available. Pay everyone earlier based on how they moved the market toward that consensus.
+- **Why pay delta, not absolute closeness?** michaellwy explainer (Monad blog): if you just paid for being close to the final consensus, traders could free-ride · copy the converged number, collect a prize for "accuracy" without adding insight. The delta-based rule rewards *marginal contribution* (how much your move pushed the market toward where it settled), not endpoint proximity.
+- **Random stopping prevents endgame manipulation.** Each agent doesn't know if they'll be the last. This breaks the incentive for late agents to "skew their report to influence T's report" · because you don't know if you're T-1. The α parameter is the lever for trading off market length vs. final-agent influence.
+- **The flat-fee-for-last-k participants is the anti-gaming patch.** Without it, the agent before the final reporter could manipulate to swing the reference; with it, late agents have no financial stake in influencing the final, so the final report is honestly informed.
+- **The Keynesian-beauty-contest fix.** michaellwy: traders in this mechanism don't earn from "matching what others will say" · they earn from *moving* the consensus. So sincere reporting (your true probability given all available information) is the dominant strategy. The mechanism resists the convergence-on-public-anchor pathology that plagues normal forecasting tournaments.
+- **What this unlocks** (michaellwy/SKC): markets for *unverifiable* questions ,
+- "How safe does new drug X seem right now given today's evidence?"
+- "How likely is policy Y to reduce unemployment in the next year?"
+- "Is this research claim credible yet?"
+- Long-term policy impacts, controversial geopolitical claims, subjective sentiment/cultural trends.
+- **The "no human oracle needed" framing oversells slightly.** Type 1 self-resolving (on-chain price feeds) still requires *trusted* on-chain data · and on-chain price oracles have their own well-documented MEV/manipulation attack surface (st1ne lists oracle latency arbitrage and oracle MEV). Type 2 (SKC) eliminates external truth but introduces other coordination/gaming concerns.
+- **Turf's sdk.markets parimutuel-with-AI-oracle is a hybrid.** Three resolution modes: single admin, multi-admin consensus, and an AI oracle that resolves from arbitrary URLs. The AI-from-URL mode is functionally "the AI fetches the answer from a specified page" · a *partial* self-resolving design (no human review, but still trusting the model and the page).
+- **Self-resolving markets can be reflexive in dangerous ways** (Gunitsky, *Priced to Kill*). When prediction-market prices influence the outcomes they predict · when a "consensus" price *causes* the consensus through media amplification · self-resolution becomes self-reference and the market becomes a propaganda device, not a truth-discovery one. The Paris-thermometer/hair-dryer case is the trivial version; political-narrative manipulation is the dangerous version.
+- **Long-tail markets are the natural application.** Most subjective/policy questions can never attract enough liquidity for a traditional oracle to bother resolving. Self-resolving designs let these markets exist at all · the *informational value of price discovery* is decoupled from the cost-of-oracle question.
+- **The trust regress.** Self-resolving markets require trust in the mechanism design itself (the math of SKC, the on-chain feed integrity, the AI judge prompt). They've moved the trust problem from "oracle voters" to "mechanism specification," but they haven't eliminated it.
+- **SKC equilibrium proof is impressive but assumption-heavy.** The proof assumes rational, self-interested agents with access to private information and common knowledge of the mechanism. Real-world deviations (irrational traders, coordinated groups, partial-information settings) may break the Bayesian-equilibrium result. michaellwy is candid: "admittedly stretches beyond my full comprehension."
+- **The "what about Sybil attacks?" question.** Type 2 self-resolving markets implicitly need agents to be costly-to-spawn (otherwise an attacker creates many fake agents and floods the consensus). SKC paper doesn't fully address this; production implementations would need staking, KYC, or fee thresholds.
+- **The wide-open frontier.** Among the six Oracle & Resolution concepts, self-resolving markets is the one with the *least* current production deployment and the *most* theoretical potential. Whether mainstream platforms (Polymarket, Kalshi) adopt SKC-style mechanisms for long-tail markets is the open question for 2026-2027.
+
+## Notable Quotes
+
+> "How can we design a system where the act of truthfully sharing information becomes the most rational and rewarding strategy, even when no objective 'right answer' will ever exist?"
+> — *michaellwy, *Explainer on Self-Resolving Prediction Markets**
+
+> "A reference agent with access to more information can serve as a reasonable proxy for the ground truth."
+> — *Srinivasan, Karger, Chen*
+
+> "The mechanism pays you for information you add, not for how close you end up to the crowd once it's finished."
+> — *michaellwy*
+
+> "We show that it is a perfect Bayesian equilibrium (PBE) for all agents to report truthfully in our mechanism and to believe that all other agents report truthfully."
+> — *SKC abstract*
+
+> "When you build a financial instrument that pays based on a number produced by a heat sensor in a plastic box, someone will try to point a heat source at that box."
+> — *Seva Gunitsky (on the dual-edge of self-resolving designs that rely on a single data source)*
+
+## Where It Matters
+
+Self-resolving markets are the only mechanism in the corpus that addresses *the entire long tail* of questions that current oracle-driven markets cannot serve. If SKC-style mechanisms work in production, the addressable market for prediction-market platforms expands by orders of magnitude (every policy debate, every research claim, every cultural sentiment is suddenly a tradable question). The catch: the design is theoretically elegant but production-untested at scale, and the assumptions about rational truth-telling have not been stress-tested against actual adversaries.
+
+## Related Concepts
+
+- **proper scoring rules** · SKC is built on negative cross-entropy, a proper scoring rule.
+- **incentive compatibility** · SKC's central theoretical claim.
+- **peer prediction** · the broader family of mechanisms SKC sits inside.
+- **information aggregation** · what self-resolving markets are *for*.
+- **oracle design** · what they try to eliminate.
+- **reflexivity** · the risk: markets that resolve to crowd consensus may *cause* the consensus.
+- **Keynesian Beauty Contest** · the failure mode SKC's payoff structure is designed to prevent.
+
+## Sources
+
+- [Priced to Kill](https://hegemon.substack.com/p/polymarket-is-making-wars-more-dangerous) — Seva Gunitsky · May 13 2026 ·
+- [Every Opinion Deserves A Market](https://x.com/turfsports_/status/2047030930699854334) — Turf · Apr 22 2026 ·
+- [Explainer on Self-Resolving Prediction Markets](https://blog.monad.xyz/blog/self-resolving-prediction-markets) — michaellwy · Nov 11 2025 ·
+- [Self-Resolving Prediction Markets for Unverifiable Outcomes](https://arxiv.org/abs/2306.04305) — Srinivasan, Karger, Chen · Jun 7 2023 (rev. Feb 18 2025)
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/semantic-tick-size.md b/.qoder/plans/pm-plan/theory_concepts/semantic-tick-size.md
new file mode 100644
index 0000000000..231d1d1642
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/semantic-tick-size.md
@@ -0,0 +1,48 @@
+# Semantic Tick Size
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Semantic Tick Size](https://www.pmatlas.xyz/concepts/semantic-tick-size)
+
+---
+
+## Definition
+
+**Quick definition.** The minimum price increment in a prediction market that doubles as a *narrative unit*. Because contracts resolve at $0 or $1, each one-cent move is universally read as a one-percentage-point probability revision · making microstructure noise *appear* informative. Coined by @allquantor.
+
+## Key Insights
+
+- @allquantor's source data: **600 million Polymarket orderbook datapoints**. Headline finding: **~70% of one-cent price moves do not continue in the same direction** · most penny moves are noise that gets reinterpreted as signal because the price *is* a probability.
+- The implication is a **contrarian fade strategy**: systematically fade penny moves on PMs and you harvest the overreaction.
+- The piece situates this against Paul Tetlock's TradeSports microstructure research: passive limit-order walls slow price discovery while impatient market orders amplify short-term noise. PMs amplify this dynamic because every penny carries narrative weight.
+- functionSPACE V1: the $0.01 tick size compounds liquidity fragmentation by adding a **rounding tax** that makes low-probability contracts structurally imprecise. A contract that "should" trade at 0.3% can only be quoted at 0% or 1%, both of which are wrong.
+- The combined effect: penny ticks are simultaneously **too large** for low-probability contracts (rounding tax) and **too small** for mid-probability contracts (noise gets narrativized).
+- Implicit design implication: prediction markets may benefit from probability-aware variable tick sizes · finer granularity in the tails, coarser in the middle.
+
+## Notable Quotes
+
+> "70% of one-cent price moves do not continue in the same direction."
+> — *@allquantor*
+
+> "Each penny reads as a one-percentage-point probability change, creating overreactions that a contrarian fade strategy can profitably harvest."
+> — *@allquantor*
+
+> "The $0.01 tick size... creates a rounding tax that makes low-probability contracts structurally imprecise."
+> — *functionSPACE*
+
+## Where It Matters
+
+Semantic tick size is one of those concepts that, once named, becomes hard to unsee. It explains why CNN/WSJ headlines about "Polymarket probability of X jumped 4 points" can be entirely noise, why MMs can profitably fade headline moves, and why every continuous-distribution / multi-outcome design proposal is partially motivated by escaping the penny's narrative weight. It's also a structural reason PM prices are *not* directly equal to true probabilities · they're heavily perturbed by microstructure with a probability-shaped projection.
+
+## Related Concepts
+
+- **Bid-ask spread** · penny ticks bound how tight spreads can be.
+- **Price discovery** · tick size shapes how price discovery looks at high frequency.
+- **Liquidity fragmentation** · the rounding tax compounds across many fragmented contracts.
+- **Binary contracts / multi-outcome markets** · the structural environment.
+- **Calibration** · semantic tick noise pushes prices away from calibrated probabilities.
+
+## Sources
+
+- [Binary Events: What Happens When You Split One Market Into Twenty](https://x.com/functionspaceHQ/status/2039554933024776516) — functionSPACE · Apr 2, 2026
+- [Prediction Markets Have a Semantic Tick Size](https://x.com/ZEITFinance/article/2034314079599243694) — allquantor · Mar 19, 2026
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/superforecasting.md b/.qoder/plans/pm-plan/theory_concepts/superforecasting.md
new file mode 100644
index 0000000000..9cc42f1a51
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/superforecasting.md
@@ -0,0 +1,50 @@
+# Superforecasting
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Superforecasting](https://www.pmatlas.xyz/concepts/superforecasting)
+
+---
+
+## Definition
+
+Techniques and traits of forecasters who consistently outperform base rates and prediction markets. Coined by Philip Tetlock in the Good Judgment Project. Operationalized via Brier scores, calibration, foxes vs hedgehogs, and trainable habits (granular probability vocabularies, Bayesian updating, viewpoint diversity).
+
+## Key Insights
+
+- Tetlock's *Superforecasting* → Polymarket. Direct line from the book's thesis (forecasting skill is measurable, trainable, outperforms expert punditry) to Polymarket's success in the 2024 US election. Key concepts: foxes vs hedgehogs, Good Judgment Project, Brier scores, calibration. Polymarket effectively *operationalized Tetlock's framework at scale* by converting crowd forecasting into a liquid financial market (Ahnianchykau).
+- "What if we're capturing the wrong signal?" · binary markets flatten the precision that separates superforecasters from average predictors. Vanderbilt study: PredictIt 93% accuracy vs 67% on high-volume platforms · more liquidity ≠ better signal. The argument: binary mechanism doesn't give superforecasters room to express their edge (Jo).
+- ForecastBench: GPT-4.5 Brier 0.101 vs superforecasters 0.081. LLMs improving ~0.016 Brier/year → projected parity by late 2026. Some models *game* the benchmark by copying PM prices rather than reasoning independently (Forecasting Research Institute).
+- ForecastBench full-read additional details: three interpretations of the 0.02 Brier gap · (1) "GPT-4.5's Brier score is 25% higher (i.e., worse) than superforecasters'," (2) superforecasters capture 68% of the possible improvement from always-50% baseline (Brier 0.25), GPT-4.5 captures 60% · 8pp short, (3) the 0.02 gap is "smaller than the 0.03-point improvement from GPT-4 to GPT-4.5, suggesting the distance to superforecasters is less than one major model generation." A year ago, the median public forecaster ranked #2; now it sits at #22 · LLMs have decisively passed non-expert humans.
+- ForecastBench on the LLM "copy-paste" tactic: "GPT-4.5's predictions have a correlation of 0.994 with provided market forecasts, and it submitted exact market values for 26 out of 122 questions, with a median deviation of just 0.5 percentage points. Since prediction markets are highly accurate, this tactic works but reveals little about underlying capabilities." The Baseline leaderboard (no market access) shows true LLM improvement of 0.036 Brier points/year · faster than the Tournament leaderboard (which allows market access) at 0.015/year.
+- ForecastBench projected parity dates by question type: dataset questions June 2026 (95% CI Nov 2025 – Apr 2027), market questions March 2026 (95% CI Mar 2025 – Dec 2028), all-questions November 2026 (95% CI Dec 2025 – Jan 2028). Submissions are now open to the public.
+
+## Notable Quotes
+
+> "Polymarket effectively operationalized Tetlock's framework at scale."
+> — *Ahnianchykau*
+
+> "Binary markets flatten complex beliefs into coin flips, losing the precision that separates superforecasters from average predictors."
+> — *Jo*
+
+> "Some models game the benchmark by copying prediction market prices rather than reasoning independently."
+> — *Forecasting Research Institute*
+
+## Where It Matters
+
+Superforecasting is the human-skill substrate that PMs convert into a price. If superforecasters are 0.02 Brier-points better than the next best LLM (and ~0.04 better than an average forecaster), the question for PM builders is: can your market mechanism extract that 0.02 edge? Binary markets compress it; distribution markets (per Jo, functionSPACE, Tide) plausibly preserve more of it because superforecasters typically express belief as a *narrow distribution*, not as "55% YES." For Dekant, this is part of the user-targeting story: superforecasters are the natural early adopters of a curve-drawing market because the surface finally matches the shape of their belief.
+
+## Related Concepts
+
+- **Calibration / Brier score** · operational metrics
+- **Forecasting accuracy** · the umbrella claim
+- **Wisdom of crowds** · superforecasters often *outperform* crowds
+- **Distribution markets** · argued to preserve superforecaster edge that binary markets compress
+- **AI agents** · the emerging competitor benchmark
+- **Information aggregation** · superforecasters as the high-quality input
+
+## Sources
+
+- [The Book That Predicted Polymarket](https://x.com/mikita_crypto/article/2029939210350645729) — Ahnianchykau · Mar 6, 2026 [FULL_READ]
+- [What If We're Capturing the Wrong Signal?](https://x.com/TideMarkets/article/2016912289941827801) — Jo · Jan 29, 2026 [FULL_READ]
+- [How Well Can Large Language Models Predict the Future?](https://forecastingresearch.substack.com/p/ai-llm-forecasting-model-forecastbench-benchmark) — Forecasting Research Institute · Oct 8, 2025 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/temporal-arbitrage.md b/.qoder/plans/pm-plan/theory_concepts/temporal-arbitrage.md
new file mode 100644
index 0000000000..3fc90a5c25
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/temporal-arbitrage.md
@@ -0,0 +1,45 @@
+# Temporal Arbitrage
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Temporal Arbitrage](https://www.pmatlas.xyz/concepts/temporal-arbitrage)
+
+---
+
+## Definition
+
+**Quick definition.** Profiting from swings in probability estimates as a prediction market converges toward its binary resolution · trading volatility rather than the final outcome. The defining property of PM positions is **time-bounded decay and binary convergence to truth**, which creates a distinct trading mechanic unavailable in other asset classes.
+
+## Key Insights
+
+- njokuScript ("A New Kind of Asset Class") coined the term: PM positions have **time-bounded decay and binary convergence to truth**, properties that produce a unique trading mechanic. Volatility on the way to resolution is itself tradable.
+- Kunal Doshi ("A Game of Volatility"): Polymarket fast crypto markets generate 16% of volume but ~40% of fees. **19 algo addresses** extract consistent profits through paired trading and maker execution; **69% of retail lose**. PMs are becoming a game of volatility rather than forecasting.
+- njokuScript's leverage piece: in a **1× market only whales can move prices** because the barrier is capital, not insight; at 10× thousands of smaller traders can collectively contest mispricing. The gap-risk workaround is a **temporal-arbitrage approach where leveraged markets close before event resolution** · liquidation engines never face the discontinuity. Vault-based yield layer captures trading-activity returns rather than directional outcome exposure.
+- Implicit: temporal arbitrage is the leveraging-amenable subset of PM trading. Strategies that close before resolution avoid the binary jump that wrecks margin engines.
+- The natural counterparty to temporal arbitrageurs is bonding-trade holders (who *want* to ride to resolution for the small yield) and retail directional bettors (who arrive late). The trader earning the spread between these two cohorts is harvesting both time decay and execution edge simultaneously.
+
+## Notable Quotes
+
+> "Prediction markets are becoming a game of volatility rather than forecasting."
+> — *Kunal Doshi*
+
+> "Time-bounded decay and binary convergence to truth create a distinct trading mechanic."
+> — *njokuScript*
+
+## Where It Matters
+
+Temporal arbitrage is the structural escape hatch from gap risk: if your strategy never holds positions through resolution, the binary jump can't hurt you. That makes it the only path so far for safe leveraged PM products (njokuScript's vault model, Astaria's perp thesis), and it explains why short-duration crypto up/down markets dominate Polymarket fees · fast markets are pure temporal-volatility products with minimal binary risk per trade.
+
+## Related Concepts
+
+- **Gap risk** · what temporal arbitrage avoids by closing before resolution.
+- **Execution quality** · the marginal edge in volatility trading.
+- **Market making** · MMs are continuous temporal arbitrageurs.
+- **Liquidity provision** · vault-based yield models monetize this.
+- **Retail flow** · the non-temporal counterparty.
+
+## Sources
+
+- [A Game of Volatility](https://x.com/Kunallegendd/status/2054191944944038084) — Kunal Doshi · May 12, 2026
+- [Leverage Fixes Prediction Markets: The Case for Why 10x Is Safer Than 1x](https://x.com/njokuScript/article/2022351299547689325) — njokuScript · Feb 14, 2026
+- [Prediction Markets: A New Kind of Asset Class](https://x.com/njokuScript/article/2010080717729046613) — njokuScript · Jan 11, 2026
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/time-arbitrage.md b/.qoder/plans/pm-plan/theory_concepts/time-arbitrage.md
new file mode 100644
index 0000000000..6c3167a76b
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/time-arbitrage.md
@@ -0,0 +1,42 @@
+# Time Arbitrage
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Time Arbitrage](https://www.pmatlas.xyz/concepts/time-arbitrage)
+
+---
+
+## Definition
+
+**Quick definition.** Profiting from a delay between when information becomes known and when the prediction market price adjusts to reflect it. Closely related to *oracle latency arbitrage* · the classic example being trading on news before the on-chain oracle (e.g., UMA) updates the resolution-relevant data.
+
+## Key Insights
+
+- st1ne enumerates **oracle latency arbitrage** as one of five MEV-style edges on Polymarket: trading on news *before* UMA oracle updates. Related: **resolution arbitrage** (front-running outcome settlement) and **dispute sniping** (gaming the UMA dispute process). All three are subsets of the broader "time arbitrage" frame.
+- Nekt0 lists time arbitrage as one of **eight distinct PM arbitrage strategies**, with concrete dollar examples and risk factors.
+- The PM-specific structural cause: **oracles update slower than information propagates**. Twitter posts, CNBC interviews, and government announcements all hit faster than UMA, Kalshi's manual review, or any other resolution mechanism. Time-arbitrageurs exploit the gap.
+- Related (covered under *insider trading* and *toxic flow*): when the time-arb edge becomes large enough, it bleeds into MNPI territory · buying a market on the basis of imminent-but-unannounced information is structurally similar to oracle-latency arb when the resolution signal is itself the news.
+- The defense against time arbitrage looks identical to the defense against gap risk: batched auctions, delayed settlement, dynamic spread widening near expected information arrival.
+
+## Notable Quotes
+
+> "Sophisticated actors extract value from structural inefficiencies rather than informational edges."
+> — *st1ne (re: oracle latency arb)*
+
+## Where It Matters
+
+Time arbitrage is what determines who profits from the gap between *real-world resolution* and *on-chain settlement*. On Polymarket it's a continuous revenue stream for actors with low-latency news pipelines; on Kalshi, the same edge exists but in a centralized form (the team that gets news first into the matching engine). Every platform's "fairness" claims are testable against how compressed this window is.
+
+## Related Concepts
+
+- **Orderflow arbitrage** · sibling strategy.
+- **Cross-platform arbitrage** · time arbs frequently cross venues.
+- **Resolution criteria / oracle design** · directly control the size of the time-arb window.
+- **Insider trading** · when the news being front-run is non-public, the line gets crossed.
+- **Adverse selection** · time arb is one of its purest forms.
+- **Batched auctions** · leading mitigation.
+
+## Sources
+
+- [Polymarket Is a Hidden MEV Playground. Most Traders Have No Idea.](https://x.com/SolSt1ne/article/2032008002094366899) — st1ne · Mar 13, 2026
+- [All Types of Arbitrage on Prediction Markets](https://x.com/Nekt_0/article/2019107985079816395) — Nekt0 · Feb 5, 2026
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/toxic-flow.md b/.qoder/plans/pm-plan/theory_concepts/toxic-flow.md
new file mode 100644
index 0000000000..abf9d971eb
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/toxic-flow.md
@@ -0,0 +1,60 @@
+# Toxic Flow
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Toxic Flow](https://www.pmatlas.xyz/concepts/toxic-flow)
+
+---
+
+## Definition
+
+**Quick definition.** Order flow from informed traders that systematically moves against the market maker's positions · adverse selection in motion. In prediction markets the "toxicity" can be extreme because counterparties may hold near-perfect information about a discrete resolution event.
+
+## Key Insights
+
+- semaji.eth: market makers profit from retail flow *while avoiding toxic informed counterparties*. The Jane Street coffee question is the canonical illustration: if someone wants to trade with you, you should be *less* confident.
+- semaji.eth Part II: in PMs, toxic flow is "worse than any other asset class" because informed counterparties can have **near-perfect information and take out entire order books** in a single tweet-driven move.
+- sybilpm: snipers paying 10 cents on geopolitical markets that resolve to 0.99 are pure toxic flow. Costs the MM **80 cents on the dollar** per fill · proposes batched auctions to neutralize. Specific case: "dudukos" cleared the entire order book of "Will Israel strike Gaza on January 3, 2026?" from $0.10 to $0.80 in a single trade, then repeated the pattern Jan 10/11/12 across dozens of Israel-strike markets. Pure toxic flow with no informational edge · just faster reaction time.
+- sybilpm's "liquidity mirage": passive LPs on Polymarket and Kalshi "behave more like underwriters of terminal risk than classical market makers" · the platforms burn millions on LP incentives, but rewards never sized to cover 80-cents-per-contract gap risk. The retail LP earns yield in normal periods and gets wiped out by toxic flow on the day news breaks.
+- Mitts & Ofir's $143M anomalous-profit estimate at 69.9% win rate (60σ above chance) is the empirical signature of toxic flow on Polymarket · even with a conservative buy-side-only, $500+ filter, the magnitude of informed-trader extraction is enormous.
+- Dune analysis: bots control 55–62% of fast-market volume on Polymarket. Without identification, hard to know how much of bot flow is toxic vs. arbitrage vs. liquidity-providing.
+- taetaehoho: sportsbooks compensate for toxic flow via **counterparty-aware pricing** (price-discriminate against known winners). PMs compensate via maker competition and orderbook transparency. PM prices are 100–300 bps better on liquid markets but **10–50% spreads on long-tail** are the toxic-flow tax.
+- Momin: the structural funnel sends **70% of 1.7M Polymarket addresses to losses** because retail flow is by definition the *non-toxic* side that informed flow needs to extract from.
+- Daedalus Research: the academic frame is shifting from "accuracy" to **trader welfare** · which is largely a function of how toxic flow is distributed.
+- Becker provides the category-level distribution of toxic vs. retail flow: Finance gap 0.17pp (efficient · informed/uninformed mix is balanced); Sports 2.23pp; World Events 7.32pp; Media 7.28pp; Entertainment 4.79pp. Higher gap = more toxic-flow exposure to takers.
+- Becker on the temporal arrival of toxic flow on Kalshi: before October 2024 (CFTC court decision + election volume surge), takers actually outperformed makers (+2.0% vs -2.0%). Toxic flow as we know it today emerged when sophisticated algorithmic MMs arrived to absorb the new retail volume · pre-existing markets had been a different equilibrium.
+
+## Notable Quotes
+
+> "Market makers profit from retail flow while avoiding toxic informed counterparties."
+> — *semaji.eth*
+
+> "Informed counterparties can have near-perfect information and take out entire order books."
+> — *semaji.eth*
+
+> "Passive LPs on these venues behave more like underwriters of terminal risk than classical market makers."
+> — *Lotus*
+
+## Where It Matters
+
+Toxic flow is the asset class's central pricing input · its presence dictates spread width, its absence determines whether a market exists at all, and its mitigation defines every successful platform's microstructure. The empirical record is that long-tail markets fail not because no one wants to trade them but because no MM will quote into a venue where the modal counterparty knows the answer.
+
+## Related Concepts
+
+- **Adverse selection** · the conceptual parent.
+- **Market making** · what bears the cost.
+- **Retail flow** · the desirable counter-flow that subsidizes toxic flow losses.
+- **Gap risk** · toxic flow's binary-specific amplifier.
+- **Batched auctions** · leading mitigation.
+- **Long-tail markets** · where toxic flow dominates and MMs disappear.
+- **Insider trading** · one well-defined source of toxic flow.
+
+## Sources
+
+- [Sportsbooks vs Prediction Markets - Market Structure and Its Effects](https://x.com/0xtaetaehoho/status/2054215076471873575) — taetaehoho · May 12, 2026
+- [The Prediction Market Epidemic: Who's Actually Winning](https://x.com/mominsaqib/status/2046551020231385529) — Momin · Apr 21, 2026
+- [What Happens When Institutional Liquidity Enters Prediction Markets](https://x.com/DaedalusRsch/status/2046219948452979151) — Daedalus Research · Apr 20, 2026
+- [Faster, Shorter, More Automated: Anatomy of Polymarket's Fastest Markets](https://x.com/Dune/status/2044045952730726863) — Dune · Apr 14, 2026
+- [The Sniper's Tax](https://sybilpm.substack.com/p/the-snipers-tax) — sybilpm · Mar 8, 2026
+- [The Liquidity Problem in Prediction Markets, Part II: Adverse Selection in Prediction Markets](https://x.com/semajeth/status/1975211193418563697) — semaji.eth · Oct 6, 2025
+- [The Liquidity Problem in Prediction Markets, Part I: Adverse Selection and Market Making](https://x.com/semajeth/status/1967598604006134024) — semaji.eth · Sep 15, 2025
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/uma-protocol.md b/.qoder/plans/pm-plan/theory_concepts/uma-protocol.md
new file mode 100644
index 0000000000..78967e19b8
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/uma-protocol.md
@@ -0,0 +1,68 @@
+# UMA Protocol
+
+**Category:** Oracle & Resolution
+**Source:** [PM Atlas — UMA Protocol](https://www.pmatlas.xyz/concepts/uma-protocol)
+
+---
+
+## Definition
+
+**Quick definition.** UMA is an *optimistic oracle*: proposed answers are presumed correct unless challenged within a liveness window, with disputes adjudicated by token-holder vote in the Data Verification Mechanism (DVM). It is the production oracle behind Polymarket · and consequently the most-stressed oracle in the prediction-market industry. Every major Polymarket dispute case study in 2024-2026 is also a UMA case study.
+
+## Key Insights
+
+- **The "optimistic" assumption is the design's load-bearing premise.** Most proposals settle without dispute because the bond requirement and slashing economics make false assertions unprofitable. The protocol was originally built for synthetic-asset price feeds, where disputes are rare and the universe of "true answers" is narrow. Polymarket pushed it into open-ended natural-language event resolution, which is a much more adversarial domain.
+- **The four-phase pipeline** (Smaliy, Hadi/Cossar/Shimony, Lou Kerner): (1) **Assertion** · proposer posts the outcome with a bond. (2) **Liveness period** · anyone can dispute by posting a counter-bond. (3) **Dispute** · challenge routes to UMA's Data Verification Mechanism (DVM). (4) **DVM vote** · UMA token-holders cast on-chain votes, weighted by staked tokens; aligned votes earn rewards, dissenting ones get slashed.
+- **The conflict-of-interest design flaw is structural.** UMA voters are not segregated from the markets they vote on. The same Polygon address can hold Polymarket positions *and* stake UMA *and* vote on the DVM. No recusal, no KYC, no auditable wall.
+- **DVM voter participation is low.** Prosperi estimates only ~20% of $UMA tokens actively vote, which means the *effective* corruption budget is much smaller than the token's full market cap. A would-be attacker only needs to swing the voting subset.
+- **The Polymarket-UMA relationship is also commercial.** Polymarket's Liquidity Rewards Program was historically funded by $UMA tokens granted from the UMA DAO treasury (1.25M UMA in March 2023). This entanglement undermines the "independent oracle" framing.
+- **UMA's bond/reward economics:**
+- Bond requirement: deters spurious assertions/disputes
+- Slashing on losing side: makes dishonest votes economically costly
+- Reward to majority voters: incentivizes participation
+- But: rewards scale with token holdings, so a coordinated whale group can capture both the reward and the trade.
+- **The Venezuela 2024 case is the canonical UMA failure.** (Muci) Polymarket's stated primary resolution source was "official information from Venezuela." Venezuelan electoral authority declared Maduro the winner. UMA voters, after Discord/forum lobbying, overrode this and declared Gonzalez the winner. The market's own rules were the casualty.
+- **The Zelensky-suit market is the canonical *semantic* failure.** $240M in volume hinged on whether a "militarized black outfit" was a "suit." Multiple major outlets (BBC, NYP, Reuters, NYT) called it a suit. UMA voters ruled there was no "consensus of credible reporting" and resolved NO. The protocol did exactly what it was designed to do · UMA voters just disagreed with the press.
+- **The US government shutdown market is the canonical *technicality* failure.** Resolution rule pointed at OPM's website status. Trump signed the bill Nov 12; OPM didn't update its site until Nov 13. Traders who correctly predicted the Nov 12 end-date lost to a webmaster's delay. UMA dutifully reported what the website said.
+- **UMA token market cap is structurally too small for Polymarket's open interest.** Prosperi's Corruption Value Multiple math (Nov 2024): Polymarket OI ≈ $300M; UMA float ≈ $220M; majority capture cost ≈ $110M. CVM = ($300M / 2) / $110M ≈ **1.36x** · meaning $1 of attack capital yields $1.36 of expected profit. And that's the conservative bound assuming 100% voter turnout and unambiguous resolutions; reality is much worse.
+- **The DVM vote is the most expensive layer in the stack.** It's slow (days, not minutes), social (Discord lobbying drives outcomes), and conflicted (voters can hold market positions). It's also the layer with the highest manipulation surface.
+- **Lou Kerner's UMIP-107 walkthrough** lists the four UMA response codes: p1 = NO, p2 = YES, p3 = answer cannot be determined, p4 = cannot be determined + early-expiration. Most disputed cases collapse into p3 or hostile interpretations.
+- **AI/LLM judges are the most-cited proposed UMA replacement** (Hall, XO Labs, Smaliy). The pitch: replace token-vote subjectivity with a cryptographically committed model + prompt. Loses some flexibility, gains transparency and bribery-resistance.
+- **UMA still has structural advantages over alternatives.** Versus Augur's $REP fork mechanism: UMA is faster and avoids universe-splits. Versus centralized oracles: UMA is more transparent and harder to capture covertly. Versus single-data-source oracles: UMA can handle natural-language criteria.
+- **Polymarket's bulletin-board feature is a partial mitigation.** Allows market creators to directly question UMA about ambiguity *before* resolution. Reduces disputes by narrowing interpretation early. Not used enough.
+- **UMA expansion into broader use creates aggregate risk.** UMA is also used by Across, Outcome.Finance, and others · a DVM capture could cascade across multiple protocols. The bigger the UMA footprint, the more incentive to attack the DVM.
+- **The Cardi B halftime market exposed cross-platform UMA limits.** Polymarket (UMA-resolved) = YES; Kalshi (centralized-resolved) = NO. Same event, two oracle systems, two answers. UMA is correct *for its rules* but cannot impose those rules cross-platform.
+
+## Notable Quotes
+
+> "It's some 'decentralized' blockchain-based mechanism that settles bets. It's called UMA. Per their website, they're a 'decentralized truth machine' that can 'record any verifiable truth or data onto a blockchain.'… UMA token-holders are unknown crypto-savvy persons that could be anywhere in the world. They aren't accountable to Polymarket or its customers or liable in any legal system. They can place bets on the very same prediction markets that they vote to resolve."
+> — *Frank Muci*
+
+> "UMA has a market cap of $240M. So it's easy to see how someone betting on Trump could likely buy enough UMA to sway the vote and have it be highly profitable."
+> — *Lou Kerner*
+
+> "With an investment of c. $110m (half of $UMA's float) someone could theoretically amass enough tokens to influence the oracle's dispute resolutions. The CVM would be ($300 / 2) / $110 = 1.36x, indicating that for every $1 invested in manipulation there's a potential gain of $1.36."
+> — *Luca Prosperi*
+
+> "Holding a majority of $UMA tokens grants decisive influence over dispute resolutions."
+> — *Luca Prosperi*
+
+## Where It Matters
+
+UMA is the de facto reference implementation of decentralized prediction-market resolution · every new oracle proposal in 2025-2026 is benchmarked against it. Its failures are the strongest empirical case that token-voting oracles do not scale to consumer-prediction-market value-at-stake. The next generation of designs (XO Oracle, AI-judge commits, Meta Pool credibility tokens) all explicitly position themselves as UMA replacements.
+
+## Related Concepts
+
+- **oracle design** · UMA is one concrete instance; the broader design space is bigger.
+- **dispute resolution** · UMA's DVM *is* the dispute layer.
+- **corruption value multiple** · the diagnostic UMA repeatedly fails when paired with Polymarket open-interest.
+- **resolution criteria** · UMA inherits all the ambiguity in the upstream rules.
+- **market manipulation** · UMA's voter-conflict surface is a manipulation surface.
+
+## Sources
+
+- [The Hidden Risk Of Prediction Markets: 14 Resolution Failures That Cost $500M](https://x.com/xomarket/status/2046924302952640934) — XO Market · Apr 22 2026 ·
+- [The Economy of Truth: Inside the Decentralized Courtroom of Polymarket & UMA](https://x.com/smaaaaliy/article/2033242239498076190) — Smaliy · Mar 16 2026 ·
+- [Why Prediction Markets Are Broken (And How to Fix Them)](https://x.com/michael_lwy/status/1877211555496079683) — michaellwy · Jan 9 2025 ·
+- [Polymarket Settles Bet Against Its Own Rules](https://frankmuci.substack.com/p/polymarket-settles-bet-against-its) — Frank Muci · Aug 8 2024 ·
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/wash-trading.md b/.qoder/plans/pm-plan/theory_concepts/wash-trading.md
new file mode 100644
index 0000000000..e06aa2113c
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/wash-trading.md
@@ -0,0 +1,61 @@
+# Wash Trading
+
+**Category:** Liquidity & Trading
+**Source:** [PM Atlas — Wash Trading](https://www.pmatlas.xyz/concepts/wash-trading)
+
+---
+
+## Definition
+
+**Quick definition.** Executing offsetting buy and sell transactions to inflate apparent trading volume without taking real market risk. Federally prohibited under the Commodity Exchange Act. On prediction markets, wash trading boosts platform metrics, distorts liquidity signals, and laundered trader cohorts can pose as legitimate market makers.
+
+## Key Insights
+
+- Rajiv Sethi opens with a forensic vignette from **6pm on November 1 (~2 days before US polls opened)** on Polymarket: a single trader executed a string of paired buy-then-sell transactions where each purchase was followed quickly by a sale at "a price that is a tenth of a penny lower" · guaranteed micro-losses on every round-trip, but enormous volume inflation. The puzzle: are those losses real, or are the counterparties also in on it?
+- The paper Sethi co-authors (lead author **Allen Sirolly**, with **Hongyao Ma** and **Yash Kanoria**) introduces a novel detection algorithm: **modular, with an initialization stage and an iterative network-based redistribution stage**. Initialization scores each wallet on its "propensity to open and close positions repeatedly". Redistribution updates each wallet's score from the volume-weighted scores of its counterparties, iterated to convergence. Wallets retained above a threshold are flagged.
+- The theoretical core: in a counterparty network, **wash traders exhibit homophily** (trade only within their collusive clique) while **market makers exhibit heterophily** ("market makers seldom trade with other market makers" because each side of an MM trade is a liquidity provider vs taker, not two MMs).
+- Empirical anchor (now expanded with article detail): the flagged cluster is a group of **200 wallets all starting with "MAY"** (e.g. MAY20, MAY175, MAY176) trading "almost exclusively with each other". Collectively they traded **over 116 million shares** generating **>$113M in dollar volume** for an aggregate loss of just **$57.86**. One wallet · **MAY117** · bought and sold over a million shares across **33 markets** over several months and ended up with **profits of precisely zero**.
+- Algorithm convergence: Figure 5 in the paper shows many wallets red-flagged at initialization get dropped at successive redistribution iterations, "leaving wash traders identified with high confidence after three iterations".
+- Sethi names two foundational adverse-selection references for MM behaviour: **Glosten and Milgrom** and **Kyle** · important because the legal/economic distinction between an MM and a wash trader hinges on adverse-selection exposure (real MMs face it; wash traders avoid it).
+- Sethi's strategic note: any published detection algorithm "is likely to result in strategic responses by wash traders that better allow them to evade detection. Ideally one would want to design a system that is not vulnerable to such gaming, but whether or not this ideal is even attainable remains an open question."
+- Sethi's framing of the contribution: the *enduring* contribution is "a transparent and modular detection algorithm · rather than the specific empirical claims about a particular exchange". Trading volume is the most commonly cited measure of market participation, and wash trading "makes it imprecise and less meaningful".
+- Sethi reports the **initial reaction from at least one Polymarket insider has been encouraging** · suggesting cooperation rather than adversarial response from the venue.
+- Luca Prosperi's separate finding (referenced in liquidity-provision file): during the 2024 election cycle, **~41% of Polymarket volume appeared to be wash trading** and four coordinated accounts controlled 23% of open interest.
+- The implication is severe: **a significant share of headline PM volume may be wash trading**, and when CNN/WSJ broadcast probabilities backed by that volume, they're laundering manipulated signal through credible newsrooms (Vaidik Mandloi).
+- The legal hook: wash trading is "prohibited by law on regulated exchanges in the United States, and can be subject to significant sanctions" under the **Commodity Exchange Act** · making Kalshi's CFTC-regulated structure structurally tougher for wash traders than Polymarket's pseudonymous blockchain.
+- Detection is network-based; pseudonymous chains help and hurt simultaneously · wallet linkages are visible on-chain but real-world identity is not.
+
+## Notable Quotes
+
+> "If one were to represent traders as nodes and transactions between them as edges in a network, wash traders would exhibit homophily while market makers exhibit heterophily."
+> — *Rajiv Sethi*
+
+> "These wallets collectively traded over 116 million shares and generated more than 113 million in dollar volume, but ended up with an aggregate loss of just $57.86."
+> — *Rajiv Sethi*
+
+> "One of these was MAY117, an account that bought and sold over a million shares across 33 markets over several months and ended up with profits of precisely zero."
+> — *Rajiv Sethi*
+
+> "Trading volume is among the most commonly used measures of market participation and conviction, and wash trading makes it imprecise and less meaningful."
+> — *Rajiv Sethi*
+
+> "Any algorithm of this kind, once published and implemented, is likely to result in strategic responses by wash traders that better allow them to evade detection."
+> — *Rajiv Sethi*
+
+## Where It Matters
+
+Wash trading is one of the most legally exposed pathologies in the asset class · and one of the easiest for sophisticated detection algorithms to identify post-hoc. Every platform's headline volume figure should be discounted by the share that on-chain network analysis flags as wash trading. For regulated venues (Kalshi) this is enforced; for pseudonymous venues (Polymarket) it's a structural risk that can be detected but only after the fact. The fact that 23% of OI during the 2024 election was controlled by four wallets is the single most-cited data point that PMs are not yet ready to serve as authoritative information signals.
+
+## Related Concepts
+
+- **Market manipulation** · the broader category.
+- **Market surveillance** · the detection regime.
+- **Market making** · what wash traders pose as.
+- **Insider trading** · frequent companion offense.
+- **Liquidity provision** · wash trading inflates reported liquidity without providing real depth.
+- **Regulatory classification** · determines who has authority to enforce.
+
+## Sources
+
+- [The Detection of Wash Trading](https://rajivsethi.substack.com/p/the-detection-of-wash-trading) — Rajiv Sethi · Nov 12, 2025
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/wisdom-of-crowds.md b/.qoder/plans/pm-plan/theory_concepts/wisdom-of-crowds.md
new file mode 100644
index 0000000000..ec3ce13250
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/wisdom-of-crowds.md
@@ -0,0 +1,67 @@
+# Wisdom of Crowds
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Wisdom of Crowds](https://www.pmatlas.xyz/concepts/wisdom-of-crowds)
+
+---
+
+## Definition
+
+The phenomenon where aggregated group estimates often outperform individual experts in forecasting accuracy. The Surowiecki/Galton folk theory PMs are marketed on · but empirical 2026 research significantly qualifies it: PM accuracy may be informed-minority-driven, not crowd-driven.
+
+## Key Insights
+
+- Prediction markets are the next stage in the history of expression (print → radio → social media → markets) · markets demand speakers bear consequence for being wrong. Hayek on price as coordination, Taleb on skin in the game, Hanson on futarchy. Staked speech out-trusts cheap talk in an AI-saturated information environment (Abhitej).
+- Polymarket is not a truth machine: headline Brier 0.047 hides sports Brier 0.325 (worse than coin flip); 99% of volume in last hours. PMs work on ~2% of contracts (binary, high-profile, short-term, millions at stake). CNN/WSJ broadcasting illiquid odds = whale trades laundered through newsrooms (Mandloi).
+- ~3% of accounts drive most price discovery. PM accuracy is *informed minority*, not wisdom of crowds. Remaining accounts contribute volume but minimal information · their losses fund the informed minority (Gomez-Cram, Guo, Jensen, Kung).
+- Tiered framework for evaluating PM reliability: financialized economic indicators highest, speculative prop bets lowest. Three use cases: triangulating against polls, nowcasting delayed econ data, hedging event risk. Federal Reserve paper validates Kalshi data quality (Isar Bhattacharjee).
+- Tetlock's Superforecasting → Polymarket: forecasting skill is measurable, trainable, outperforms expert punditry. Foxes vs hedgehogs, Good Judgment Project, Brier scores, calibration. Polymarket operationalized Tetlock's framework at scale (Ahnianchykau).
+- Skin-in-the-game accountability produces more accurate signals than commentary-based analysis. Price movements anticipate news before official announcements. COVID-19 was a case where markets *underperformed* (JP).
+- "What if we're capturing the wrong signal?" · binary markets flatten complex beliefs into coin flips, losing the precision that separates superforecasters from average predictors. 2024 French trader whale ($30M moving election odds); Vanderbilt: PredictIt 93% vs 67% on high-volume platforms (Jo).
+- Hayek on information aggregation via price signals; thick vs thin markets; when markets work (elections, scientific replication) and when they struggle; the oracle problem; corporate forecasting and futarchy (a16z podcast).
+- Wisdom of crowds theory → decentralized oracle mechanisms. PMs could systematize event probabilities to expand financial markets like derivatives historically did · but current implementations face liquidity fragmentation, oracle incentives, complexity (Luca Prosperi).
+- 2024 Biden-Trump race: Polymarket priced in Biden's withdrawal probability while polls measured only head-to-head support (fil).
+- Luca Prosperi "Crypto Prediction Markets" (FULL_READ) on the foundations: Galton's 1907 ox-weight experiment (800 estimates averaging 1,197 lbs vs actual 1,198 lbs) is the canonical wisdom-of-crowds anchor. Cites Wolfers & Zitzewitz's "Interpreting Prediction Market Prices as Probabilities" (model: equilibrium = mean of belief distribution under log utility and normal risk aversion). Modeling implications: under moderate risk aversion + symmetric beliefs you get longshot bias *as an artifact*; wealth-weighted aggregation can outperform unweighted means if accurate forecasters compound wealth.
+- fil "The Art of Forecasting" (FULL_READ) frames the four channels · expert commentary, traditional polls, social media, prediction markets · on two axes: grassroots vs top-down, and dilettantism vs expertise. Polls ask "who will you vote for?" (intention); PMs ask "who do you think will win?" (expectation). The two diverge sharply when hedging is possible (a Trump-supporter who bets on Biden to hedge regret). Bloomberg added Polymarket odds to its terminal in August 2024 · adoption signal.
+- a16z podcast "Prediction Markets · Everything You Need to Know" (FULL_READ · notes only, audio is timestamped) covers: Hayek's information aggregation, when thick vs thin markets work, the oracle problem, why most internal corporate prediction markets failed, scientific-replication markets (Dreber/Pfeiffer/Almenberg studies), futarchy. The mid-discussion claim: PMs are public goods that suffer from undersupply absent subsidies, particularly for niche topics.
+- JP "Ahead of the Headlines" (FULL_READ): elaborates the chapter-opening claim that PMs are "mirrors of belief, distilled into probability." During Joe Biden's withdrawal, "the Polymarket market jumped literally the moment he posted this announcement tweet" · testable as a sub-second reaction-time experiment. Markets work because incentives reward research and punish confident wrong answers, the opposite of media incentives.
+- Bhattacharjee "How to Use Prediction Markets as a High Quality Info Source" (FULL_READ): four-tier reliability framework: (1) Category 1 · financialized econ data (CPI, S&P, weather) · high liquidity, verifiable, sophisticated participation; (2) Category 2 · political/macro outcomes (elections, recession forecasts) · somewhat financialized; (3) Category 3 · sports · moderate liquidity, less sophisticated; (4) Category 4 · true prop bets (Powell mentions, sports microevents) · low liquidity, unsophisticated, large swings. Categories 1-2 are useful; 3-4 are "harmless but should be regulated thoughtfully." Three legitimate uses: data triangulation, nowcasting, hedging. Cites Susquehanna and other quant firms as the sophisticated counterparty class operating quietly in Categories 1-2.
+
+## Notable Quotes
+
+> "Prediction market accuracy is not the wisdom of crowds. Roughly 3% of accounts drive most price discovery."
+> — *Gomez-Cram et al.*
+
+> "Only markets demand that speakers bear consequence for being wrong."
+> — *Abhitej*
+
+> "Markets work on roughly 2% of listed contracts."
+> — *Vaidik Mandloi*
+
+## Where It Matters
+
+Wisdom of crowds is the folk pitch but 2026 empirical work has destabilized it. The "informed minority" finding doesn't kill the *outcome* (PMs are accurate on a band of contracts) but kills the *mechanism* claim (it's not many small bets averaging, it's a few sharps anchored by skin in the game). For PM builders, this changes who you optimize for: sharps need execution quality, data feeds, and predictable surveillance; retail flow is the *fuel* that subsidizes them, not the source of signal. For Dekant, the distribution-market thesis is partially a play to give the informed minority *more dimensions* on which to express edge, since a single price point can only encode so much information.
+
+## Related Concepts
+
+- **Information aggregation** · the mechanism wisdom-of-crowds is supposed to instantiate
+- **Forecasting accuracy** · the testable claim
+- **Calibration / Brier score** · how the claim is operationalized
+- **Superforecasting** · the human-skill complement
+- **Adverse selection** · what informed-minority dynamics produce
+- **Distribution markets** · proposed higher-resolution alternative to binary crowd-pricing
+- **Endogeneity** · what can break wisdom-of-crowds in self-referential settings
+
+## Sources
+
+- [Predictions Are The New Expression](https://x.com/abhitejxyz/status/2047353485700825546) — Abhitej · Apr 24, 2026 [FULL_READ]
+- [Polymarket Is Not a Truth Machine](https://www.thetokendispatch.com/p/polymarket-is-not-a-truth-machine) — Mandloi · Apr 11, 2026 [FULL_READ]
+- [Prediction Market Accuracy: Crowd Wisdom Or Informed Minority?](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6617059) — Gomez-Cram, Guo, Jensen, Kung · Apr 1, 2026 [FULL_READ]
+- [How to Use Prediction Markets as a High Quality Info Source](https://uncover.substack.com/p/how-to-use-prediction-markets-as) — Isar Bhattacharjee · Mar 30, 2026 [FULL_READ]
+- [The Book That Predicted Polymarket](https://x.com/mikita_crypto/article/2029939210350645729) — Ahnianchykau · Mar 6, 2026 [FULL_READ]
+- [Ahead of the Headlines: Prediction Markets and the Collective Mind](https://jprz1321.substack.com/p/ahead-of-the-headlines-prediction) — JP · Feb 25, 2026 [FULL_READ]
+- [What If We're Capturing the Wrong Signal?](https://x.com/TideMarkets/article/2016912289941827801) — Jo · Jan 29, 2026 [FULL_READ]
+- [Prediction Markets · Everything You Need to Know](https://a16zcrypto.com/posts/podcast/prediction-markets-explained/) — Chokshi, Tabarrok, Kominers · Sep 25, 2025 [FULL_READ · podcast notes]
+- [Crypto Prediction Markets](https://dirtroads.substack.com/p/63-crypto-prediction-markets) — Luca Prosperi · Oct 11, 2024 [FULL_READ]
+- [The Art of Forecasting](https://paragraph.com/@filarm/the-art-of-forecasting) — fil · Sep 30, 2024 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/theory_concepts/yes-bias.md b/.qoder/plans/pm-plan/theory_concepts/yes-bias.md
new file mode 100644
index 0000000000..cc8c5d7081
--- /dev/null
+++ b/.qoder/plans/pm-plan/theory_concepts/yes-bias.md
@@ -0,0 +1,46 @@
+# Yes Bias
+
+**Category:** Information Theory
+**Source:** [PM Atlas — Yes Bias](https://www.pmatlas.xyz/concepts/yes-bias)
+
+---
+
+## Definition
+
+The systematic tendency for YES shares in prediction markets to be overpriced relative to true probabilities, potentially explaining what was previously attributed to favorite-longshot bias. A 2026 reframe of the canonical PM mispricing.
+
+## Key Insights
+
+- "The yes bias might not exist" · 7,292 resolved Polymarket markets and 28,793 on-chain trades. Finding: traders buy whichever token is *cheaper*, not whichever is labeled YES. The apparent bias is a compound effect: longshot preference channeled through Polymarket's "Will X happen?" question framing, which systematically assigns the longshot to the YES token. Implication: yes-bias and longshot-bias are *measurably the same effect viewed from different sides* (functionSPACE).
+- Tick-level order flow across Polymarket and Kalshi: classic favorite-longshot bias may be a *statistical artifact* masking a pervasive yes-bias driven by temporal volatility and incomplete contract-lifecycle controls. Whales are NOT the sharpest participants · heavily capitalized traders systematically bleed EV to small-order traders, likely from ideological conviction rather than informational edge (Deleep, Lee, Bai, Suresh, Dhawan · SSRN).
+- "Microstructure of Wealth Transfer": 72.1M Kalshi trades ($18.26B volume). Systematic transfer from takers to makers averaging 1.12% excess on each side. Takers disproportionately buy YES longshots, accepting returns 64pp lower than equivalent NO positions. Transfer emerged only after Kalshi's Oct 2024 legal victory attracted professional algorithmic MMs. Market efficiency varies sharply by category: finance near-efficient, entertainment/media >7pp gaps (Jonathan Becker).
+
+## Notable Quotes
+
+> "Traders buy whichever token is cheaper, not whichever is labeled YES."
+> — *functionSPACE*
+
+> "Takers disproportionately buy YES longshots, accepting returns 64 percentage points lower than equivalent NO positions."
+> — *Jonathan Becker*
+
+> "Whales are not the sharpest participants: heavily capitalized traders systematically bleed expected value to small-order traders."
+> — *Deleep et al.*
+
+## Where It Matters
+
+Yes-bias is a *measurement* finding more than a behavioral one · it tells researchers that you can't separate "people overpay for YES" from "people overpay for cheap longshots" without controlling for which side the question framing assigns to the longshot. For platforms, the practical fix is contract-wording neutrality (e.g., balanced questions like "Will Trump win OR not win?" rather than "Will Trump win?"). For traders, the renewable arbitrage is *sell YES longshots* · especially on Kalshi entertainment/media. For Dekant, yes-bias largely doesn't apply because the curve-drawing primitive has no asymmetric YES/NO labeling · but the underlying behavioral pattern (overpaying for tail outcomes) will reappear as overweighting the tails of the drawn distribution, which the L2-norm CFAMM payout function will price accordingly.
+
+## Related Concepts
+
+- **Longshot bias** · the confound; possibly identical effect under a different lens
+- **Calibration** · yes-bias is one input into calibration error
+- **Retail flow** · the source of demand for overpriced YES longshots
+- **Adverse selection / Toxic flow** · how MMs profit from yes-bias takers
+- **Market making** · fading YES longshots is the explicit strategy
+
+## Sources
+
+- [The Yes Bias Might Not Exist](https://x.com/functionspaceHQ/article/2037374271278989409) — functionSPACE · Mar 27, 2026 [FULL_READ]
+- [How Wise Is the Crowd? Bias and Edge in Prediction Markets](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=6322678) — Deleep, Lee, Bai, Suresh, Dhawan · Feb 28, 2026 [FULL_READ]
+- [The Microstructure of Wealth Transfer in Prediction Markets](https://www.jbecker.dev/research/prediction-market-microstructure) — Jonathan Becker · Jan 18, 2026 [FULL_READ]
+
diff --git a/.qoder/plans/pm-plan/viz-dlt-prediction-market-protocol-spec.md b/.qoder/plans/pm-plan/viz-dlt-prediction-market-protocol-spec.md
new file mode 100644
index 0000000000..68b935494e
--- /dev/null
+++ b/.qoder/plans/pm-plan/viz-dlt-prediction-market-protocol-spec.md
@@ -0,0 +1,678 @@
+# Onix Prediction Market — VIZ DLT Protocol Specification
+
+> Node-side (consensus-level) design for implementing the Onix Protocol as **first-class operations and ChainBase objects** on VIZ DLT — not `custom_json`, not smart contracts. Grounded in the VIZ node docs: [data-types](../viz-cpp-node/docs/protocol/data-types.md), [operations overview](../viz-cpp-node/docs/protocol/operations/overview.md), [escrow](../viz-cpp-node/docs/protocol/operations/escrow.md), [committee](../viz-cpp-node/docs/governance/committee.md), [chain-properties](../viz-cpp-node/docs/governance/chain-properties.md), [database-schema](../viz-cpp-node/docs/advanced/database-schema.md), [virtual-operations](../viz-cpp-node/docs/protocol/virtual-operations.md), [plugin-development](../viz-cpp-node/docs/development/plugin-development.md).
+>
+> Settlement model (both market types): **the AMM assigns weights (CPMM binary / LMSR multi); losers fund winners pro-rata by weight** — see [betting-rules](betting-rules-and-system-overview.md), [plan_unified_parimutuel_binary](plan_unified_parimutuel_binary.md). Front-running mitigation (instant default + opt-in batch/commit-reveal): [plan_batch_commit_reveal_betting](plan_batch_commit_reveal_betting.md).
+
+---
+
+> ## ⬛ СТАТУС РЕАЛИЗАЦИИ (HF14, на 2026-06-19)
+>
+> Аннотации ниже сверены с реально реализованным кодом (`libraries/chain/pm_evaluator.cpp`,
+> `pm_objects.hpp`, `database.cpp`, ops в `libraries/protocol/...`, плагин `prediction_market_api`,
+> snapshot) и интеграционными тестами `tests/consensus_sim/.../test_pm_lifecycle.cpp` (28 кейсов) +
+> `tests/pm/*` (LMSR-векторы, parimutuel, leverage, meta-parse).
+>
+> **Легенда пометок:**
+> - 🟢 **СДЕЛАНО** — реализовано как в спеке.
+> - 🟡 **СДЕЛАНО С ОТЛИЧИЯМИ** — реализовано, но модель/детали изменены (см. «почему»).
+> - 🔴 **НЕ СДЕЛАНО / ОТЛОЖЕНО** — особо выделено.
+> - ➕ **СВЕРХ СПЕКИ** — добавлено того, чего в этом документе нет.
+>
+> **Главное:** фазирование из §8 НЕ соблюдено — вместо «Phase 1 binary-only» реализованы
+> сразу **все три фазы**: binary CPMM + multi LMSR + batch/commit-reveal + полный Lazy Pool +
+> **подсистема leverage (маржа)**, которой в этой спеке нет вовсе. Решение принято осознанно
+> («у нас нет миграций, это не прод» — можно менять раскладку объектов свободно).
+
+## 0. Conventions
+
+- **Amounts:** `asset` (VIZ, 3 decimals) in operation params; stored internally as `share_type` (int64 satoshi). Permille fees are `uint16` basis-of-1000.
+- **Accounts:** `account_name_type` (≤16 bytes). **Stake weight** for any voting = `effective_vesting_shares = vesting_shares − delegated_vesting_shares + received_vesting_shares` (same as committee voting).
+- **Auth levels** (mirroring VIZ): financial actions → `active`; stake-weighted dispute voting → `regular` (same as `committee_vote_request`).
+- **Time:** `time_point_sec`. Deterministic deadlines processed during block application via `by_` indexes (same pattern as vesting withdrawals / escrow ratification).
+- **New op IDs** start at **64** (regular) and **96** (virtual) to avoid collision with existing IDs (0–63). New `chain_object_types` IDs appended after the existing registry.
+- **Hardfork:** all new objects/operations/chain-property version are gated behind a new hardfork (`CHAIN_HARDFORK_PM`); see [hardfork-management](../viz-cpp-node/docs/advanced/hardfork-management.md).
+
+> 🟡 **§0 СДЕЛАНО С ОТЛИЧИЯМИ.**
+> - **Op IDs «start at 64» — НЕ соблюдено (осознанно).** Операции просто **дописаны в конец**
+> `operation` static_variant; индекс варианта = консенсусный op-id. Нумерация «с 64» из спеки —
+> наследие прототипа и для VIZ неприменима (см. комментарий в `pm_operations.hpp`). Аналогично
+> виртуальные op-id 96+ из §4 не используются как отдельные номера.
+> - **Хардфорк назван `CHAIN_HARDFORK_14`** (не `CHAIN_HARDFORK_PM`).
+> - Amounts/accounts/auth/time/stake-weight — 🟢 как в спеке.
+
+---
+
+## 1. ChainBase Objects
+
+Each object is `chainbase::object` with a `shared_multi_index_container`. All financial sums are `share_type`. Indexes follow the VIZ convention (`by_id` unique + secondary indexes by account name / status / time for O(log N) lookups and bounded cron scans).
+
+### 1.1 `pm_oracle_object`
+
+Registered oracle: bonded insurance, fee policy, reputation counters.
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `id` | id_type | PK |
+| `owner` | account_name_type | Oracle account |
+| `insurance` | share_type | Locked insurance bond (VIZ) |
+| `fee_permille` | uint16 | Per-market % fee on losers' pool (≤ `pm_max_oracle_fee_permille`) |
+| `fixed_fee` | share_type | Per-market fixed fee (paid by creator at acceptance) |
+| `rules_url` | string (≤255) | Oracle policy/terms |
+| `active_since` | time_point_sec | Registration time |
+| `last_active_time` | time_point_sec | Last accept/resolve |
+| `banned_until` | time_point_sec | 0 = not banned; >0 = temp; `time_point_sec::maximum()` = permanent |
+| `markets_accepted` … `bans_received` | uint32 / share_type | The 14 reputation counters (accepted, resolved, no_contest, missed, disputes_received/lost/won/auto_closed, dispute_responses_missed, total_volume_resolved, total_insurance_slashed, avg_resolution_time, penalty_stamps) |
+
+**Indexes:** `by_id`; `by_owner` (unique); `by_status` (banned/active); `by_insurance` (for min-bond checks).
+
+> Reputation **score** is *computed on read* in the API plugin (not stored) — same approach as the prototype and as `compute_oracle_reliability_score`.
+
+> 🟡 **§1.1 СДЕЛАНО С ОТЛИЧИЕМ — модель `fixed_fee` и оракульской фи изменена (offer→quote).**
+> Спека: фикс-фи *платит создатель оракулу при acceptance* (перевод creator→oracle); оракульская
+> фи задаётся создателем. **Реализовано иначе:** (1) фикс-фи и фи **финансируются из пула
+> проигравших на сеттлменте**, не минтятся (`oracle_take = oracle_fee + oracle_fixed_paid`, см.
+> `parimutuel.hpp`), держит zero-sum [[project_pm_zero_sum]]. (2) Условия фиксируются по схеме
+> **offer→quote**: создатель на `create` объявляет *потолок* (`oracle_fee_percent`+`oracle_fixed_fee`),
+> оракул на `accept` **котирует свои фактические** (≤ потолка и ≤ медианного кэпа), они замораживаются
+> в объект рынка; self-oracle фиксирует своё при создании. Объект оракула хранит `fee_percent`/
+> `fixed_fee` как **публичный прайс-лист** (advisory). Резолв читает только замороженные поля рынка,
+> к медиане не обращается. Проверено `oracle_fixed_fee_external_vs_self` (#56, offer/quote + negative).
+> Все 14 reputation-счётчиков + `penalty_stamps` — 🟢. **Все ‰ → bp (10000=100%).**
+
+### 1.2 `pm_market_object`
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `id` | id_type | PK; referenced by bets/liquidity |
+| `creator` | account_name_type | Market maker |
+| `oracle` | account_name_type | Designated oracle |
+| `market_type` | uint8 | 0 = binary (CPMM), 1 = multi (LMSR) |
+| `outcome_count` | uint8 | 2 for binary; 3–10 for multi (≤ `pm_max_outcomes`) |
+| `url` | string (≤255) | Question / resolution criteria |
+| `status` | uint8 | −1 deleted, 0 waiting, 1 active, 2 closed, 3 resolved |
+| `payout_status` | uint8 | 0 none, 1 calculated/pending, 2 paid, 3 disputed |
+| `created_time` / `betting_expiration` / `result_expiration` | time_point_sec | Lifecycle timestamps |
+| `resolved_outcome` | int16 | −1 unresolved/no-contest; else outcome index/side |
+| **Binary CPMM** | | |
+| `reserve_a`, `reserve_b`, `k` | share_type / uint128 | CPMM pricing state (`k` = `reserve_a·reserve_b`) |
+| `a_bets_sum`, `b_bets_sum` | share_type | Stakes per side (for parimutuel + UI) |
+| **Multi LMSR** | | |
+| `lmsr_b` | share_type | Liquidity parameter (`= subsidy / ln(N)`) |
+| `lmsr_subsidy` | share_type | LP subsidy (returned unconditionally) |
+| **Common** | | |
+| `bets_sum`, `liquidity_sum` | share_type | Totals |
+| `oracle_fee_permille`, `creator_fee_permille`, `liquidity_fee_permille` | uint16 | Resolution-time fees from losers' pool |
+| `oracle_fixed_fee` | share_type | Snapshotted from oracle at acceptance |
+| `liquidity_fee_earned` | share_type | Already-paid LP fees (early withdrawals) |
+| `forfeit_pool` | share_type | Non-revealed commit penalties → winners' pool |
+| `time_penalty_type`, `time_penalty_value`, `penalty_curve_type` | uint8/uint32 | Late-bet penalty config |
+| `allow_early_resolution`, `allow_cancellation`, `allow_batch` | bool | Flags |
+| `allow_instant_bet` | bool | Default `true`. When `false`, instant `pm_place_bet` (mode=0) is rejected — every order MUST go through batch / commit-reveal (anti-MEV). Multi-outcome markets MUST keep this `true` until LMSR batch settlement is implemented. Mutual constraint: `allow_instant_bet || allow_batch` MUST be true at creation, otherwise the market would be unbettable. |
+| `endogeneity_tier` | uint8 | 1 econ-data / 2 sports / 3 political (reflexivity-risk tag) |
+| `current_epoch` | uint32 | Batch epoch counter |
+| **Dispute routing** | | |
+| `dispute_mode` | uint8 | 0 = **committee** (public stake vote); 1 = **account** (centralized resolver) |
+| `dispute_resolver` | account_name_type | Resolver account when `dispute_mode==1` (e.g. a regulator multisig); empty for committee |
+
+**Indexes:** `by_id`; `by_creator`; `by_oracle`; `by_status`; `by_betting_expiration` `(status, betting_expiration, id)`; `by_result_expiration` `(status, result_expiration, id)` (bounded cron scan for missed-resolution penalty); `by_payout_status`.
+
+> 🟡 **§1.2 СДЕЛАНО + ➕ 2 поля сверх спеки.**
+> К `pm_market_object` добавлены:
+> - ➕ `dispute_penalty_percent` (int16, −10000..+10000) — политика наказания оракула на успешном
+> диспуте: `>0` слэш % страховки ×consensus_strength; `<0` good-faith (без слэша, оракулу бонус
+> из fee); `0` нет. Задаётся в `pm_create_market`. Покрыто кейсами committee/good-faith диспутов.
+> - ➕ `metadata` (`shared_string`, **без cap**, как `custom_op`) — свободный клиентский JSON,
+> **консенсус-непрозрачный** (нода не валидирует/не интерпретирует), парсится офчейн плагином
+> (категория/теги/юрисдикции). Добавлено по запросу «отдельное json-поле metadata».
+> Остальные поля (CPMM/LMSR/fees/flags/dispute routing) — 🟢 как в спеке.
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `id` | id_type | PK |
+| `market` | pm_market_id_type | FK |
+| `outcome_index` | uint8 | 0…N−1 |
+| `label` | string (≤64) | Display label |
+| `q` | share_type | LMSR quantity |
+| `bets_sum` | share_type | Stakes on this outcome (parimutuel losers/winners base) |
+| `weight_sum` | share_type | Σ token weights on this outcome (parimutuel denominator) |
+| `bets_count` | uint32 | Count |
+
+**Indexes:** `by_id`; `by_market_outcome` `(market, outcome_index)` unique.
+
+### 1.4 `pm_bet_object`
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `id` | id_type | PK |
+| `market` | pm_market_id_type | FK |
+| `account` | account_name_type | Bettor |
+| `side` | int8 | Binary: 0/1; multi: −1 (use `outcome_index`) |
+| `outcome_index` | int16 | Multi: 0…N−1; binary: −1 |
+| `amount` | share_type | Stake |
+| `weight` | share_type | Tokens (CPMM/LMSR) — relative claim |
+| `price` | uint64 | Entry price (display, precision 1e6) |
+| `time_penalty` | uint32 | Penalty ratio at placement (precision 1e6) |
+| `mode` | uint8 | 0 instant, 1 batch, 2 commit-reveal |
+| `epoch` | uint32 | Settlement epoch (batch/reveal) |
+| `status` | uint8 | 0 active, 1 cancelled, 2 refunded, 3 resolved, 5 queued, 6 revealed-pending |
+| `resolved_amount` | share_type | Final payout (set at resolution) |
+| `created_time` | time_point_sec | Placement/submit time (time-penalty basis) |
+
+**Indexes:** `by_id`; `by_market` `(market, id)`; `by_account` `(account, id)`; `by_market_account` `(market, account, id)`; `by_market_outcome` `(market, outcome_index, id)` and/or `(market, side, id)` (for Σweight); `by_epoch` `(market, epoch, status)` (batch settlement).
+
+### 1.5 `pm_liquidity_object`
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `id` | id_type | PK |
+| `market` | pm_market_id_type | FK |
+| `provider` | account_name_type | LP (0/empty = Lazy Pool) |
+| `amount` | share_type | Principal |
+| `weight_a`, `weight_b` | share_type | Binary reserve shares (for principal-safe withdrawal) |
+| `b_share` | share_type | Multi: share of `lmsr_b`/subsidy |
+| `sec_to_expiration` | uint32 | Time-weight basis at deposit |
+| `deposit_time` | time_point_sec | |
+| `earned_fee` | share_type | Paid-out fees (early withdrawal) |
+| `status` | uint8 | 0 active, 3 resolved/closed |
+
+**Indexes:** `by_id`; `by_market` `(market, id)`; `by_provider` `(provider, id)`.
+
+### 1.6 `pm_commit_object` (commit-reveal)
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `id` | id_type | PK |
+| `market` | pm_market_id_type | FK |
+| `account` | account_name_type | Committer |
+| `commitment` | sha256 | `H(market‖account‖side/outcome‖amount‖min_tokens‖salt)` |
+| `escrow_amount` | share_type | Locked stake |
+| `no_reveal_fee_permille` | uint16 | Penalty rate **snapshotted at commit** (consensus-checked, see §3.6) |
+| `commit_time` | time_point_sec | |
+| `reveal_deadline` | time_point_sec | |
+| `status` | uint8 | 0 committed, 1 revealed, 2 forfeited |
+
+**Indexes:** `by_id`; `by_market`; `by_account`; `by_reveal_deadline` `(status, reveal_deadline, id)` (bounded forfeit cron).
+
+### 1.7 `pm_dispute_object`
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `id` | id_type | PK |
+| `market` | pm_market_id_type | FK (one active dispute per market) |
+| `disputer` | account_name_type | Filer |
+| `dispute_fee` | share_type | Escrowed fee |
+| `filed_time` | time_point_sec | |
+| `oracle_response_deadline` | time_point_sec | |
+| `dispute_mode` | uint8 | Copied from market (0 committee / 1 account) |
+| `voting_end_time` | time_point_sec | Committee mode: stake-vote tally time |
+| `auto_close_time` | time_point_sec | Anti-freeze fallback |
+| `proposed_outcome` | int16 | Disputer's claim |
+| `status` | uint8 | 0 open, 1 oracle-wrong, 2 oracle-right, 3 auto-closed |
+
+**Indexes:** `by_id`; `by_market` (unique active); `by_voting_end` `(status, voting_end_time, id)`; `by_auto_close` `(status, auto_close_time, id)`.
+
+### 1.8 `pm_dispute_vote_object` (committee mode only)
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `id` | id_type | PK |
+| `market` | pm_market_id_type | FK |
+| `voter` | account_name_type | Any SHARES holder |
+| `vote_outcome` | int16 | Outcome the voter believes correct; −1 = uphold oracle |
+| `vote_percent` | int16 | Conviction / penalty intensity (−10000…10000) |
+| `time` | time_point_sec | Last update |
+
+**Indexes:** `by_id`; `by_market_voter` `(market, voter)` unique; `by_voter` `(voter, id)`.
+
+> Voting weight is read at **tally time** as `effective_vesting_shares` (live), exactly like committee requests — votes store only the choice + percent, not a snapshotted weight.
+
+### 1.9 Lazy Liquidity Pool (optional, phase 2)
+
+- `pm_lazy_pool_object` (singleton): `total_shares`, `free_balance`, `allocated_balance`, `reward_per_share`, params.
+- `pm_lazy_deposit_object` (per user): `account`, `shares`, `reward_snapshot`, `unlock_time`.
+- `pm_lazy_allocation_object` (per market): `market`, `amount`, `recalled_amount`, `check_step`, `status`.
+
+Indexes mirror the prototype's MasterChef accounting. Deferred to a later phase; not required for v1.
+
+> 🟡 **§1.9 СДЕЛАНО ПОЛНОСТЬЮ — НЕ отложено** (вопреки «deferred to a later phase»).
+> Реализован весь Lazy Pool: deposit/shares/lock, MasterChef `reward_per_share` (1e9), late-depositor
+> fairness, unlock-consolidation, planned + emergency withdraw (со штрафом на залоченный профит),
+> авто-аллокация в рынки, **graduated early recall с тайм-гейтом** (1/10 шаг за `(result−created)/10`),
+> fault-stamp защита. Поля объектов расширены против эскиза (`earned_balance`, `original_amount`,
+> `bets_sum_at_check`, `last_check_time`, `leverage_fund_used` и т.д.).
+> **Найдены и починены консенсус-баги:** отсутствие синглтона пула (создаётся в HF14-хуке),
+> эмиссия pending-награды при withdraw, recall без тайм-гейта. См. [[project_pm_leverage]],
+> [[project_undo_all_recovery_hang]] не связан.
+>
+> ➕ **СВЕРХ СПЕКИ — подсистема Leverage (маржа), в этом документе отсутствует.**
+> Добавлен `pm_leverage_position_object` (collateral+loan, `liquidation_threshold`,
+> `cancel_value`, статусы liquidated/closed/converted) и 3 операции (см. §3). Заём фронтит
+> Lazy Pool (`leverage_fund_used`), проценты пула — в `earned_balance`. Каскадная ликвидация при
+> встречной/отменённой ставке, bad-debt поглощается пулом. Kill-switch `pm_leverage_enabled`
+> (по умолчанию **false** — выключено на мейннете до решения governance). Frozen-математика в
+> `pm/leverage.{hpp,cpp}`. Покрыто кейсами open/close, cascade-bad-debt, convert (#49/#50/#52).
+>
+> ➕ **СВЕРХ СПЕКИ — `pm_creator_ban_object`** (account → `banned_until`, `ban_count`). Введён,
+> чтобы поле `ban_creator`/`ban_creator_until` в `pm_dispute_resolve` (см. §3.9) перестало быть
+> no-op: account-mode резолвер банит создателя, `pm_create_market` отклоняет новые рынки пока бан
+> в силе. Покрыто кейсом `dispute_bans_creator_from_new_markets` (#14).
+
+---
+
+## 2. Object Type Registry additions
+
+Append to `chain_object_types.hpp` (after existing IDs): `pm_oracle`, `pm_market`, `pm_outcome`, `pm_bet`, `pm_liquidity`, `pm_commit`, `pm_dispute`, `pm_dispute_vote`, `pm_lazy_pool`, `pm_lazy_deposit`, `pm_lazy_allocation`. All registered via `db.add_core_index<...>()` during `initialize_indexes()` (these are **consensus** objects, not plugin-only).
+
+> 🟡 **§2 СДЕЛАНО — фактический реестр шире (13 типов, не 11).** Порядок APPEND-ONLY соблюдён:
+> 11 из спеки + ➕ `pm_leverage_position` + ➕ `pm_creator_ban` (дописаны в конец enum
+> `chain_object_types`, id-typedef, FC_REFLECT_ENUM, `add_core_index` в `database.cpp`). Все —
+> консенсусные core-индексы, все попадают в snapshot (§7.9).
+
+---
+
+## 3. Regular Operations (user-broadcast)
+
+All carry standard validation in `validate()` (static) + `do_apply()` (stateful, in an evaluator). `has_hardfork(CHAIN_HARDFORK_PM)` gates all of them.
+
+> 🟡 **§3 СДЕЛАНО + ➕ 5 операций сверх нумерованного списка спеки.**
+> Реализованы все операции §3.2–§3.10, **плюс** (т.к. Lazy Pool и Leverage реализованы сразу):
+> ➕ `pm_lazy_deposit`, ➕ `pm_lazy_withdraw`, ➕ `pm_leverage_open`, ➕ `pm_leverage_close`,
+> ➕ `pm_leverage_convert`. Все дописаны в конец `operation` variant (см. §0). Гейт —
+> `CHAIN_HARDFORK_14`.
+
+### 3.1 Roles → who sends what
+
+| Role | Operations |
+|------|-----------|
+| **Oracle** | `pm_oracle_register`, `pm_oracle_update`, `pm_oracle_accept_market`, `pm_resolve_market`, `pm_no_contest` |
+| **Market maker / creator** | `pm_create_market`, `pm_add_liquidity`, `pm_withdraw_liquidity` (creator is auto first LP) |
+| **User / bettor** | `pm_place_bet`, `pm_commit_bet`, `pm_reveal_bet`, `pm_cancel_bet`, `pm_transfer_position`, `pm_dispute_create`, `pm_add_liquidity` |
+| **Stakeholder (committee dispute)** | `pm_dispute_vote` (any SHARES holder) |
+| **Resolver account (centralized dispute)** | `pm_dispute_resolve` |
+
+### 3.2 `pm_oracle_register` (ID 64) — auth: `active`
+
+| Field | Type | Constraints |
+|-------|------|-------------|
+| `owner` | account_name_type | Must exist; not already registered |
+| `insurance` | asset (VIZ) | ≥ `pm_min_oracle_insurance` |
+| `fee_permille` | uint16 | ≤ `pm_max_oracle_fee_permille` |
+| `fixed_fee` | asset (VIZ) | ≥ 0 |
+| `rules_url` | string | ≤255 |
+
+Charges `pm_oracle_registration_fee` → committee fund. Locks `insurance` from `owner` balance.
+
+### 3.3 `pm_oracle_update` (ID 65) — auth: `active`
+Top-up/withdraw insurance (withdraw blocked while oracle has active accepted markets or below min), change `fee_permille`/`fixed_fee`/`rules_url`.
+
+### 3.4 `pm_create_market` (ID 66) — auth: `active`
+
+| Field | Type | Constraints |
+|-------|------|-------------|
+| `creator` | account_name_type | Payer of creation fee + initial liquidity |
+| `oracle` | account_name_type | Registered oracle (or `creator` if self-oracle) |
+| `market_type` | uint8 | 0/1 |
+| `outcomes` | vector | size 2 (binary) or 3…`pm_max_outcomes` (multi) |
+| `url` | string | resolution criteria, ≤255 |
+| `oracle_fee_permille`, `creator_fee_permille`, `liquidity_fee_permille` | uint16 | each ≤ caps; Σ ≤ `pm_max_total_fee_permille` |
+| `liquidity` | asset (VIZ) | ≥ `pm_min_liquidity` (binary reserves) / ≥ subsidy floor (multi) |
+| `betting_expiration`, `result_expiration` | time_point_sec | future; `result > betting`; ≤ `pm_max_market_duration` |
+| `time_penalty_type`, `time_penalty_value`, `penalty_curve_type` | uint8/uint32/uint8 | within bounds |
+| `allow_early_resolution`, `allow_cancellation`, `allow_batch` | bool | |
+| `allow_instant_bet` | bool | default `true`; when `false` requires `allow_batch=true` (else rejected); ignored / forced `true` for `market_type==1` (multi LMSR has no batch path yet) |
+| `endogeneity_tier` | uint8 | 1–3 |
+| `dispute_mode` | uint8 | 0 committee / 1 account |
+| `dispute_resolver` | account_name_type | required & must exist iff `dispute_mode==1`; ignored for committee |
+
+Charges `pm_market_creation_fee` → committee fund. Locks `liquidity`. Binary: `reserve_a=reserve_b=liquidity/2`, `k`. Multi: `lmsr_b=liquidity/ln(N)`, creates N `pm_outcome_object`. Creator inserted as first `pm_liquidity_object`. Self-oracle → status 1 immediately (insurance check); else status 0.
+
+### 3.5 `pm_oracle_accept_market` (ID 67) — auth: `active` of `oracle`
+`{oracle, market_id, accept}`. accept→ status 0→1, transfer `oracle_fixed_fee` creator→oracle, snapshot fee onto market, trigger lazy allocation. reject→ status 0→−1, refund liquidity to creator. Requires oracle insurance ≥ min.
+
+> 🟡 **§3.5 СДЕЛАНО С ОТЛИЧИЯМИ — accept несёт котировку оракула.**
+> - **`accept` получил 2 поля `oracle_fee_percent` + `oracle_fixed_fee`** — оракул котирует свои
+> условия (≤ потолка создателя на рынке и ≤ медианного `pm_max_oracle_fee_percent`); они
+> замораживаются в объект, status 0→1, эмитится **виртуальная `pm_market_accepted`** (см. §4).
+> Фактическая выплата оракулу — на сеттлменте из пула (§1.1). 🟢 lazy allocation, проверка страховки.
+> - **`reject` — починен консенсус-баг двойного возврата (эмиссия).** Было: креди́т `liquidity_sum`
+> И `return_liquidity()` → эмиссия. Стало: только `return_liquidity()`. Покрыто
+> `oracle_reject_refunds_liquidity_once` (#2).
+
+### 3.6 Betting
+
+**`pm_place_bet` (ID 68)** — auth: `active`
+
+| Field | Type | Notes |
+|-------|------|-------|
+| `account` | account_name_type | |
+| `market_id` | id | status==1, time0 |
+| `min_tokens` | share_type | slippage floor (0 = none) |
+| `mode` | uint8 | 0 instant (default), 1 batch |
+
+instant: apply to CPMM/LMSR immediately, mint `weight`, update reserves/q/sums. batch (requires `allow_batch`): enqueue (status 5, `epoch`), lock amount; settled by virtual `pm_batch_settle` (§4).
+
+> **`allow_instant_bet=false` enforcement.** When the market's `allow_instant_bet` flag is `false`, the chain MUST reject `mode=0` with `pm_instant_bet_disabled`. The bettor's only paths are `mode=1` (batch) or `pm_commit_bet` → `pm_reveal_bet`. Conversely the off-chain platform UX must hide the instant option and pre-select batch on these markets.
+
+> 🟡 **§3.6 СДЕЛАНО; гейт `allow_instant_bet` добавлен последним (был пропущен).** Поле хранилось,
+> но `pm_place_bet` его НЕ проверял — instant-ставки проходили при выключенном флаге (латентный
+> баг). Добавлен консенсус-гейт: `mode 0 ⇒ FC_ASSERT(allow_instant_bet)`, `mode 1 ⇒
+> FC_ASSERT(allow_batch)`. Взаимное ограничение `allow_instant_bet || allow_batch` — в `validate()`.
+> Покрыто `instant_bet_disabled_gate` (#55). 🟢 commit/reveal, cancel, time-penalty — реализованы.
+> Замечание по `batch (mode=1)`: в `pm_place_bet` он исполняется немедленно по CPMM (фронт-ран-защита
+> идёт через `commit_bet`/`reveal_bet` и эпохальный `pm_batch_settle`, не через `mode=1`).
+
+**`pm_commit_bet` (ID 69)** — auth: `active`
+
+| Field | Type | Notes |
+|-------|------|-------|
+| `account`, `market_id` | | requires `allow_batch` & `pm_commit_reveal_enabled` |
+| `commitment` | sha256 | binds account+market |
+| `escrow_amount` | asset (VIZ) | ≥ `pm_min_batch_bet` |
+| `no_reveal_fee_permille` | uint16 | **must equal `pm_commit_no_reveal_penalty_permille`** (consensus check) — the user explicitly agrees to the current chain-voted rate, which is then snapshotted on the commit |
+
+**`pm_reveal_bet` (ID 70)** — auth: `active`. `{commit_id, side/outcome, amount, salt, min_tokens}`; verify hash; refund surplus; enqueue for the next epoch boundary (§4). Unrevealed → virtual `pm_commit_forfeit`.
+
+**`pm_cancel_bet` (ID 71)** — auth: `active`. Requires `allow_cancellation`, status active/queued, betting open. Binary: reverse CPMM; multi: reverse LMSR; refund.
+
+### 3.7 Liquidity
+
+**`pm_add_liquidity` (ID 72)** / **`pm_withdraw_liquidity` (ID 73)** — auth: `active`. Binary: proportional reserve add / principal-safe reverse (block if `reserve 🟡 **§3.8 СДЕЛАНО; `pm_no_contest` переработан + починены 2 бага.** Было: штраф считался от
+> *всей страховки* (≈50%) и сжигался в forfeit_pool. Стало: `permille × dispute_fee`, распределяется
+> pro-rata возвращённым бетторам; no-contest сделан **disputable** — ставит `status=3/payout=1/
+> resolved=-1` + grace, а возврат/штраф откладываются в `settle_market` (ветка `win<0`), чтобы диспут
+> мог переопределить исход. Покрыто `oracle_no_contest_refund_and_compensate`, `dispute_overrides_no_contest`.
+> Parimutuel-сеттлмент 🟢 (zero-sum доказан, [[project_pm_zero_sum]]); починена инверсия сторон в
+> batch-сеттлменте (side 0 теперь как в `pm_place_bet`).
+
+**Settlement (both types, computed at resolve):**
+```
+losers_sum = Σ amount of non-winning bets
+winners_pool = losers_sum − oracle_fee − creator_fee − liq_fee (fees = permille × losers_sum) + forfeit_pool
+payout_i = bet_amount_i + floor(winners_pool × weight_i / total_winning_weight) − time_penalty(profit)
+LP: principal returned unconditionally + time-weighted share of liq_fee (+ no-winner bonus)
+```
+
+### 3.9 Disputes
+
+**`pm_dispute_create` (ID 76)** — auth: `active`. `{disputer, market_id, proposed_outcome}`. Preconditions: disputer has a bet; within `pm_dispute_grace`; no open dispute. Escrow `pm_dispute_fee`. Sets payout_status 3 (frozen). Creates `pm_dispute_object` copying `dispute_mode`. Sets `oracle_response_deadline`, `auto_close_time = now + pm_dispute_auto_close`. Committee mode: `voting_end_time = now + pm_dispute_vote_period`.
+
+**Committee mode (`dispute_mode==0`) — public stake-weighted vote:**
+
+**`pm_dispute_vote` (ID 77)** — auth: `regular` of `voter`. `{voter, market_id, vote_outcome, vote_percent}`. Any SHARES holder. Upsert into `pm_dispute_vote_object`. `vote_outcome` = correct outcome (or −1 to uphold oracle); `vote_percent` ∈ [−10000,10000] = conviction/penalty intensity. Tally is deterministic at `voting_end_time` via virtual `pm_dispute_finalize` (§4): stake-weighted per-outcome rshares, participation threshold `pm_dispute_approve_min_percent`, winning outcome = argmax; **penalty scaled by consensus strength** (`winning_rshares / max_rshares`); bans scaled likewise. (Algorithm = [committee-dao-and-prediction-markets.md](committee-dao-and-prediction-markets.md) §Resolution.)
+
+**Account mode (`dispute_mode==1`) — centralized resolver:**
+
+**`pm_dispute_resolve` (ID 78)** — auth: `active` of `dispute_resolver`. `{resolver, market_id, correct_outcome, penalty_amount, ban_oracle, ban_oracle_until, ban_creator, ban_creator_until}`. Only the market's `dispute_resolver` account may call. Used for **regulated markets** where a named body (e.g. a country's commission multisig) decides. Recalculate payouts / slash insurance / apply bans per fields.
+
+> **Both modes converge** on the same post-verdict recalculation path (delete pending payouts → flip/replace winner → regenerate parimutuel payouts → slash insurance/reward disputer → unfreeze). Only *who decides* differs: the whole SHARES electorate vs one configured account.
+
+> 🟡 **§3.9 СДЕЛАНО ПОЛНОСТЬЮ + починен no-op `ban_creator`.**
+> Committee-режим: stake-weighted tally (вес = `effective_vesting_shares` **+ доля в lazy-пуле,
+> сконвертированная в vesting-shares** через `get_vesting_share_price()` — чтобы DAO-участники,
+> переложившие токены в lazy-пул ради доходности, не теряли право голоса; знаменатель кворума тоже
+> включает `pool_NAV→shares`; покрыто `committee_dispute_lazy_pool_voting_weight`),
+> порог участия `pm_dispute_approve_min_percent`, winning = argmax по rshares,
+> **`consensus_strength = winning_rshares × 100% / max_rshares`** масштабирует слэш; распределение
+> fee zero-sum (uphold→оракулу; overturn→disputer fee + carve-out `fee×reward_multiplier/1000` из
+> слэша, остаток→forfeit_pool); good-faith (`dispute_penalty_percent<0`) — без слэша, оракулу
+> бонус. Account-режим — тот же канон, слэш = `penalty_amount` (без масштабирования). Auto-close
+> возвращает fee диспутеру (anti-freeze). Найдены/починены: double-refund emission, time-gate recall.
+> - 🔴→🟢 **`ban_creator`/`ban_creator_until` был no-op** (поле в операции есть, кода нет, хранилища
+> нет). Теперь применяется: upsert `pm_creator_ban_object`, проверка в `pm_create_market`.
+> Покрыто `dispute_bans_creator_from_new_markets` (#14). `ban_oracle` 🟢 работал ранее.
+
+### 3.10 `pm_transfer_position` (ID 79) — auth: `active`
+`{from, bet_id, to, amount, memo}`. Reassign all/part of a bet's `weight` to `to` (same market/outcome). No market impact. `memo`: plaintext, or `#`-prefixed ECIES-encrypted via VIZ account memo keys.
+
+---
+
+## 4. Virtual Operations (deterministic, block-generated)
+
+Generated during block application by scanning `by_`/`by_epoch` indexes with **bounded per-block work** (cap N per block; defer remainder — same pattern as committee payouts every 200 blocks and vesting withdrawals).
+
+| ID | Virtual op | Trigger | Effect |
+|----|-----------|---------|--------|
+| 96 | `pm_batch_settle` | Epoch boundary (`block % pm_batch_epoch_blocks == 0`) per market with a queue | Aggregate queued+revealed bets into one CPMM/LMSR transition per side/outcome at a uniform price; `min_tokens` gate; mint weights |
+| 97 | `pm_commit_forfeit` | `reveal_deadline` passed, unrevealed | penalty = `escrow × no_reveal_fee_permille/1000` → `market.forfeit_pool`; refund rest |
+| 98 | `pm_auto_payout` | Resolved + grace elapsed, no open dispute | Credit `resolved_amount` to winners, fees to oracle/creator, principal+fees to LPs, subsidy return |
+| 99 | `pm_dispute_finalize` | Committee `voting_end_time` | Tally stake-weighted votes; apply verdict + scaled penalty/bans; recalc payouts |
+| 100 | `pm_dispute_auto_close` | `auto_close_time` reached, unresolved | Full refund + oracle penalty + disputer fee return (anti-freeze) |
+| 101 | `pm_oracle_missed_penalty` | `result_expiration` passed, status 2 | Slash `pm_oracle_penalty_percent` of insurance; refund all; distribute bonus |
+| 102 | `pm_lazy_recall` | Lazy pool graduated-recall step | Recall idle allocation (phase 2) |
+
+> 🟢 **§4 СДЕЛАНО — единый ограниченный cron, виртуальные операции ЭМИТИРУЮТСЯ.**
+> Детерминированная логика собрана в `database::process_pm_markets()` (раз в блок, бюджет
+> `pm_processing_cap_per_block`, сканы по `by_`/`by_reveal_deadline`/`by_epoch`). **Каждый шаг
+> эмитит свою virtual_operation** (`pm_virtual_operations.hpp`, derive от `virtual_operation` →
+> попадают в `account_history`): `pm_batch_settle`, `pm_commit_forfeit`, `pm_auto_payout`,
+> `pm_dispute_finalize`, `pm_dispute_auto_close`, `pm_oracle_missed_penalty`, `pm_lazy_recall`, плюс
+> leverage `pm_leverage_liquidate`/`pm_leverage_resolve`. Нумерация id 96–102 из спеки не
+> используется (op-id = индекс в `operation` variant, append-only). ➕ **Добавлена
+> `pm_market_accepted`** — эмитится при accept оракулом и при self-oracle авто-accept (фиксирует
+> условия + market_id + флаг self), чтобы парсеры истории видели «рынок запущен».
+
+---
+
+## 5. Chain Properties (consensus parameters)
+
+Set **only** via the existing `versioned_chain_properties_update_operation` (ID 46). Add a new version **index 4 (`chain_properties_pm`)** which **inherits all `chain_properties_hf9` fields** and appends the PM fields below. Validators publish; the network applies the **per-field median** across active validators (same machinery as all other chain properties — no separate transaction, no new op).
+
+| Property | Type | Default | Constraints / notes |
+|----------|------|---------|---------------------|
+| `pm_oracle_registration_fee` | asset (VIZ) | 10.000 | → committee fund |
+| `pm_min_oracle_insurance` | asset (VIZ) | 5000.000 | bond floor |
+| `pm_market_creation_fee` | asset (VIZ) | 5.000 | → committee fund |
+| `pm_min_liquidity` | asset (VIZ) | 100.000 | binary/multi seed floor |
+| `pm_max_outcomes` | uint8 | 10 | 2–10 |
+| `pm_max_market_duration` | uint32 (s) | 31536000 | ≤ 1 year |
+| `pm_max_oracle_fee_permille` | uint16 | 50 | per-fee cap |
+| `pm_max_total_fee_permille` | uint16 | 100 | Σ(oracle+creator+liq) cap |
+| `pm_default_time_penalty_percent` | uint16 | 50 | window % of duration |
+| `pm_max_time_penalty` | uint32 | 1000000 | 100% of profit (precision 1e6) |
+| `pm_dispute_fee` | asset (VIZ) | 1000.000 | to open dispute |
+| `pm_dispute_grace_sec` | uint32 | 43200 | 12 h |
+| `pm_oracle_dispute_response_sec` | uint32 | 43200 | 12 h |
+| `pm_dispute_auto_close_sec` | uint32 | 1209600 | 14 d (anti-freeze) |
+| `pm_dispute_vote_period_sec` | uint32 | 259200 | 3 d (committee mode) |
+| `pm_dispute_approve_min_percent` | uint16 | 1000 | participation threshold (bp of total SHARES) |
+| `pm_oracle_penalty_percent` | uint16 | 500 | insurance slashed on missed deadline (bp) |
+| `pm_no_contest_penalty_permille` | uint16 | 500 | of dispute fee |
+| `pm_dispute_reward_multiplier` | uint16 | 3000 | ‰ — disputer reward carve-out |
+| `pm_batch_epoch_blocks` | uint32 | 20 | ~60 s |
+| `pm_reveal_window_blocks` | uint32 | 200 | ~10 min (liveness) |
+| `pm_commit_no_reveal_penalty_permille` | uint16 | 200 | **20%** → winners' pool; carried & checked in `pm_commit_bet` |
+| `pm_min_batch_bet` | asset (VIZ) | 1.000 | anti-dust |
+| `pm_commit_reveal_enabled` | bool | true | global kill-switch |
+| `pm_lazy_pool_*` | … | … | phase 2 (allocation %, max %, lock, recall) |
+
+> **Open governance question** (see §7.1): putting ~25 PM params into the validator median set materially enlarges what every validator must publish. Consider a smaller median-voted subset + a dedicated `pm_params_object` updated by a separate mechanism.
+
+> 🟡 **§5 СДЕЛАНО как `chain_properties_pm` (version index 4) + ➕ параметры сверх таблицы.**
+> Реализовано через существующий `versioned_chain_properties_update_operation` с per-field median
+> (как все прочие свойства). Сверх перечня добавлены:
+> - ➕ `pm_lazy_emergency_penalty_permille` (штраф emergency-вывода; раньше ошибочно переиспользовался
+> `pm_no_contest_penalty_permille` — починено).
+> - ➕ Параметры leverage: `pm_leverage_enabled` (kill-switch, **false**), `pm_leverage_pool_profit_percent`
+> (R, 10), `pm_leverage_safety_margin_percent`, `pm_leverage_max_slippage_percent`, `pm_leverage_*`
+> (fund_percent, expiration_buffer, max_per_position_bp, max_position_ratio, min_market_liquidity),
+> `pm_conversion_profit_cost_percent`, `pm_lazy_alloc_percent` и др.
+> - ➕ `pm_processing_cap_per_block` (бюджет cron из §4/§7.6).
+> **Решение по §7.1: пошли путём (a) — median-vote всех** (включая новые), отдельный `pm_params_object`
+> НЕ вводили. Поверхность параметров стала ещё больше — вопрос остаётся открытым (см. §7.1).
+>
+> **Пересмотр фи-параметров (по решению владельца):** (1) **`pm_max_total_fee_permille` УДАЛЁН** —
+> агрегатный кэп признан лишним; остаётся только `pm_max_oracle_fee_percent` (на фи оракула),
+> creator/liquidity самолимитируются, инвариант платёжеспособности «сумма ≤ 100%» — в `validate()`.
+> (2) **Все ‰ → bp (10000 = 100.00%)** как везде в VIZ: `*_fee_percent`, `pm_max_oracle_fee_percent`
+> (500), `pm_no_contest_penalty_percent` (5000), `pm_commit_no_reveal_penalty_percent` (2000),
+> `pm_lazy_emergency_penalty_percent` (5000); `pm_dispute_reward_multiplier` стал bp-множителем
+> (30000 = 3×, пол 10000=1×, потолок 100×). См. секцию фи в `chain-properties-governance.md`.
+
+---
+
+## 6. API Plugin: `prediction_market_api`
+
+Read-only JSON-RPC plugin over the **consensus** objects (mirrors `committee_api`/`database_api`; objects live in `chain`, the plugin only queries indexes). Deps: `json_rpc`, `chain`. Method name = `prediction_market_api.`. All list methods paginated (`from`, `limit ≤ 1000`). Heavy reads cached by the webserver (cleared per applied block).
+
+| Method | Params | Returns / index used |
+|--------|--------|----------------------|
+| `get_market` | `market_id` | market + outcomes (`by_id`) |
+| `list_markets` | `status, from, limit` | markets (`by_status`) |
+| `list_markets_by_oracle` / `_by_creator` | `account, from, limit` | (`by_oracle`/`by_creator`) |
+| `get_market_bets` | `market_id, from, limit` | bets (`by_market`) |
+| `get_account_positions` | `account, from, limit` | bets (`by_account`) **+ computed `expected_payout` & `expected_payout_approx`** (parimutuel estimate; exact for resolved) |
+| `get_market_weight_sums` | `market_id` | `a_weight_sum/b_weight_sum` or per-outcome `weight_sum`/`bets_sum` — feeds client `market_math.js` (no server business logic) |
+| `get_oracle` | `account` | oracle obj + **computed reliability/trust score** |
+| `list_oracles` | `from, limit, sort` | (`by_status`/`by_insurance`) |
+| `get_dispute` | `market_id` | dispute obj |
+| `get_dispute_votes` | `market_id, from, limit` | committee votes + live tally (`by_market_voter`) |
+| `get_liquidity` | `market_id` / `account` | LP positions |
+| `get_pm_chain_properties` | — | current median PM params (from `validator_schedule_object.median_props`) |
+| `get_lazy_pool` | — | pool + caller deposit (phase 2) |
+
+> The frontend already computes pricing/payouts locally ([market_math.js](../market_math.js)); the API only needs to expose **raw object state** (reserves, `q`, `lmsr_b`, per-side/outcome `bets_sum`+`weight_sum`, fees) so the client can mirror the on-chain math without trusting a server. Settlement itself is consensus (never client/server).
+
+**Real-time:** optional `set_market_applied_callback` via the webserver block callback for live odds.
+
+> 🟡 **§6 СДЕЛАНО + ➕ объединение двух плагинов в один и оффчейн-индекс метаданных.**
+> Был отдельный плагин `prediction_market_meta` — **слит в `prediction_market_api`** (один плагин
+> владеет markets/oracles/disputes/lazy/leverage/**metadata**; старый каталог удалён). Добавлены:
+> ➕ оффчейн-индекс `pm_market_meta_object` + хэндлер `applied_block`, который парсит поле `metadata`
+> рынка (категория/подкатегория/теги/`banned_jurisdictions`, неизвестные ключи игнорируются,
+> не бросает на не-JSON), TTL-прунинг; ➕ методы `get_market_meta`, `list_markets_by_category`;
+> опция `pmm-ttl-days`. Чистые хелперы в `meta_parse.hpp` юнит-тестятся (`tests/pm/meta_parse_test.cpp`).
+
+---
+
+## 7. Review — contentious / hard points to resolve before node implementation
+
+> ⬛ **§7 СТАТУС закрытия пунктов (на момент HF14-реализации):**
+> - **§7.2** 🟢 LMSR Q96 реализован в C++ (`pm/lmsr_q96.hpp`), bit-parity к замороженным векторам
+> проверяется в CI (`tests/pm/lmsr_vectors_test.cpp`).
+> - **§7.6** 🟢 единый cron с бюджетом `pm_processing_cap_per_block` (см. §4).
+> - **§7.9** 🟢 все объекты (вкл. новые `pm_leverage_position`, `pm_creator_ban`) в snapshot
+> export/import/clear, FC_REFLECT-нуты; APPEND-ONLY id стабильны.
+> - **§7.10** 🟢 floor-округление, zero-sum-инвариант доказан тестами parimutuel.
+> - **§7.12** 🟢 `MAX_PM_*` константы заданы и проверяются в эвалуаторах (`shared_string`).
+> - **🔴 Открыто:** §7.1 (поверхность параметров — стала ещё больше), §7.3 (точный in-block ordering
+> batch vs instant — реализовано, но формально не специфицировано), §7.4 (кворум/сибил/vote-buying),
+> §7.5/§7.7/§7.8 — проектные вопросы, кодом не «закрываются».
+
+### 7.1 Chain-property surface (governance design)
+~25 PM params in the validator median set is a lot for every validator to track and publish. **Options:** (a) median-vote all (consistent, but heavy + slow to change); (b) median-vote a small core (fees caps, insurance floor, dispute fee/periods) and fix the rest at hardfork; (c) a separate `pm_params_object` updated by committee vote or a 2/3 validator supermajority. **Decision needed.**
+
+### 7.2 Floating-point determinism in consensus — **RESOLVED**
+**Status:** resolved by [docs/lmsr-fixed-point-spec.md](lmsr-fixed-point-spec.md). LMSR `exp`/`ln` are now computed in Q96 fixed-point on big integers (PHP `gmp`, JS `BigInt`, future C++ `boost::multiprecision::int256_t`) using a frozen Taylor / atanh series with a fixed iteration count and a single banker's-rounding step in `exp` range reduction. The PHP and JS reference implementations produce **bit-identical** outputs across `tests/lmsr_fixed_vectors.json` (all 71 functional vectors); the C++ plugin author follows §6 of the fixed-point spec and runs the same vector file in CI. Binary CPMM remains integer-only and unchanged.
+
+### 7.3 Batch / commit-reveal as virtual ops
+Uniform-price batch settlement (`pm_batch_settle`) and per-epoch processing must be (a) deterministic, (b) bounded per block, (c) ordered vs same-block instant bets and liquidity ops. Need a defined in-block op ordering and an epoch-open vs live-curve decision (see [plan_batch_commit_reveal_betting](plan_batch_commit_reveal_betting.md) §6.1; with parimutuel it can be epoch-open snapshot for full immunity). **Spec the exact ordering + caps.**
+
+### 7.4 Committee dispute voting — sybil, quorum, cost, vote-buying
+- **Quorum:** `pm_dispute_approve_min_percent` of *total* SHARES is hard to reach for niche markets → many disputes fall through to auto-close. Tune, or use participating-stake-relative thresholds.
+- **Vote storage:** one `pm_dispute_vote_object` per voter per dispute — unbounded; cap or require min stake to vote.
+- **Plutocracy / vote-buying:** whales decide outcomes; delegation can concentrate. Acceptable? (Same property as all VIZ governance.)
+- **Front-running the tally — DECIDED: votes stay public, NO commit-reveal for disputes (will not be implemented).** A committee dispute is an **open public hearing**. The DAO's whole value proposition is resolving markets as truthfully and transparently as possible; hiding ballots behind a reveal phase would corrode that trust — the platform would lose credibility exactly where credibility matters most. Two consequences are locked in: (1) the live tally is queryable (`get_dispute_votes`); (2) **ballots are revisable** until `voting_end_time` — a repeat `pm_dispute_vote` overwrites the prior one (latest wins), so voters can change their mind as new arguments surface. The KBC/bandwagon worry is weak here because voters are **not paid** for matching the majority (influence is pure stake weight). Implemented in `pm_dispute_vote_evaluator` (modify-or-create); tested by `committee_dispute_flips_outcome`.
+- **Penalty scaling formula** (consensus_strength) and **tie-breaking** must be exactly specified (integer math).
+
+### 7.5 Insurance & escrowed funds custody
+Where do locked funds live? Options: (a) dedicated `share_type` fields on objects (oracle.insurance, dispute.dispute_fee, bet.amount) decremented from account balance — simplest, but they're invisible to generic balance tooling; (b) reuse the escrow subsystem. Recommend (a) with explicit virtual-op accounting so account_history reflects locks/unlocks.
+
+### 7.6 Time-driven processing cost
+Auto-payout, forfeit, dispute-finalize, missed-penalty, lazy-recall all scan time indexes every block. With many markets this is per-block work. **Cap per block + defer** (like committee 200-block cadence). Define caps and a fairness/ordering rule so no market starves.
+
+### 7.7 Bandwidth / energy for high-frequency betting
+Betting is `active`-auth and consumes bandwidth (∝ stake). Batch/commit-reveal add 2 ops per bet. Confirm bandwidth model tolerates active markets; consider whether PM ops need a distinct cost class (`data_operations_cost_additional_bandwidth` analog).
+
+### 7.8 Regulated (account) vs committee dispute — legal binding
+`dispute_mode==1` points to a `dispute_resolver` account (e.g. a national commission multisig). On-chain it's just an account with `active` auth over `pm_dispute_resolve`. KYC/whitelisting of that account, and which oracles/markets a regulated client surfaces, are **client-layer** concerns — the protocol stays neutral (same op set for both). Confirm this separation is acceptable and that a regulated resolver can also `ban`/slash.
+
+### 7.9 Snapshots & replay
+All new objects must be (a) included in `snapshot` plugin serialization, (b) deterministically rebuildable on replay, (c) `FC_REFLECT`-ed. New op/object IDs must be stable across the hardfork. Confirm snapshot format extension + reindex requirement.
+
+### 7.10 Precision & dust
+VIZ asset = 3 decimals; fees are permille; LMSR/CPMM produce remainders. Define rounding (floor) and dust routing (→ committee fund) consistently with §settlement. Ensure `Σ payouts ≤ pool` always holds under integer floor (conservation proven for parimutuel — keep it exact).
+
+### 7.11 Multi-outcome cancellation & LMSR reverse
+`pm_cancel_bet` for multi reverses LMSR (`lmsr_sell_return`); confirm it can't be gamed (buy/sell churn) and interacts safely with `weight_sum`/`bets_sum` accounting.
+
+### 7.12 Frozen byte-length caps for variable-length fields
+Variable-length on-chain strings (`pm_oracle.profile_url`, `pm_market.title`, `pm_outcome.label`, `pm_resolve_market.decision_url`, `pm_dispute.reason`, etc.) MUST have a byte-length cap defined as a chain constant. **This is not an anti-spam concern** — economic gating (`pm_market_creation_fee`, oracle registration cost, `active`-auth bandwidth) already prevents abusive volume. The reason is purely **consensus-mechanical**:
+
+1. **ChainBase allocation determinism.** `chainbase::allocator` strings without a hard cap force the C++ author to invent one; two nodes with different invented caps produce different `fc::raw::pack` byte length → different object hash on the same logical input → fork.
+2. **Block-size envelope.** A single op must fit inside `MAX_TRANSACTION_SIZE`/`MAX_BLOCK_SIZE` deterministically across builds.
+3. **Snapshot / replay byte-stability** (see §7.9): variable-length fields without a known maximum can disagree on field offsets after a node upgrade.
+4. **Operation cost model.** VIZ bandwidth/fee charges by serialized byte size; without a cap the fee ceiling itself is undefined.
+
+**Required output:** a frozen table of constants in §1 / §2 (illustrative starting values, freeze before hardfork):
+```
+MAX_PM_DECISION_URL_LEN = 256
+MAX_PM_PROFILE_URL_LEN = 256
+MAX_PM_DISPUTE_REASON_LEN = 1024
+MAX_PM_MARKET_TITLE_LEN = 256
+MAX_PM_OUTCOME_LABEL_LEN = 64
+MAX_PM_OUTCOMES_PER_MARKET = 16
+```
+Evaluators reject ops exceeding the cap; ChainBase fields use `fc::shared_string` with the cap enforced on insert.
+
+---
+
+## 8. Phasing recommendation
+
+1. **Phase 1 (binary-only, integer-safe):** objects, oracle, create/accept, instant bet, liquidity, resolve, both dispute modes, auto-payout, missed-penalty. No LMSR (avoids §7.2), no batch/commit-reveal. Ships a usable consensus market.
+2. **Phase 2:** deterministic LMSR (resolve §7.2) → multi markets; batch + commit-reveal (§7.3); lazy pool.
+3. **Phase 3:** shared/category liquidity, automated data oracles, advanced governance of PM params.
+
+> 🟡 **§8 ФАЗИРОВАНИЕ НЕ соблюдено — реализованы Phase 1 + 2 (и часть «расширений») разом.**
+> Поскольку HF14 ещё не на мейннете и миграций нет, дробить релиз смысла не было. В одном HF14:
+> binary CPMM **и** multi LMSR, instant **и** batch/commit-reveal, полный Lazy Pool, **+ leverage**
+> (которого в фазах нет — ближе к Phase 3/«advanced»). Из Phase 3 **не сделано (🔴):** shared/category
+> liquidity (есть только оффчейн-категоризация через `metadata`), автоматические data-оракулы.
+> Верификация: `consensus_sim` 28 кейсов + `tests/pm/*` — всё зелёное.
+
+---
+
+## 9. ⬛ Сводка отличий реализации от спеки (приложение)
+
+**Изменённые модели (🟡):**
+1. Op-id не «с 64», а append-only в `operation` variant; хардфорк = `CHAIN_HARDFORK_14`.
+2. Оракульская фи и `oracle_fixed_fee` — модель **offer→quote**: создатель предлагает потолок на
+ create, оракул котирует фактическое на accept (≤ потолка, ≤ медианы), замораживается в рынок;
+ выплата из пула на сеттлменте (zero-sum). Резолв к медиане не обращается.
+3. Виртуальные операции §4 эмитируются (через `process_pm_markets`), но без нумерации id 96–102;
+ ➕ добавлена `pm_market_accepted` (announce запуска рынка).
+4. `pm_no_contest` штраф = % от dispute_fee (не от всей страховки) и сделан disputable.
+5. **Фи-параметры:** удалён `pm_max_total_fee_permille` (агрегатный кэп); все ‰ → bp (10000=100%);
+ `pm_dispute_reward_multiplier` — bp-множитель (10000=1×, ≤100×).
+
+**Добавлено сверх спеки (➕):**
+5. Подсистема **leverage**: объект `pm_leverage_position` + 3 операции + параметры + kill-switch.
+6. Объект **`pm_creator_ban`** (чтобы `ban_creator` перестал быть no-op).
+7. Поля рынка **`dispute_penalty_percent`** и **`metadata`** (консенсус-непрозрачный JSON).
+8. Параметры: `pm_lazy_emergency_penalty_permille`, `pm_processing_cap_per_block`, весь набор leverage.
+9. Объединение `prediction_market_meta` → `prediction_market_api` + оффчейн-индекс метаданных.
+
+**Реализовано вопреки «отложено/phase 2» (🟢):** весь Lazy Pool (§1.9), LMSR multi, batch/commit-reveal.
+
+**Закрытые консенсус-баги (найдены тестами):** double-refund при reject (эмиссия); отсутствие
+синглтона Lazy Pool; recall без тайм-гейта; эмиссия pending-награды при lazy-withdraw; инверсия
+сторон в batch-сеттлменте; no-op `allow_instant_bet`; no-op `ban_creator`; неверная база/сжигание
+штрафа no-contest; emergency-штраф брал не тот параметр.
+
+**Не сделано / отложено (🔴):** отдельные virtual-ops в `account_history`; shared/category on-chain
+liquidity; автоматические data-оракулы; формальная спецификация §7.3 ordering; открытые governance-
+вопросы §7.1/§7.4/§7.5/§7.7/§7.8.
+
+---
+
+🇷🇺 По запросу сделаю русскую версию (`-ru`) и/или раскрою любой раздел (точные C++ сигнатуры объектов/эвалуаторов, бинарную сериализацию операций, или детальный алгоритм tally для committee-диспута).
diff --git a/@l10n/ru/docs/advanced/hardfork-management.md b/@l10n/ru/docs/advanced/hardfork-management.md
index c9298c8edf..0c9ffe8402 100644
--- a/@l10n/ru/docs/advanced/hardfork-management.md
+++ b/@l10n/ru/docs/advanced/hardfork-management.md
@@ -48,6 +48,8 @@ VIZ Ledger координирует обновления протокола че
| 10 | Модель инфляции |
| 11 | Изменения модели эмиссии |
| 12 | Аварийное восстановление консенсуса (см. ниже) |
+| 13 | Длина эпохи распределения (`chain_properties_hf13`) |
+| 14 | Прогнозные рынки (Onix): 18 операций + 7 виртуальных, CPMM/LMSR, паримутюэль-расчёт, оракулы, споры, commit-reveal, lazy-пул; chain properties v5 |
---
diff --git a/@l10n/ru/docs/governance/chain-properties.md b/@l10n/ru/docs/governance/chain-properties.md
index 0358a42f28..1c2b1d2227 100644
--- a/@l10n/ru/docs/governance/chain-properties.md
+++ b/@l10n/ru/docs/governance/chain-properties.md
@@ -122,8 +122,28 @@
| `chain_properties_hf4` | 1 | HF4 | inflation_validator_percent, inflation_ratio_committee_vs_reward_fund, inflation_recalc_period |
| `chain_properties_hf6` | 2 | HF6 | data_operations_cost_additional_bandwidth, validator_miss_penalty_percent, validator_miss_penalty_duration |
| `chain_properties_hf9` | 3 | HF9 | create_invite_min_balance, committee_create_request_fee, create_paid_subscription_fee, account_on_sale_fee, subaccount_on_sale_fee, validator_declaration_fee, withdraw_intervals |
+| `chain_properties_hf13` | 4 | HF13 | distribution_epoch_length |
+| `chain_properties_pm` | 5 | HF14 | ~30 параметров прогнозных рынков + kill-switch `pm_commit_reveal_enabled`, `pm_lazy_pool_enabled` |
-Для всех новых публикаций параметров валидатора используйте индекс версии 3 (`chain_properties_hf9`).
+Для всех новых публикаций параметров валидатора используйте индекс версии **5** (`chain_properties_pm`). Индекс 4 — `chain_properties_hf13` (`distribution_epoch_length`).
+
+### Параметры прогнозных рынков (v5, HF14) {#pm-parameters}
+
+Все медиан-голосуемые; см. [Операции прогнозных рынков](../protocol/operations/prediction-markets.md).
+
+Все проценты PM — в bp (10000 = 100.00%), как прочие `*_percent`; промилле (‰) нигде нет.
+
+- **Оракул:** `pm_min_oracle_insurance`, `pm_max_oracle_fee_percent` (**единственный** governance-кэп на фи — на % оракула), `pm_oracle_registration_fee`, `pm_oracle_penalty_percent`, `pm_oracle_dispute_response_sec`, `pm_oracle_accept_window_sec` (по умолчанию 3600 = 1 ч — названный оракул должен принять или отклонить пендинг-рынок в течение этого окна; по истечении крон возвращает создателю сид-ликвидность, но **не** комиссию за создание, и аннулирует рынок → `pm_market_expired`).
+- **Риск / покрытие** *(процент от объёма ставок рынка, 100 = 1.0×):* `pm_listing_min_coverage_percent` (250 = 2.5×) — рынки, чья страховка оракула покрывает меньше этой доли их объёма, скрыты из каталога `list_markets` по умолчанию (показываются через `show_risky`); `pm_betting_min_coverage_percent` (150 = 1.5×) — рекомендательный порог, публикуемый для клиентов, чтобы требовать явного подтверждения риска перед ставкой (не форсится on-chain; должен быть `≤ pm_listing_min_coverage_percent`).
+- **Рынок:** `pm_min_liquidity`, `pm_market_creation_fee`, `pm_max_outcomes`, `pm_max_market_duration`. *(Агрегатного кэпа фи нет; creator/liquidity-фи без кэпа, самолимитируются; статический инвариант `сумма ≤ 100%`.)*
+- **Batch / commit-reveal:** `pm_batch_epoch_blocks`, `pm_reveal_window_blocks`, `pm_min_batch_bet`, `pm_commit_no_reveal_penalty_percent`, `pm_commit_reveal_enabled`.
+- **Споры:** `pm_dispute_fee`, `pm_dispute_grace_sec`, `pm_dispute_vote_period_sec`, `pm_dispute_auto_close_sec`, `pm_dispute_approve_min_percent`, `pm_no_contest_penalty_percent`, `pm_dispute_reward_multiplier` (bp-множитель, 10000 = 1×).
+- **Time penalty:** `pm_default_time_penalty_percent`, `pm_max_time_penalty`.
+- **Lazy-пул:** `pm_lazy_pool_enabled`, `pm_lazy_alloc_percent`, `pm_lazy_max_total_alloc_percent`, `pm_lazy_recall_step_percent`, `pm_lazy_lock_sec`, `pm_lazy_emergency_penalty_percent`, `pm_lazy_min_liquidity_fee_percent` (по умолчанию 200 = 2% — пул отказывается со-предоставлять ликвидность рынку, чей `liquidity_fee_percent` ниже этого порога вознаграждения).
+- **Плечо (опц.):** `pm_leverage_enabled`, `pm_leverage_fund_percent`, `pm_leverage_max_per_position_bp`, `pm_leverage_max_position_ratio_percent`, `pm_leverage_min_market_liquidity`, `pm_leverage_safety_margin_percent`, `pm_leverage_max_slippage_percent`, `pm_leverage_m_factor_percent`, `pm_leverage_pool_profit_percent`, `pm_leverage_expiration_buffer_sec`, `pm_conversion_profit_cost_percent`.
+- **Справедливость:** `pm_processing_cap_per_block`.
+
+Три флага `*_enabled` (`pm_commit_reveal_enabled`, `pm_lazy_pool_enabled`, `pm_leverage_enabled`) — живые kill-switch: медиана валидаторов может отключить commit-reveal, lazy-пул или плечо без нового хардфорка.
---
diff --git a/@l10n/ru/docs/node/configuration.md b/@l10n/ru/docs/node/configuration.md
index 47a190c954..8bdcfa7d15 100644
--- a/@l10n/ru/docs/node/configuration.md
+++ b/@l10n/ru/docs/node/configuration.md
@@ -158,6 +158,11 @@ skip-virtual-ops = false
# Разрешить производство при устаревшей цепочке (только для разработки/тестовой сети)
enable-stale-production = false
+# Отключить обнаружение minority fork (ТОЛЬКО для тестовой сети/форка одного оператора).
+# В отличие от enable-stale-production, никогда не сбрасывается автоматически при
+# здоровом участии. Никогда не включайте в реальной публичной сети.
+disable-minority-fork-detection = false
+
# Минимальный процент участия для производства блоков (0–99)
required-participation = 33
@@ -203,4 +208,4 @@ logger.p2p.appenders = p2p
| `plugins/chain/plugin.hpp` | `shared-file-size`, `min-free-shared-file-size`, `inc-shared-file-size`, `block-num-check-free-size`, `single-write-thread`, `enable-plugins-on-push-transaction`, `read-wait-micro`, `max-read-wait-retries`, `write-wait-micro`, `max-write-wait-retries`, `skip-virtual-ops`, `clear-votes-before-block`, `track-account-range`, `history-whitelist-ops`, `history-blacklist-ops`, `history-start-block` |
| `plugins/p2p/p2p_plugin.hpp` | `p2p-endpoint`, `p2p-max-connections`, `p2p-seed-node`, `checkpoint` |
| `plugins/webserver/webserver_plugin.hpp` | `webserver-http-endpoint`, `webserver-ws-endpoint`, `webserver-thread-pool-size` |
-| `plugins/validator/validator.hpp` | `enable-stale-production`, `required-participation`, `validator`, `private-key`, `emergency-private-key`, `fork-collision-timeout-blocks`, `ntp-server`, `ntp-request-interval`, `debug-block-production` |
+| `plugins/validator/validator.hpp` | `enable-stale-production`, `disable-minority-fork-detection`, `required-participation`, `validator`, `private-key`, `emergency-private-key`, `fork-collision-timeout-blocks`, `ntp-server`, `ntp-request-interval`, `debug-block-production` |
diff --git a/@l10n/ru/docs/node/validator-node.md b/@l10n/ru/docs/node/validator-node.md
index 78036c09d2..63fb59dcc0 100644
--- a/@l10n/ru/docs/node/validator-node.md
+++ b/@l10n/ru/docs/node/validator-node.md
@@ -164,6 +164,9 @@ docker run -d \
### Обнаружение minority fork
Если форк-база данных узла показывает 21+ последовательных блоков только от собственных валидаторов, узел автоматически откатывается к LIB и выполняет повторную синхронизацию. Это позволяет обнаружить сетевую изоляцию.
+> [!WARNING] Форки одного оператора
+> На тестовой сети или форке основной сети, где **один оператор контролирует все валидаторы**, «21 блок подряд от нас» — это нормальное здоровое состояние, поэтому детектор бесконечно откатывается к LIB. `enable-stale-production = true` здесь **не помогает**: при участии ≥33% это переопределение автоматически снимается на каждом блоке. Используйте вместо него `disable-minority-fork-detection = true` — он обходит и стандартный, и DLT-путь обнаружения и никогда не сбрасывается автоматически. **Никогда не включайте его в реальной публичной сети** — это убирает защиту от изоляции.
+
### Watchdog производства
Если в течение 180 секунд (60 с для экстренного мастера) при активном `should_be_producing` не был произведён ни один блок, watchdog автоматически сбрасывает зависшие флаги (`minority_fork_recovering`, нагон P2P, синхронизация цепочки) и пытается возобновить производство.
diff --git a/@l10n/ru/docs/plugins/prediction-market-api.md b/@l10n/ru/docs/plugins/prediction-market-api.md
new file mode 100644
index 0000000000..7fa3ac25aa
--- /dev/null
+++ b/@l10n/ru/docs/plugins/prediction-market-api.md
@@ -0,0 +1,192 @@
+# Плагин `prediction_market_api`
+
+Read-only JSON-RPC доступ к состоянию прогнозных рынков HF14 (рынки, ставки, оракулы, ликвидность, споры, lazy-пул, chain properties v5). Плагин возвращает сырые консенсус-объекты `pm_*` напрямую плюс несколько вычисляемых DTO.
+
+**Включение:** добавить `prediction_market_api` в список плагинов узла (в `vizd` зарегистрирован по умолчанию). Зависит от `chain` + `json_rpc`. Все list-методы пагинируются через `from` (пропуск) и `limit` (`≤ 1000`).
+
+## Методы
+
+### Рынки
+
+| Метод | Аргументы | Возврат |
+|-------|-----------|---------|
+| `get_market` | `market_id` | `pm_market_object` |
+| `list_markets` | `status, from, limit, [show_risky]` | `pm_market_object[]` |
+| `list_markets_by_oracle` | `oracle, from, limit` | `pm_market_object[]` |
+| `list_markets_by_creator` | `creator, from, limit` | `pm_market_object[]` |
+| `get_market_outcomes` | `market_id` | `pm_outcome_object[]` |
+| `get_market_weight_sums` | `market_id` | `pm_market_weight_sums` (вычисляемый) |
+| `get_market_bets` | `market_id, from, limit` | `pm_bet_object[]` |
+| `get_market_liquidity` | `market_id, from, limit` | `pm_liquidity_object[]` |
+| `get_market_full` | `market_id, [account]` | `pm_market_full` (вычисляемый) |
+
+`status` для `list_markets`: `-1` удалён, `0` ожидание, `1` активен, `2` закрыт, `3` разрешён. По
+умолчанию `list_markets` скрывает недострахованные рынки (страховка оракула < **2.5×** объёма ставок);
+`show_risky = true` показывает их (рынки только скрываются, ставки on-chain всегда разрешены).
+
+`get_market_full` — **обогащённое представление за один вызов** для экрана деталей рынка: возвращает рынок + исходы + суммы весов + оракула (с надёжностью) + распарсенные метаданные и — если передан необязательный `account` — ставки, плечевые позиции и LP этого аккаунта **на этом рынке**. Экономит тонкому клиенту несколько round-trip'ов.
+
+### Метаданные рынка (парсятся off-chain)
+
+Каждый рынок несёт свободную, консенсус-непрозрачную JSON-строку `metadata`. Плагин парсит индексируемые
+ключи (категория / подкатегория / теги / запрещённые юрисдикции) в `pm_market_meta_object` — **только для
+отображения/индексации, не консенсус**.
+
+| Метод | Аргументы | Возврат |
+|-------|-----------|---------|
+| `get_market_meta` | `market_id` | `pm_market_meta_object` (или ошибка, если нет) |
+| `list_markets_by_category` | `category, from, limit, [jurisdiction], [subcategory], [tag], [sort]` | `pm_market_meta_object[]` |
+| `get_market_categories` | — | `pm_market_categories` (вычисляемый) |
+
+`list_markets_by_category` исключает рынки, чьи `banned_jurisdictions` содержат необязательный ISO-код
+`jurisdiction` (регулируемый клиент передаёт свою юрисдикцию, чтобы получить только допустимые рынки).
+Необязательные `subcategory` (точное совпадение) и `tag` (членство в CSV) сужают набор; `sort` ∈ `newest`
+(id рынка по убыв., по умолч.) · `oldest` · `volume` (`bets_sum` по убыв.) · `expiration`
+(`betting_expiration` по возр.). `get_market_categories` возвращает живую таксономию — счётчики по
+категориям / подкатегориям плюс топ-20 горячих тегов (`jurisdiction-*` исключены) — агрегированную по
+проиндексированным сейчас рынкам, чтобы браузинг-UI строил свои фильтр-чипы без захардкоженной таксономии.
+Объект: `market`, `category`, `subcategory`, `tags` (через запятую), `banned_jurisdictions` (ISO через
+запятую; пусто = разрешено везде), `expiry` (пруна после закрытия окна спора + TTL).
+
+### Позиции и оракулы
+
+| Метод | Аргументы | Возврат |
+|-------|-----------|---------|
+| `get_account_positions` | `account, from, limit` | `pm_position[]` (ставка + `expected_payout`) |
+| `get_account_leverage_positions` | `account, from, limit` | `pm_leverage_position_object[]` |
+| `get_market_leverage_positions` | `market_id, from, limit` | `pm_leverage_position_object[]` |
+| `get_creator_ban` | `account` | `pm_creator_ban_object` (или ошибка, если нет) |
+| `get_oracle` | `owner` | `pm_oracle` (объект + `reliability_score`) |
+| `list_oracles` | `from, limit` | `pm_oracle_object[]` |
+
+### Превью плеча (Boost)
+
+Read-only котировки, вызывающие **ту же внутриузловую математику маржи**, что и эвалуаторы, так что превью совпадает с тем, что вычислила бы соответствующая операция `pm_leverage_*` на головном блоке. Это неконсенсусные оценки (резервы двигаются между чтением и бродкастом — всегда отправляйте on-chain защиту от проскальзывания).
+
+| Метод | Аргументы | Возврат |
+|-------|-----------|---------|
+| `get_leverage_quote` | `market_id, outcome_index, collateral` | `pm_leverage_quote` (вычисляемый) |
+| `get_leverage_close_preview` | `position_id` | `pm_leverage_close_preview` (вычисляемый) |
+| `get_leverage_convert_preview` | `position_id` | `pm_leverage_convert_preview` (вычисляемый) |
+
+`get_leverage_quote` зеркалит `pm_leverage_open`: возвращает максимальный платёжеспособный заём и итоговое максимальное плечо, кэпы пула/позиции, до 12 стопов слайдера (каждый с токенами, порогом, текущей и худшей стоимостью отмены) и — когда плечо невозможно — `available = false` со списком `failed_constraints[]`. `get_leverage_close_preview` / `get_leverage_convert_preview` зеркалят `pm_leverage_close` / `pm_leverage_convert` при текущих резервах (стоимость отмены, обязательство пула, что получает беттер, закрываемость/конвертируемость и комиссия конвертации при текущей медиане `pm_conversion_profit_cost_percent`).
+
+> Выплата каждому беттору — виртуальная операция `pm_payout` (стейк, side/outcome, итог; `0` при
+> проигрыше); закрытие плечевой позиции — `pm_leverage_resolve` (`outcome_index`, `won`, `leverage`).
+> Обе видны в `account_history`; сами объекты позиций — через методы выше.
+
+### Споры, lazy-пул, governance
+
+| Метод | Аргументы | Возврат |
+|-------|-----------|---------|
+| `get_dispute` | `market_id` | `pm_dispute_object` |
+| `get_dispute_votes` | `market_id` | `pm_dispute_votes` (голоса + живой подсчёт) |
+| `get_lazy_pool` | — | `pm_lazy_pool_object` |
+| `get_lazy_deposit` | `account` | `pm_lazy_deposit_object` |
+| `get_lazy_allocations` | `from, limit` | `pm_lazy_allocation_object[]` |
+| `get_market_lazy_allocation` | `market_id` | `pm_lazy_allocation_object` (или ошибка, если нет) |
+| `get_pm_chain_properties` | — | `chain_properties_pm` (медиана, v5) |
+
+`get_lazy_allocations` перечисляет записи аллокаций lazy-пула по рынкам (для дашборда пула); `get_market_lazy_allocation` берёт запись для конкретного рынка. Штрафные штампы оракула отдельного метода не требуют — они идут на `pm_oracle_object` (`penalty_stamps`, `last_penalty_stamp_time`) через `get_oracle`.
+
+### Графики — kline / история весов
+
+Тайм-серия для построения графика изменения веса каждого исхода. Плагин добавляет точку **каждый раз, когда веса исходов рынка меняются** — ставка, отмена, ликвидация, batch-settle, открытие плеча или расчёт плеча — как таймстампированный снимок паримутюэль-веса (сумма ставок) по каждому исходу. Это **неконсенсусное** состояние плагина (хранится в chainbase, undo/redo-безопасно, не входит в хеш состояния); история копится с момента первого включения плагина на узле.
+
+**Хранение:** kline-история пруна́ется **вместе с метаданными рынка**, по тому же расписанию — `result_expiration` + grace спора + `pmm-ttl-days` (по умолч. 7). Полный график рынка доступен на протяжении его жизни и в окне хранения после расчёта, затем оба индекса очищаются (для очень длинной истории — частями за несколько блоков), чтобы хранилище узла оставалось ограниченным.
+
+| Метод | Аргументы | Возврат |
+|-------|-----------|---------|
+| `get_market_kline` | `market_id, [from], [limit]` | `pm_kline[]` (по возрастанию `seq`) |
+
+Пагинация — **отступ от новейших** (намеренно простая для тонких клиентов): `from` — сколько **новейших** точек пропустить, `limit ≤ 1000` — размер страницы.
+- `(market_id, 0, 1000)` → последние ≤ 1000 изменений.
+- `(market_id, 1000, 1000)` → предыдущие 1000 (страницей раньше) — повторяй с `from += 1000`, чтобы дозагружать более старую историю.
+
+График: x = `timestamp` (unix-секунды), по одной линии на исход `i` с y = `weights[i]` (или нормированно `weights[i] / Σweights` — вменённая вероятность).
+
+## Вычисляемые DTO
+
+- **`pm_position`** — ставка + `expected_payout` (выплата, если сторона победит, или реализованная после расчёта; байт-в-байт повторяет `settle_market`), `market_status`, `resolved_outcome`.
+- **`pm_oracle`** — объект оракула + `reliability_score` (bp `[0..10000]`, неконсенсусная эвристика: смесь доли успешных разрешений и доли выигранных споров минус штраф за баны).
+- **`pm_market_weight_sums`** — `bets_sum`/`weight_sum` по сторонам/исходам (веса считаются сканом ставок, т.к. не хранятся).
+- **`pm_kline`** — одна точка графика: `seq` (uint32, 0-based, монотонный индекс изменения по рынку), `timestamp` (unix-секунды, x), `reason` (uint8: 0 ставка, 1 отмена, 2 ликвидация, 3 batch settle, 4 открытие плеча, 5 расчёт плеча), `bets_sum` (всего поставлено), `weights[]` (вес по каждому исходу, y; индекс = outcome_index).
+- **`pm_dispute_votes`** — голоса + подсчёт finalize. Старые поля (вес = `|vote_percent|`, не стейк): `uphold_weight`/`challenge_weight`/`total_weight`, `challenger_leads` (≥ `pm_dispute_approve_min_percent`), `proposed_outcome`. **Точная stake-взвешенная проекция (зеркалит `pm_dispute_finalize`; все `*_shares` в vesting-shares = `effective_vesting_shares` + стейк lazy-пула→shares):** `participation_shares` (Σ веса проголосовавших), `electorate_shares` (`total_vesting_shares` + NAV пула→shares), `quorum_required_shares`, `quorum_percent_bp` (кворум в bp, 10000 = 100.00%), `quorum_reached` (bool), `oracle_defense_shares`/`change_shares`, `outcome_change_shares[]` (по исходам), `expected_uphold` (останется ли решение оракула), `expected_outcome` (какой исход будет выставлен при резолюции сейчас), `expected_consensus_strength_bp`. Проекция совпадает с тем, что крон применит на `voting_end_time` при текущих голосах (голоса изменяемы до этого момента).
+
+**`pm_market_full`** — обогащённое представление рынка за один вызов (`oracle`/`meta` = `null`, когда
+отсутствуют; массивы `my_*` пусты, если не передан аргумент `account`):
+```
+{ market: pm_market_object,
+ outcomes: pm_outcome_object[], // пусто для бинарных рынков
+ weight_sums: pm_market_weight_sums,
+ oracle: pm_oracle | null,
+ meta: pm_market_meta_object | null,
+ my_positions: pm_position[], // ставки аккаунта на ЭТОМ рынке
+ my_leverage_positions: pm_leverage_position_object[],
+ my_liquidity: pm_liquidity_object[] }
+```
+
+**`pm_leverage_quote`** — превью открытия плеча (из `pm::leverage::*`, та же математика, что и в эвалуаторе):
+```
+{ available: bool, outcome_index, collateral,
+ max_loan, max_leverage_x100, // 100 = 1.00×
+ pool_free_amount, fund_available, per_position_cap, market_position_cap,
+ pool_profit_percent, safety_margin_percent, max_slippage_percent, m_factor_percent,
+ expiration_buffer_sec, auto_close_time, // betting_expiration − buffer
+ stops: [ { leverage_x100, loan, total_bet, expected_tokens, pool_profit,
+ liquidation_threshold, current_cancel_value, worst_case_cancel_value } ],
+ failed_constraints: [ { constraint, reason } ] } // заполнено, когда !available
+```
+**`pm_leverage_close_preview`** — `{ position_id, outcome_index, cancel_value, pool_obligation, bettor_receives, collateral, loan, pool_profit_charge, closeable: bool, loss_vs_collateral, loss_percent_bp }`.
+**`pm_leverage_convert_preview`** — `{ position_id, outcome_index, cancel_value, pool_obligation, current_profit, conversion_profit_cost_percent, conversion_fee, total_user_payment, convertible: bool }`.
+
+**`pm_market_categories`** — браузинг-таксономия с живыми счётчиками:
+```
+{ categories: [ { category, count, subcategories: [ { subcategory, count } ] } ], // сортировка по count убыв.
+ hot_tags: [ { tag, count } ] } // топ-20 (jurisdiction-* исключены)
+```
+
+## Пример
+
+Последние 1000 точек графика для рынка `42`, затем предыдущие 1000:
+```bash
+# новейшая страница
+curl -s --data '{"jsonrpc":"2.0","id":1,"method":"call",
+ "params":["prediction_market_api","get_market_kline",[42,0,1000]]}' http://127.0.0.1:8090
+# страницей старше
+curl -s --data '{"jsonrpc":"2.0","id":1,"method":"call",
+ "params":["prediction_market_api","get_market_kline",[42,1000,1000]]}' http://127.0.0.1:8090
+```
+
+Тонкий клиент (дозагрузка старой истории при прокрутке назад) — каждая точка в серию `{ x: unixtime, y: weight }` по исходам:
+```js
+async function call(method, params) {
+ const r = await fetch('http://127.0.0.1:8090', { method: 'POST',
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'call',
+ params: ['prediction_market_api', method, params] }) });
+ return (await r.json()).result;
+}
+
+// Страницы по 1000 от новейших назад, пока не наберём `want` точек (или не кончится история).
+async function loadKline(marketId, want = 3000) {
+ const points = [];
+ for (let from = 0; points.length < want; from += 1000) {
+ const page = await call('get_market_kline', [marketId, from, 1000]);
+ if (!page.length) break; // дошли до начала истории
+ points.unshift(...page); // страницы по возрастанию; старые — в начало
+ if (page.length < 1000) break;
+ }
+ return points;
+}
+
+// По одной серии {x,y} на исход — напрямую в любую библиотеку графиков.
+function toSeries(points, outcomeCount) {
+ const series = Array.from({ length: outcomeCount }, () => []);
+ for (const p of points)
+ for (let i = 0; i < outcomeCount; i++)
+ series[i].push({ x: p.timestamp, y: Number(p.weights[i]) });
+ return series;
+}
+```
+
+См. [Операции прогнозных рынков](../protocol/operations/prediction-markets.md) и [Chain Properties](../governance/chain-properties.md#pm-parameters).
diff --git a/@l10n/ru/docs/plugins/validator.md b/@l10n/ru/docs/plugins/validator.md
index 7666743682..566b700431 100644
--- a/@l10n/ru/docs/plugins/validator.md
+++ b/@l10n/ru/docs/plugins/validator.md
@@ -24,6 +24,7 @@ chain::plugin, p2p::p2p_plugin, snapshot::snapshot_plugin
| `private-key` | — | WIF-ключ(и) для подписи; может повторяться |
| `emergency-private-key` | — | WIF-ключ для экстренного консенсуса; автоматически добавляет `CHAIN_EMERGENCY_VALIDATOR_ACCOUNT` в набор валидаторов |
| `enable-stale-production` | `false` | Обход проверок участия и синхронизации (только для тестовой сети / восстановления сети) |
+| `disable-minority-fork-detection` | `false` | Полностью пропустить обнаружение minority fork (только для тестовой сети/форка одного оператора). Никогда не сбрасывается автоматически при здоровом участии — см. [Обнаружение minority fork](#обнаружение-minority-fork) |
| `required-participation` | `3300` | Минимальное участие валидаторов в **базисных пунктах** (3300 = 33%) |
| `fork-collision-timeout-blocks` | `21` | Количество последовательных отсрочек при коллизии форков перед принудительным производством (один полный раунд валидаторов) |
@@ -116,7 +117,8 @@ chain::plugin, p2p::p2p_plugin, snapshot::snapshot_plugin
Перед каждой попыткой производства (после проверок безопасности HF12) плагин просматривает последние 21 блок в `fork_db`. Если все 21 были произведены собственными настроенными валидаторами узла — узел изолирован на minority fork.
- **Действие по умолчанию:** Вызов `p2p().resync_from_lib()` — откат блоков к LIB, сброс fork DB, повторная инициация синхронизации P2P, переподключение к начальным узлам. Возвращает `minority_fork`.
-- **С `enable-stale-production=true`:** Запись предупреждения, продолжение производства.
+- **С `enable-stale-production=true`:** Запись предупреждения, продолжение производства. **Примечание:** при участии ≥33% это переопределение автоматически снимается на каждом блоке, поэтому на форке одного оператора оно *не* останавливает детектор — используйте `disable-minority-fork-detection`.
+- **С `disable-minority-fork-detection=true`:** И стандартный, и DLT-путь обнаружения полностью пропускаются, а флаг никогда не сбрасывается автоматически. Для тестовой сети/форков одного оператора, где «21 блок подряд от нас» — это нормальное устойчивое состояние. **Никогда не включайте в реальной публичной сети** — это убирает защиту от изоляции.
- **Пропускается при:** Активном экстренном консенсусе (блоки комитета всегда соответствовали бы нашему настроенному набору). В экстренном режиме вместо него используется специфичная для DLT проверка изоляции ведомого.
---
@@ -217,7 +219,7 @@ validator[skip_flags=0x0 catching_up=0 head=#79881136 last_prod=45s_ago minority
| `no_private_key` | В конфиге отсутствует `private-key` для ключа подписи, зарегистрированного в блокчейне |
| `low_participation` | Участие сети < 33%; проверьте подключение к пирам или установите `enable-stale-production=true` |
| `fork_collision` | Конкурирующий блок на следующей высоте; ждать разрешения по весу голосов или таймаута 21 отсрочки |
-| `minority_fork` | Изолирован; плагин автоматически пересинхронизируется с LIB |
+| `minority_fork` | Изолирован; плагин автоматически пересинхронизируется с LIB. На форке одного оператора зацикливается — установите `disable-minority-fork-detection=true` |
| Watchdog срабатывает повторно | Флаг синхронизации или нагона завис; watchdog сбросит автоматически при продвижении головы |
| Логи `SLOT-HIJACK` | Экстренный мастер обнулил наш ключ; восстановите через `validator_update_operation` |
diff --git a/@l10n/ru/docs/prediction-markets/concepts-analysis.md b/@l10n/ru/docs/prediction-markets/concepts-analysis.md
new file mode 100644
index 0000000000..13bb38ed28
--- /dev/null
+++ b/@l10n/ru/docs/prediction-markets/concepts-analysis.md
@@ -0,0 +1,244 @@
+---
+title: Анализ концептов — Onix против 90 концептов прогнозных рынков
+description: Как живая on-chain реализация на VIZ (протокол Onix, клиент Forecaster) ложится на 90 теоретических концептов прогнозных рынков — что решено, имманентно, не нужно или в роадмапе.
+---
+
+# Анализ концептов — Onix против 90 теоретических концептов
+
+> **Forecaster** — это тонкий клиент к on-chain прогнозному рынку VIZ, слой доступа, позволяющий людям
+> со всего мира участвовать, подписывая операции `pm_*` (см. [обзор раздела](./)).
+> **Onix** — протокол, к которому он обращается. Эта страница отображает каждый из **90 концептов
+> прогнозных рынков PM Atlas** на **то, как живая on-chain реализация на VIZ его закрывает**, и **нужен
+> ли он** для этой архитектуры.
+>
+> Опорные документы: [whitepaper](./whitepaper), [спецификация](./specification),
+> [воркфлоу и диспуты](./workflows).
+
+## Легенда
+
+| Метка | Значение |
+|------|---------|
+| ✅ **Решено** | Дизайн Onix напрямую решает/обрабатывает это |
+| ⚪ **Имманентно** | Свойство, которое Onix демонстрирует/наследует by construction (доп. работа не нужна) |
+| ➖ **Не нужно** | Архитектурно избыточно в Onix |
+| 🟡 **Частично / Roadmap** | Частично решено сегодня; остальное — в роадмапе VIZ |
+| 🏛 **Слой клиента** | Решается на уровне юрисдикционного клиента, не протокола |
+| 🔴 **Открыто / Риск** | Остаётся живой проблемой; не решено полностью |
+
+Главное структурное отличие от всех остальных платформ: **принципал LP структурно гарантирован (победителям платят только из проигранных ставок), ценообразование — CPMM для бинарных и LMSR-softmax + parimutuel-сеттлмент для мульти, и нет ордербука.** Большая часть концептов «ликвидность и трейдинг» существует именно для управления инвентарным риском маркет-мейкера CLOB/LMSR — и поэтому **не применима** к Onix, где нет инвентарь-несущего мейкера.
+
+> **Актуализация on-chain (HF14 / live).** Эта таблица изначально писалась по whitepaper/спецификации. Ряд пунктов, помеченных тогда как *roadmap*, теперь **реализованы как consensus-операции** и проверены в `consensus_sim`:
+> - **Батч-аукционы + commit-reveal ставки** — `pm_commit_bet` / `pm_reveal_bet` / `pm_batch_settle`, per-market `allow_batch` / `allow_instant_bet`, median kill-switch `pm_commit_reveal_enabled`. **Только бинарные** (мульти форсит `allow_instant_bet` — LMSR-батча пока нет).
+> - **Опциональный leverage-сабсистем** — `pm_leverage_open/close/convert`, фондируется из Lazy Pool, kill-switch `pm_leverage_enabled` (по умолчанию off). Прямо закрывает **Position Collateralization (#30)**.
+> - **Сам Lazy Pool** — синглтон, auto-allocation, MasterChef-учёт, leverage-займы, graduated recall.
+> - Поле рынка **`endogeneity_tier`**; on-chain **баны создателей** (`pm_creator_ban_object`); расширенные settlement-vop (`pm_payout` на каждого беттора, `pm_leverage_resolve`, `pm_market_accepted`, `pm_auto_payout`) + методы плагина.
+> - **Новое относительно спеки:** **стейк в lazy pool учитывается как вес в голосовании** — и в PM-диспутах, *и* в голосовании за заявки DAO-комитета (конвертация в vesting-shares, гейт HF14).
+>
+> **Намеренно НЕ делаем:** commit-reveal голосование в *диспутах* — диспуты комитета это **открытые публичные слушания by design** (голоса остаются публичными через `pm_dispute_vote`, и бюллетень можно менять до закрытия). **Остаётся roadmap:** автоматические экзогенные data-оракулы. Строки и таблица смягчений ниже приведены к этому live-состоянию.
+
+---
+
+## 1. Информационная теория (Information Theory)
+
+| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли |
+|---|---------|---------|--------------------------------------------------|
+| 1 | **Brier Score** | ⚪ Имманентно | Не механизм протокола, а *метрика* оценки рынков Onix. На VIZ каждая ставка/резолюция — consensus-validated событие, значит истории цен и исходы полностью on-chain → Brier-скоринг платформы (и оракулов) считается кем угодно. Нужен как вход для аналитики/репутации, не как ядро. |
+| 2 | **Calibration** | ⚪ Имманентно | Цены Onix — настоящие вероятности (CPMM `P(A)=reserve_b/(reserve_a+reserve_b)`, LMSR-softmax суммируется в 1). Калибровка — emergent-свойство для *измерения*, косвенно улучшается time-penalty (давит no-info ставки в последнюю секунду) и глубокой LP-ликвидностью. Протокол её не навязывает. |
+| 3 | **Credibility Markets** | 🟡 Частично | Bonded-oracle + 14-метричная репутация + composite trust score — это по сути credibility-рынок для *резолверов*. Ставка репутацией против исхода — нативна. Общий продукт «стейкай репутацию на заявлениях» — возможная сборка на слое клиента, не ядро. |
+| 4 | **Distribution Markets** | 🟡 Roadmap | Onix Multi (3–10 дискретных исходов) аппроксимирует распределение через бакеты. Настоящие непрерывные distribution-рынки (CDF/скаляр) **не** в скоупе сегодня; нужна скалярная операция исхода. Примыкает к roadmap-пункту «category-level AMMs». |
+| 5 | **Endogeneity** | 🟡 Частично (смягчено) | Риск, что рынок меняет то, что предсказывает. Категориально-специфичен (эконом-данные чисто, политика/соц рискованно). Смягчается **живым полем `endogeneity_tier`** (тег оракула 1/2/3) и **опциональными commit-reveal/батч ставками (теперь on-chain)**, которые не дают публичной цене «течь» во время приёма (канал «термостата»); экзогенная резолюция через automated data oracles — **всё ещё roadmap**. См. [раздел Смягчения](#смягчения-для-семейства-рефлексивности). |
+| 6 | **Forecasting Accuracy** | ⚪ Имманентно | Вся ценностная гипотеза. Onix улучшает её косвенно: risk-free LP → глубже книги → меньше слиппедж → больше информированного участия → лучше цены. Точность — это выход для измерения, не фича. |
+| 7 | **Info Finance** | ⚪ Имманентно | Onix *и есть* инфо-финансовый инструмент: consensus-level операции превращают информацию в priced, settle-able позиции. Миграция на VIZ делает информационный слой цензуроустойчивым и композируемым. |
+| 8 | **Information Aggregation** | ✅ Решено | Основная функция. CPMM/LMSR агрегируют разрозненные ставки в единую вероятность; глубокая risk-free LP-ликвидность — именно тот рычаг, которым Onix заставляет агрегацию работать (флайвил §7.3 whitepaper). |
+| 9 | **Information Asymmetry** | 🟡 Частично | Дизайн Onix (parimutuel/CPMM) означает, что информированные трейдеры извлекают прибыль из *других проигравших бетторов*, а не из LP — поэтому асимметрия не банкротит ликвидность (в отличие от CLOB/LMSR-мейкеров). **Опциональные commit-reveal + батч ставки теперь live (бинарные):** закоммиченные ставки сеттлятся по единой батч-цене, убирая утечку направления через mempool; per-market `allow_batch` + median kill-switch оставляют это опциональным. |
+| 10 | **Legibility** | ✅ Решено | Каждое финансовое действие — consensus-validated VIZ-операция с audit trail `market_log` (до/после резервов). Полностью читаемо/аудируемо любым узлом — строго легибельнее централизованного бэкенда или непрозрачного CLOB. Новые settlement-vop (**`pm_payout`** на каждого беттора, **`pm_leverage_resolve`**, **`pm_market_accepted`**) + методы плагина делают per-bettor исходы и leverage-резолюции напрямую запрашиваемыми. |
+| 11 | **Longshot Bias** | 🟡 Частично | CPMM/LMSR всё ещё могут давать favorite-longshot bias из поведения бетторов; Onix не правит это напрямую. Time penalty и глубокая ликвидность гасят искажение, но это поведенческий выход, не устранён. |
+| 12 | **Noise Decomposition** | ⚪ Имманентно | Аналитическая линза, не фича протокола. On-chain ряды цен/объёмов на VIZ делают разложение «сигнал-шум» возможным для аналитиков. В ядре не нужно. |
+| 13 | **Nowcasting** | ⚪ Имманентно | Цены Onix обновляются на каждую ставку (~3с блоки VIZ), давая real-time nowcast-оценки. Имманентно любому живому AMM-рынку; доп. механизма нет. |
+| 14 | **Price Discovery** | ✅ Решено | CPMM и LMSR-softmax — непрерывные движки price discovery; когерентность цен (`Σ price = 1`) держится *by construction*, без слоя арбитража/split-merge. |
+| 15 | **Probability Infrastructure** | ✅ Решено | Это по сути тезис Onix на VIZ: prediction markets как **first-class consensus операции** (`pm_*`), не смарт-контракты — base-layer вероятностный примитив. Прямая цель миграции. |
+| 16 | **Superforecasting** | ⚪ Имманентно | Концепт индивидуального скилла; Onix награждает точных бетторов через payout losers→winners. Трансфер позиций + репутация могут поддержать идентичность суперфорекастера, но это черта участника, не логика протокола. |
+| 17 | **Wisdom of Crowds** | ✅ Решено | Механизм, который Onix монетизирует. Risk-free LP снижает барьер, чтобы участвовала бóльшая часть толпы, заостряя агрегат. Ядро дизайнерского обоснования. |
+| 18 | **Yes Bias** | 🟡 Частично | Поведенческий перекос к «Yes». Симметричный CPMM и profit-only time penalty структурно не благоприятствуют Yes, но и не правят человеческий bias. Смягчается глубиной ликвидности; вопрос измерения. |
+
+---
+
+## 2. Дизайн механизмов (Mechanism Design)
+
+| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли |
+|---|---------|---------|--------------------------------------------------|
+| 19 | **Binary Contracts** | ✅ Решено | Onix Binary = CPMM (`x·y=k`) на двух исходах. Доказательство AM-GM гарантирует `reserve_a+reserve_b ≥ L`, значит принципал LP покрыт. Основной тип рынка. |
+| 20 | **Combinatorial Prediction Markets** | ➖ Не нужно (сегодня) | LMSR естественно раскладывается по комбинаторным пространствам, но Onix Multi ограничен 3–10 *независимыми* исходами и намеренно опускает CTF split/merge. Комбинаторные/conditional-бандлы явно вне скоупа; для модели LP-гарантии не требуются. |
+| 21 | **Incentive Compatibility** | ✅ Решено | LMSR наследует IC от log scoring rule (truth-telling доминантна). Onix добавляет согласование стимулов через bonded-оракулов (insurance > прибыли манипуляции), сеттлмент losers-fund-winners и time-weighted LP-награды. |
+| 22 | **Keynesian Beauty Contest** | ✅ Решено (рынок) / ⚪ (слой диспутов — принято by design) | KBC — патология *относительного/peer-скоринга*. **Рыночный** слой Onix платит бетторам против внешней истины (parimutuel), значит он структурно анти-KBC — ты зарабатываешь, *отклоняясь* от цены толпы, когда она неправа. Единственная экспозиция KBC — **stake-weighted голосование комитета в диспуте** (peer-механизм). **Решение: commit-reveal в диспутах внедряться НЕ будет** — диспут комитета это **открытые публичные слушания**, и доверие к ДАО держится на максимально прозрачном разрешении споров; сокрытие голосов это доверие подорвало бы. Остаточный риск KBC принят и структурно мал: голосующим **не платят** за совпадение с большинством (нет bandwagon-бонуса), а **бюллетень можно менять** до закрытия (честные апдейты на новые доводы ожидаемы, а не подавляются). Участники пула также **с правом голоса** (стейк lazy pool → vesting-shares, HF14). См. [раздел Смягчения](#смягчения-для-семейства-рефлексивности). |
+| 23 | **LMSR** | ✅ Решено (ключевая инновация) | Файл концепта отмечает, что LMSR *провалился на бинарных* (перманентный убыток на границе 0/1). Ответ Onix: **CPMM для бинарных, а LMSR только для мульти, где мейкер НЕ контрагент** — parimutuel-сеттлмент платит победителям из проигравших, поэтому LMSR-субсидия никогда не под риском (`макс. убыток LP = 0` против `b·ln(N)`). Центральный ход дизайна. |
+| 24 | **LOX (Log-Odds Excess Lateness)** | ➖ Не нужно | Специализированная метрика скоринга/запаздывания. Onix вместо этого использует **квадратичный time penalty на прибыль** для стимулов поздних ставок — проще, на этапе сеттлмента. LOX-скоринг не часть модели. |
+| 25 | **Market Manipulation** | 🟡 Частично | Bonded-оракул (бонд > прибыли манипуляции), DPoS-валидируемые операции и арбитраж комитета повышают стоимость манипуляции. Манипуляция ценой большими ставками ограничена глубиной и — на **батч/commit-reveal рынках (теперь live)** — единой клиринговой ценой, нейтрализующей speed-снайпинг («налог снайпера»); активный surveillance — всё ещё roadmap. |
+| 26 | **Market Scoring Rules** | ✅ Решено | Onix Multi — реализация market scoring rule (LMSR), переиспользованная с parimutuel-сеттлментом. Используется напрямую. |
+| 27 | **Multi-Outcome Markets** | ✅ Решено | Onix Multi обрабатывает N=3–10 через LMSR-softmax + parimutuel payout, с `b = S/ln(N)`. First-class тип рынка. |
+| 28 | **Parimutuel Markets** | ✅ Решено (фундамент) | Сеттлмент в *обоих* типах рынков — parimutuel: проигранные ставки формируют `winners_pool`, распределяемый по доле токенов. Именно это делает гарантию LP структурной, а не страховой. |
+| 29 | **Peer Prediction** | ➖ Не нужно | Схемы truth-telling без ground-truth. Onix опирается на bonded-оракулов + комитет, не на peer-prediction скоринг. Могло бы помочь резолюции субъективных рынков, но не используется. |
+| 30 | **Position Collateralization** | ✅ Решено (+ опц. leverage, live) | По умолчанию каждая ставка полностью предоплачена (вся сумма входит в резервы; нет комиссий при ставке) — тотальная коллатерализация by construction. Теперь Onix **также** реализует «next level» концепта: **опциональный leverage-сабсистем** (`pm_leverage_open/close/convert`, kill-switch `pm_leverage_enabled`, по умолчанию off). Маржа — это **займ из Lazy Pool** (без эмиссии токенов — zero-sum сохранён), поэтому позиция остаётся полностью обеспеченной *с точки зрения системы*. Бинарный «jump risk», ломающий liquidation-движки CLOB (по этому концепту), решается **ликвидацией по pre-bet резервам**: opposing-bet / settlement force-close возвращает `min(cancel_value, obligation) ≥ loan`, так что пул получает заём + проценты; **единственный** ограниченный путь bad-debt — same-side `pm_cancel_bet` (Case B). Каскад ликвидации намеренно **не** гейтится kill-switch'ем, поэтому выключение leverage никогда не снимает защиту с открытых позиций. |
+| 31 | **Proper Scoring Rules** | ✅ Решено | LMSR — cost-function дуал log proper scoring rule; Onix Multi наследует его truthful-elicitation свойство. |
+| 32 | **Reflexivity** | 🟡 Частично (смягчено) | Родитель endogeneity. Смягчается тем же набором — **commit-reveal/батч ставки + `endogeneity_tier` теперь live**, экзогенная резолюция всё ещё roadmap — **плюс on-chain баны создателей** (`pm_creator_ban_object`) для вредной рефлексивности (рынки убийств/«hit», пропаганда-рынки, создающие «конституенцию за исход»); сам *список* запрещённых категорий остаётся на слое клиента. Глубокая risk-free LP-ликвидность также повышает стоимость newsworthy-манипуляции ценой. См. [раздел Смягчения](#смягчения-для-семейства-рефлексивности). |
+
+---
+
+## 3. Ликвидность и трейдинг (Liquidity & Trading)
+
+> **Заголовок:** в Onix **нет ордербука и нет инвентарь-несущего маркет-мейкера**. Большой класс этих концептов существует именно для управления инвентарным риском CLOB/мейкера, поэтому **не применим** к Onix.
+
+| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли |
+|---|---------|---------|--------------------------------------------------|
+| 33 | **Adverse Selection** | ✅ Решено (переосмыслено) | Классическая проблема (информированный поток банкротит мейкера) **не может обанкротить LP Onix**: победителям платят из проигравших, не из принципала LP (гарантия AM-GM / parimutuel). Информированные извлекают из других *бетторов*, не из LP. Устраняет ключевой режим отказа LP. |
+| 34 | **Arbitrage** | ⚪ Имманентно | Внутрирыночный арбитраж не нужен: когерентность (`Σ price = 1`) держится by construction в CPMM и LMSR-softmax. Слой split/merge-арбитража не нужен. |
+| 35 | **Batched Auctions** | ✅ Решено (опц., live) | Реализовано как **per-market uniform-price батч** (`mode=1` ставки + commit-reveal → `pm_batch_settle` на каждой эпохе `pm_batch_epoch_blocks`); AMM двигает только **нетто-остаток**, поэтому все same-side филлы клирятся по одной цене, а speed-снайпинг («налог снайпера») нейтрализуется. Per-market `allow_batch` + median kill-switch `pm_commit_reveal_enabled`; **только бинарные** сегодня (мульти форсит `allow_instant_bet`). Инвариант LP `Σreserve ≥ L` не тронут — каждый батч это один валидный CPMM-переход. |
+| 36 | **Bid-Ask Spread** | ➖ Не нужно | Нет ордербука → нет котируемого спреда. «Стоимость торговли» проявляется как слиппедж CPMM/LMSR, зависящий от глубины, не от спредов мейкера. Концепт не мапится. |
+| 37 | **Bonding Trades** | ⚪ Имманентно | Ставки *и есть* bonded-сделки: капитал внесён в резервы и высвобождается только при резолюции (или через отмену/трансфер). Нативное поведение. |
+| 38 | **Continuous Double Auction** | ➖ Не нужно | CDA — модель CLOB, которую Onix явно отвергает в пользу AMM. Не используется. |
+| 39 | **Covariance Markets** | ➖ Не нужно | Торговля корреляцией событий требует комбинаторной/conditional структуры, которую Onix опускает. Вне скоупа. |
+| 40 | **Cross-Platform Arbitrage** | 🟡 Частично | Цены Onix могут расходиться с Polymarket/Kalshi; арбитраж между платформами возможен, но внешний для протокола. Открытый API VIZ + headless-клиент делают данные доступными; нативного моста нет. |
+| 41 | **Execution Quality** | ✅ Решено (переосмыслено) | Нет частичных филлов/очереди. Качество исполнения = детерминированный слиппедж + опц. `min_tokens`/`min_return`-гард, валидируемый на консенсусе. Предсказуемо by construction. |
+| 42 | **Gap Risk** | ✅ Решено (для LP) | Gap-риск (резкий скачок к 0/1, выносящий мейкера) — режим отказа, который структурная гарантия LP Onix устраняет: LP никогда не держит терминальный риск проигравшей стороны. Бетторы по-прежнему несут свой риск исхода (как и задумано). |
+| 43 | **Hedging** | 🟡 Частично | Бетторы могут хеджироваться встречными позициями, трансфером (`pm_transfer_position`), отменой ставки (если разрешена) через reverse CPMM и теперь **опциональным leverage** (`pm_leverage_open/convert`) для капиталоэффективного оффсета. Нативных мульти-leg деривативов нет; базовый + leveraged хедж возможен. |
+| 44 | **Implied Correlation** | ➖ Не нужно | Требует мульти-событийных/комбинаторных рынков, которые Onix опускает. Вне скоупа. |
+| 45 | **Insider Trading** | 🟡 Частично / 🏛 Клиент | Протокол не может детектить инсайд; смягчается time penalty (поздние инфо-ставки дают меньше прибыли) и bonded-резолюцией. KYC/surveillance для полиции инсайдеров — ответственность **слоя клиента** (регулируемые клиенты). |
+| 46 | **Kelly Criterion** | ⚪ Имманентно | Стратегия сайзинга беттора, не фича протокола. Onix отдаёт чистые вероятности и полную коллатерализацию, так что Kelly-сайзинг считается участниками; новый **опциональный leverage** позволяет беттору отыгрывать fractional-Kelly эдж с маржой (фондируется из пула, ограничено ликвидацией). Ядро не вовлечено сверх предоставления примитивов. |
+| 47 | **Liquidity Fragmentation** | 🟡 Roadmap | Per-market пулы фрагментируют ликвидность сегодня. Топ-приоритет роадмапа — **shared/category-level AMM-пулы** — архитектурный фикс. Lazy Pool уже мьютуализирует *депозиты* между рынками. |
+| 48 | **Liquidity Provision** | ✅ Решено (ключевой дифференциатор) | Risk-free LP — заголовок: принципал структурно гарантирован, time-weighted награды, Lazy Pool auto-allocation + MasterChef-учёт. Решает проблему «LP теряют деньги», ради которой и создан протокол. |
+| 49 | **Market Making** | ✅ Решено (переосмыслено) | Активный мейкер не нужен — AMM + LP-пул *и есть* мейкер, и он не несёт инвентарного риска. «Market making» схлопывается в пассивное risk-free предоставление ликвидности. |
+| 50 | **Minimum Viable Liquidity** | ✅ Решено | Принудительный пол: мин. начальная ликвидность 100 VIZ; Lazy Pool авто-засевает каждый новый рынок `free_balance × allocation_%`. MVL структурно бутстрапится, не оставлен на случай. |
+| 51 | **Order Book** | ➖ Не нужно | Onix на AMM; ордербука нет by design. |
+| 52 | **Orderflow Arbitrage** | ➖ Не нужно | Нет ордербука / нет PFOF-роутинга потока → неприменимо. |
+| 53 | **Relative Value Trading** | ➖ Не нужно | Кросс-инструментный RV требует коррелированных/комбинаторных рынков, которые Onix опускает. Вне скоупа. |
+| 54 | **Retail Flow** | ✅ Решено (переосмыслено) | В CLOB ритейл-поток субсидирует убытки мейкера от toxic flow. В Onix мейкера, которого надо защищать, нет — ритейл и информированные бетторы платят в один parimutuel-пул; LP безразличен. Напряжение «ритейл vs toxic» растворяется на слое LP. |
+| 55 | **Semantic Tick Size** | ➖ Не нужно | Гранулярность тика — концепт CLOB. Цены Onix — непрерывные AMM-функции; точность — фикс. mVIZ-единица (1/1000). Дизайн тика не нужен. |
+| 56 | **Temporal Arbitrage** | 🟡 Частично | Ставка раньше vs позже несёт разный риск; **time penalty на прибыль** в Onix — именно тот механизм, что закладывает в цену запаздывание, демпфируя «жди-определённости» арбитраж. Не устранён, но явно дестимулирован. |
+| 57 | **Time Arbitrage** | 🟡 Частично | То же семейство, что #56 — эксплуатация тайминга информации. Квадратичный time penalty + ~3с такт блоков снижают, но не убирают edge. Учтён по дизайн-намерению. |
+| 58 | **Toxic Flow** | ✅ Решено (для LP) | Определяющая проблема CLOB/LMSR (снайпер выкупает книгу по 10¢ на исходе-99¢, мейкер ест 80¢) **не бьёт по LP Onix** — payouts из ставок проигравших, а субсидия LP возвращается безусловно. Toxic flow здесь просто значит, что информированные бетторы выигрывают parimutuel-пул, как и задумано. Крупный структурный выигрыш. |
+| 59 | **Wash Trading** | 🟡 Частично / 🏛 Клиент | Отсутствие комиссий при ставке убирает один стимул wash, но накрутка объёма возможна; трансферы — чистая переуступка (там фарм комиссий невозможен). Детекция/surveillance — забота клиента + роадмапа. |
+
+---
+
+## 4. Оракул и резолюция (Oracle & Resolution)
+
+| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли |
+|---|---------|---------|--------------------------------------------------|
+| 60 | **Corruption Value Multiple (CVM)** | ✅ Решено (принципом дизайна) | Явный security-инвариант протокола: **бонд страховки оракула должен превышать потенциальную прибыль манипуляции**. Risk factor (insurance/bets) питает composite trust score. CVM — напрямую обоснование бондинга. |
+| 61 | **Dispute Resolution** | ✅ Решено | Полная система: 12ч grace, `dispute_fee`, обязательный ответ оракула, per-market резолвер (`dispute_mode==0` stake-weighted голосование комитета / `==1` именованный резолвер), slashing страховки, 3-исходные no-contest диспуты, 14-дневный auto-close против заморозки, on-chain баны создателя/оракула. Одна из наиболее проработанных частей. **Дополнение HF14:** депозитчики lazy pool сохраняют вес голоса в диспуте (NAV пула → vesting-shares, добавляется к `effective_vesting_shares`). |
+| 62 | **Oracle Design** | ✅ Решено | Bonded-модель: рег-комиссия, ≥5000 VIZ страховки, явный акцепт, резолюция с доказательствами, 14-метричная репутация, freshness decay, механика банов. Ядровая подсистема. |
+| 63 | **Resolution Criteria** | 🟡 Частично / 🏛 Клиент | Вопрос/критерии рынка в `url`/описании (custom_json, display-only). Протокол навязывает *процесс* (кто резолвит, диспуты), но не *качество* критерия — двусмысленные критерии — ответственность создателя/клиента, полиция ретроспективно через диспуты + **on-chain баны создателя** (`pm_creator_ban_object`, теперь live). |
+| 64 | **Self-Resolving Markets** | ➖ Не нужно (сегодня) | Резолюция Onix — oracle-driven, не алгоритмическая. Automated data oracles (Chainlink-style фиды) — высокоприоритетный roadmap для объективных рынков, что аппроксимировало бы self-resolution. |
+| 65 | **UMA Protocol** | ➖ Не нужно (заменено) | Оптимистичный оракул UMA (у Polymarket) функционально заменён bonded-оракулом Onix + диспут-моделью комитета VIZ. Та же проблема, нативное VIZ-решение — без внешней зависимости от оракула. |
+
+---
+
+## 5. Governance и решения (Governance & Decisions)
+
+| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли |
+|---|---------|---------|--------------------------------------------------|
+| 66 | **Attention Markets** | ➖ Не нужно | Торговля вниманием/виральностью — отдельный продукт; Onix фокусируется на резолюции событий. Возможна категория на слое клиента, не ядро. |
+| 67 | **Conditional Tokens** | ➖ Не нужно (явно) | Whitepaper §7.2 доказывает, что CTF split/merge **архитектурно не нужен** — когерентность цен математическая, не навязанная токенами. *Единственную* полезную фичу CTF (трансфер позиций) реализуют нативно как `pm_transfer_position` с шифрованными memo. Намеренно опущено. |
+| 68 | **Decision Markets** | 🟡 Возможно | Onix Multi мог бы выразить decision-рынки, но conditional-структура «если-политика-то-метрика» не нативна (нет conditional tokens). Собираемо на слое клиента; не ядровый примитив. |
+| 69 | **Futarchy** | 🟡 Возможно (клиент) | Файл концепта: футархия = decision-рынки на conditional futures. У Onix нет нативных conditional-рынков, поэтому полная футархия в ядре не поддержана. Stake-weighted комитет VIZ уже управляет *параметрами*; governance-by-market — конструкция клиента/роадмапа. |
+| 70 | **Hyperstition Markets** | ➖ Не нужно (намеренно) | Рефлексивность-как-фича (координировать, не предсказывать). Это *дизайн-выбор, а не баг для фикса*: требование Onix, чтобы исход был **внешне верифицируем bonded-оракулом**, структурно исключает hyperstition-рынки по умолчанию. Может существовать как отдельный «coordination market» продукт на слое клиента с milestone-резолюцией, но не ядровая цель. См. [раздел Смягчения](#смягчения-для-семейства-рефлексивности). |
+| 71 | **Impact Markets** | ➖ Не нужно | Retrospective-funding/impact-сертификаты — отдельный домен. Возможна категория на слое клиента; не ядро. |
+| 72 | **No-Loss Prediction Markets** | 🟡 Смежно | Onix не no-loss для *бетторов* (проигравшие теряют ставки — это финансирует победителей). Но он **no-loss для *LP*** (принципал гарантирован). Yield-funded no-loss вариант (стейкаешь yield, принципал возвращается) — другая модель; no-loss на стороне LP уже реализован. |
+| 73 | **Opportunity Markets** | ⚪ Имманентно (смежно) | **Opportunity-cost protection** Lazy Pool (graduated recall, active-market penalty, fault stamps) напрямую адресует opportunity-cost капитала — хотя «opportunity markets» как категория продукта вне скоупа. |
+
+---
+
+## 6. Бизнес и платформы (Business & Platforms)
+
+| # | Концепт | Вердикт | Как Forecaster-на-VIZ это обрабатывает / нужно ли |
+|---|---------|---------|--------------------------------------------------|
+| 74 | **AI agents** | 🟡 Roadmap | Headless-клиент + открытые VIZ-операции делают программных агентов (бетторы, LP, автоматические оракулы) простыми. AI-driven ликвидность/резолюция — естественное расширение, ещё не специфицировано. |
+| 75 | **Cross-subsidization** | ⚪ Имманентно | Lazy Pool кросс-субсидирует ликвидность по многим рынкам с одного депозита; MasterChef `reward_per_share` шарит fee-yield. Кросс-субсидирование встроено в экономику пула. |
+| 76 | **Demand markets** | ➖ Не нужно | Рынки замера/агрегации спроса — категория продукта; не ядровый примитив Onix. Слой клиента. |
+| 77 | **Distribution moat** | 🟡 Стратегия | Моат Onix — risk-free LP-yield + VIZ-нативная инфраструктура (флайвил). Дистрибуция (Telegram WebApp сегодня → headless web-клиент) — go-to-market, частично решается платформонезависимостью после миграции. |
+| 78 | **Election markets** | 🏛 Клиент | Поддержаны как обычные бинарные/мульти рынки; их *легальность* — вопрос юрисдикционного клиента (whitelisted оракулы, фильтры категорий). Протокол нейтрален. |
+| 79 | **Event contracts** | ⚪ Имманентно | Каждый рынок Onix *и есть* event contract. Регуляторная классификация — вопрос клиента/права, не логика протокола. |
+| 80 | **Federal preemption** | 🏛 Клиент (N/A протоколу) | Файл концепта: зависит от того, «swaps» ли US event contracts. VIZ DLT — **инфраструктура, не оператор** (whitepaper §6.2) — как Bitcoin это леджер. Правовые обязательства привязаны к клиентам, не к консенсусу. Не забота протокола. |
+| 81 | **Long-tail markets** | ✅ Решено | Именно ниша, которую включает bounded-loss LMSR — и Onix делает её *risk-free* для засева через Lazy Pool auto-allocation + пол ликвидности. Жизнеспособность long-tail — ядровый аргумент продажи. |
+| 82 | **Market structure** | ✅ Решено (определено) | Onix определяет чёткую структуру: AMM-ценообразование, parimutuel-сеттлмент, bonded-оракулы, DPoS-управляемые параметры, consensus-level операции. Связная, новая рыночная структура против CLOB-платформ. |
+| 83 | **Market surveillance** | 🟡 Roadmap / 🏛 Клиент | Полный on-chain audit trail (`market_log`, каждая операция consensus-validated) делает surveillance *возможным* для любого. Активный surveillance/энфорсмент — забота клиента + роадмапа. |
+| 84 | **Network effects** | 🟡 Стратегия | Флайвил (risk-free LP → глубина → бетторы → комиссии → ещё LP) — задуманный сетевой эффект. Shared liquidity pools (roadmap) усиливают его. Go-to-market, не механика протокола. |
+| 85 | **Parlays** | ➖ Не нужно | Многоногие комбо-ставки требуют conditional/комбинаторной структуры, которую Onix опускает. Вне скоупа (возможна сборка клиента поверх независимых рынков). |
+| 86 | **Platform competition** | 🟡 Стратегия | Конкурирует уникальным «passive yield without impermanent loss» против Polymarket/Kalshi (таблица сравнения whitepaper §7.1). Стратегическое позиционирование, не логика протокола. |
+| 87 | **Polymarket** | ⚪ Референс | Главный бенчмарк. Onix отличается по всем осям: CPMM/LMSR vs CLOB, ноль LP-риска vs инвентарный риск, нативные операции vs контракты Polygon, bonded-оракул vs UMA, без CTF. Используется для сравнения, не перенят. |
+| 88 | **Regulatory arbitrage** | 🏛 Клиент | Модель юрисдикционного клиента: каждый регион строит свой compliant (или permissionless) клиент на нейтральных рельсах VIZ. Регуляторное позиционирование живёт целиком на слое клиента. |
+| 89 | **Regulatory classification** | 🏛 Клиент | Являются ли рынки swaps/gaming/securities — решается per-юрисдикция на слое клиента; протокол классификационно-нейтрален (те же `pm_*` для permissionless и регулируемых клиентов). Не забота протокола. |
+
+---
+
+## Итог — что реально меняет дизайн Onix-на-VIZ
+
+**Решено структурно (ключевые выигрыши):**
+- LP-сторонние **adverse selection, toxic flow, gap risk, impermanent loss, инвентарный риск мейкера** → все устранены, т.к. победителям платят только из проигранных ставок, а принципал LP возвращается безусловно (доказательство AM-GM для CPMM; parimutuel-сеттлмент для LMSR).
+- **Провал LMSR на бинарных рынках** → обойдён использованием CPMM для бинарных и ограничением LMSR мульти-рынками, где мейкер не контрагент.
+- **Liquidity provision, minimum viable liquidity, жизнеспособность long-tail** → risk-free LP + Lazy Pool auto-allocation.
+- **Oracle design, dispute resolution, CVM** → bonded-оракул + 14-метричная репутация + stake-weighted диспуты комитета.
+- **Price discovery, arbitrage, conditional tokens** → когерентность цен математическая (`Σ price = 1`), значит не нужны ордербук, split/merge, внутренний слой арбитража.
+
+**Не нужно / намеренно опущено:** ордербук, CDA, bid-ask spread, semantic tick size, orderflow arbitrage, CTF split/merge, комбинаторные/covariance/correlation/relative-value/parlay рынки, UMA, peer prediction, LOX.
+
+**Вытолкнуто на слой юрисдикционного клиента:** federal preemption, regulatory classification/arbitrage, легальность election-рынков, KYC/энфорсмент инсайда, surveillance.
+
+**Реализовано с момента написания таблицы (теперь live on-chain, HF14):** опциональные батч-аукционы + commit-reveal ставки (бинарные), опциональный leverage-сабсистем (position collateralization), Lazy Pool, `endogeneity_tier`, on-chain баны создателей, settlement-vop на беттора/leverage + методы плагина, и вес стейка lazy pool в PM-диспутах + голосовании за заявки DAO-комитета.
+
+**В роадмапе VIZ (частично сегодня):** shared/category liquidity pools (фикс фрагментации), automated data oracles (→ self-resolving объективные рынки), distribution markets, AI agents. *(Прим.: commit-reveal в **диспутах** в этом списке **нет** — он намеренно отклонён; слушания диспутов остаются публичными. Commit-reveal для **ставок** уже live.)*
+
+**Остаётся открытым / поведенческое (смягчено, не устранено):** longshot/yes bias, манипуляция ценой через глубину, кросс-платформенный арбитраж. У семейства рефлексивности (endogeneity, reflexivity, KBC, hyperstition) есть конкретный план смягчения — см. ниже.
+
+---
+
+## Смягчения для семейства рефлексивности
+
+Endogeneity, reflexivity, Keynesian beauty contest (KBC) и hyperstition — это **одна корневая проблема на разных слоях**: рынок/цена влияет на исход, который он измеряет. Один небольшой набор примитивов адресует все четыре.
+
+### Карта корневой причины
+
+| Слой | Концепт | Канал |
+|------|---------|-------|
+| Уровень форекастера | **Keynesian Beauty Contest** | стадность к видимому консенсусу в *относительном/peer-скоринге* |
+| Уровень рынка | **Endogeneity** | *существование/видимость* рынка меняет поведение (категориально) |
+| Уровень рынка | **Reflexivity** | общая обратная связь цена↔реальность; манипуляция-как-пропаганда |
+| По дизайну | **Hyperstition** | рефлексивность используется *намеренно* для координации исхода |
+
+### Примитивы смягчения
+
+| Примитив | Статус | Что чинит | Заметки |
+|----------|--------|-----------|---------|
+| **Commit-reveal голосование в диспутах** | **отклонено (делать НЕ будем)** | KBC | Диспут комитета это **открытые публичные слушания**: `pm_dispute_vote` — публичный бюллетень, и **остаётся таким by design** — доверие к ДАО держится на прозрачном разрешении споров. Бюллетень **можно менять** до закрытия (повтор перезаписывает), чтобы голосующие честно обновляли решение на новые доводы; остаточный KBC принят (за совпадение с большинством не платят). |
+| **Commit-reveal ставки (батчем)** | **live (опц., бинарные)** | endogeneity, reflexivity, инфо-асимметрия | `pm_commit_bet`/`pm_reveal_bet`/`pm_batch_settle` скрывают направление/размер in-flight потока, чтобы публичная цена не «текла» во время приёма (убивает канал термостата). Сеттлится единой ценой **батчем** (см. ниже). |
+| **Поле рынка `endogeneity_tier`** | **живое поле** | endogeneity | Оракул тегирует tier 1 (эконом-данные — чисто), 2 (спорт/расписание), 3 (политика/соц — рискованно); UI показывает уровень рефлексивного риска; клиенты могут ограничивать tier-3. |
+| **Экзогенная резолюция (automated data oracles)** | roadmap (high) | endogeneity, reflexivity | Резолюция привязана к внешнему фиду (BLS/ФРС/спорт-API), на который рынок не влияет → чистый термометр. |
+| **Список запрещённых категорий + бан создателя** | **бан создателя live on-chain**; список — слой клиента | вредная рефлексивность, hyperstition | Блокировать рынки, где YES создаёт «конституенцию за исход» (рынки убийств/«hit»/терактов, пропаганда-рынки). Через on-chain бан создателя (`pm_creator_ban_object`) + фильтр категорий клиента. |
+| **Глубокая risk-free LP-ликвидность** | ядро сегодня | манипуляционная рефлексивность | Флайвил делает книгу глубокой, поэтому двигать цену ради «newsworthy» манипулированного заголовка — дорого. |
+
+### KBC: почему рыночный слой уже безопасен
+
+KBC — патология **относительного скоринга** (платят за близость к peers → стадность к peers). Рыночный слой Onix платит бетторам против **внешней истины через parimutuel-сеттлмент** — тебя награждают за *отклонение* от неправильной цены толпы, а не за совпадение. Значит слой бетторов структурно анти-KBC. Единственный относительный/peer-механизм в протоколе — **stake-weighted голосование комитета в диспуте**, и остаточный риск KBC там **принят by design** — диспут оставлен открытыми публичными слушаниями (без commit-reveal), потому что доверие к ДАО держится на прозрачном разрешении споров; риск ограничивает другое — голосующим не платят за совпадение с большинством, а бюллетень можно менять по мере поступления доводов.
+
+### Commit-reveal против инварианта CPMM `a·b=k`
+
+CPMM **path-dependent** (токены зависят от резервов в момент исполнения), поэтому commit-reveal *нельзя* делать ставка-за-ставкой против живой кривой — порядок раскрытия вернул бы MEV и слил бы цену. Фикс (и почему §8.3 ставит commit-reveal рядом с batch-auction): **перестать обновлять кривую per-bet; обновлять её раз в эпоху через uniform-price батч-сеттлмент.**
+
+1. **Commit:** прислать `hash(side, amount, salt, min_tokens)` и заэскроить `amount`.
+2. **Reveal:** раскрыть `(side, amount, salt)`; раскрытия собираются, но **не применяются** до конца эпохи (видеть чужие reveal бесполезно — своё уже закоммичено).
+3. **Сеттлмент раз:** встречный поток (`A_in` vs `B_in`) неттится между бетторами по единой клиринговой цене `p*`; только **нетто-остаток** двигает AMM, поэтому `k` пересчитывается **один раз**. Все A-филлы получают `p*`, все B-филлы — `1−p*` → нет внутрибатчевого преимущества по порядку.
+4. **Флор `min_tokens`:** поскольку цена невидима в момент commit, per-bet флор по токенам обязателен; если `tokens < min_tokens` на сеттлменте, ставка отклоняется и возвращается из эскроу.
+5. **Анти-griefing:** нераскрытие → штраф из эскроу в LP-fee/DAO-пул, убивая атаку «закоммить опциональность, раскрой только победителей».
+
+**Гарантия LP не тронута:** каждый батч-сеттлмент — валидный CPMM-переход, значит AM-GM `reserve_a + reserve_b ≥ L` держится. Меняется только *гранулярность* обновления кривой (per-bet → per-epoch). Onix Multi аналогично через агрегированную LMSR cost-функцию.
+
+**Фазировка:** (1) сначала uniform-price батч-аукционы — уже дёшево убивают ordering-MEV/фронт-раннинг; (2) поверх — commit-reveal сокрытие — добавляет in-flight конфиденциальность, включается выборочно для tier-3 (endogeneity-чувствительных) рынков. **И (1), и (2) теперь реализованы on-chain для бинарных рынков** (мульти всё ещё форсит instant-ставки — LMSR-батча пока нет).
+
+### Изменение вердиктов
+
+| Концепт | Было | Стало |
+|---------|------|-------|
+| Keynesian Beauty Contest | 🔴 Открыто | ✅ рынок / ⚪ диспут — голоса публичны **by design** (без commit-reveal; бюллетень изменяем, голосующим не платят, участники пула с правом голоса) |
+| Endogeneity | 🔴 Открыто | 🟡 смягчено — `endogeneity_tier` + commit-reveal/батч **live**; экзогенные оракулы roadmap |
+| Reflexivity | 🔴 Открыто | 🟡 смягчено — бан создателя **live on-chain**; список категорий — слой клиента |
+| Hyperstition | 🔴 Открыто | ➖ исключено по дизайну (опц. продукт клиента) |
diff --git a/@l10n/ru/docs/prediction-markets/index.md b/@l10n/ru/docs/prediction-markets/index.md
new file mode 100644
index 0000000000..64621de0ee
--- /dev/null
+++ b/@l10n/ru/docs/prediction-markets/index.md
@@ -0,0 +1,53 @@
+---
+title: Прогнозные рынки (Onix) — обзор и карта
+description: Стек on-chain прогнозных рынков VIZ — протокол Onix и клиент Forecaster — с полным деревом документации от whitepaper до спецификации, объектов, операций, воркфлоу и анализа концептов.
+---
+
+# Прогнозные рынки на VIZ
+
+VIZ Ledger исполняет прогнозные рынки как **операции консенсуса первого класса** (`pm_*`), live с HF14.
+Два имени, которые встречаются повсюду:
+
+- **Onix** — **протокол**: on-chain движок рынка (CPMM binary + LMSR multi, parimutuel zero-sum
+ расчёт, bonded-оракулы, lazy-пул, опциональное плечо, batch / commit-reveal ставки).
+- **Forecaster** — **тонкий клиент** к этому протоколу в VIZ Ledger. Это headless, платформонезависимый
+ фронтенд, позволяющий **людям со всего мира участвовать в on-chain прогнозном рынке** — создавать
+ рынки, ставить, давать ликвидность, быть оракулом и оспаривать — подписывая операции `pm_*` напрямую
+ против публичных узлов VIZ. Протокол нейтрален; Forecaster (и любой юрисдикционный клиент,
+ построенный так же) — это слой доступа.
+
+## Карта документации
+
+```mermaid
+flowchart TD
+ ROOT["Прогнозные рынки (Onix)"]
+ ROOT --> OV["Обзор — одностраничный питч (весь стек кратко)"]
+ ROOT --> WP["Whitepaper — тезис: почему risk-free LP, два типа рынков, флайвил"]
+ ROOT --> SP["Спецификация — формальная механика + §17 On-chain модель объектов"]
+ ROOT --> OPS["Операции — подписанные consensus-операции pm_*"]
+ ROOT --> VOPS["Виртуальные операции — детерминированные vop на расчёте / по дедлайну"]
+ ROOT --> API["API плагина — read-методы prediction_market_api"]
+ ROOT --> WF["Воркфлоу и диаграммы — один канонический рынок через все роли"]
+ ROOT --> CA["Анализ концептов — 90 концептов PM-теории против живой реализации на VIZ"]
+```
+
+## С чего начать
+
+| Страница | Что это |
+|------|-----------|
+| [Обзор](./onix) | Одностраничное позиционирование: parimutuel с AMM-ценой и структурно безрисковой ликвидностью. |
+| [Whitepaper](./whitepaper) | Индустриальный тезис — гарантия LP, Onix Binary (CPMM) + Onix Multi (LMSR), оракулы, lazy-пул, плечо, управление. |
+| [Спецификация](./specification) | Формальная спека: параметры, машина состояний, ценообразование, расчёт, споры, lazy-пул, плечо и **[On-chain модель объектов](./specification)** (каждый `pm_*_object` и его индекс поиска). |
+| [Операции](../protocol/operations/prediction-markets) | 21 подписанная consensus-операция (`pm_create_market`, `pm_place_bet`, …). |
+| [Виртуальные операции](../protocol/virtual-operations) | Детерминированные vop (`pm_payout`, `pm_market_accepted`, `pm_leverage_resolve`, `pm_batch_settle`, …). |
+| [API плагина](../plugins/prediction-market-api) | `prediction_market_api` — read-доступ к рынкам, ставкам, оракулам, спорам, lazy-пулу и медиана-голосуемым параметрам. |
+| [Воркфлоу и диаграммы](./workflows) | Один канонический бинарный рынок через всех участников, с zero-sum мастер-леджером для нормального и спорного разрешения. |
+| [Анализ концептов (Onix против 90)](./concepts-analysis) | Как on-chain реализация ложится на атлас теории прогнозных рынков — что решено, имманентно, не нужно или в роадмапе. |
+
+## Управление
+
+Все экономические параметры делегат-**медиана-голосуемы** и живут в структуре `chain_properties_pm` —
+см. [Параметры цепи → Параметры прогнозных рынков](../governance/chain-properties#pm-parameters).
+Хардфорк для настройки комиссий, штрафов, lazy-пула, плеча или тайминга batch/commit-reveal не нужен; три
+живых kill-switch (`pm_commit_reveal_enabled`, `pm_lazy_pool_enabled`, `pm_leverage_enabled`) позволяют
+медиане валидаторов отключить целую подсистему без форка.
diff --git a/@l10n/ru/docs/prediction-markets/onix.md b/@l10n/ru/docs/prediction-markets/onix.md
new file mode 100644
index 0000000000..118329978b
--- /dev/null
+++ b/@l10n/ru/docs/prediction-markets/onix.md
@@ -0,0 +1,89 @@
+---
+title: Onix — прогнозные рынки с AMM-ценой и тотализаторными выплатами
+description: "Onix кладёт непрерывное AMM-ценообразование поверх parimutuel-выплат — цена движется как в AMM, а ликвидность несёт риск-профиль тотализатора: маркет-мейкера нельзя обанкротить."
+---
+
+# Onix — прогнозные рынки: AMM-цена, тотализаторные выплаты
+
+> Непрерывное **AMM-ценообразование** поверх **parimutuel-выплат** — цена движется как в AMM, а
+> ликвидность несёт риск-профиль тотализатора: **маркет-мейкера нельзя обанкротить.**
+
+::: info Onix и Forecaster
+**Onix** — это on-chain протокол. **Forecaster** — тонкий клиент к нему в VIZ Ledger: headless,
+платформонезависимый слой доступа, позволяющий людям со всего мира участвовать в on-chain прогнозном
+рынке, подписывая операции `pm_*` напрямую против публичных узлов VIZ. Полное дерево документации —
+[обзор и карта раздела](./).
+:::
+
+## Одна идея
+
+Onix **разделяет цену и выплату**:
+
+- **Цена (discovery)** — кривая CPMM (binary) или LMSR-softmax (multi) обновляет живую вероятность на
+ каждой ставке и присваивает ставке **вес** (её «билет» на долю).
+- **Выплата (settlement)** — победителям платят **только** из проигранных ставок, делёж по весу: чистый
+ **parimutuel**, строго zero-sum (протокол никогда не печатает токен).
+
+Всё отличительное в Onix вытекает из этого разделения.
+
+## Почему это важно — три вещи
+
+::: tip 1 · Ликвидность, которую нельзя «слить»
+Поскольку победителям платят из проигравших, а не из принципала LP, поставщика ликвидности **нельзя
+обанкротить** — нет impermanent loss, нет инвентарного риска, нет «смерти от снайпера». Гарантировано *by
+construction* (AM–GM для CPMM, сохранение для LMSR), а не страховкой.
+:::
+
+::: tip 2 · Пассивный доход без IL — Lazy Pool
+Один депозит сам растекается тихой ликвидностью по многим рынкам и фондирует опциональное плечо, учёт
+наград в стиле MasterChef. Доход от ликвидности ПМ **без** выбора рынков и без impermanent loss.
+:::
+
+::: tip 3 · Нативность к цепи, zero-sum
+Рынки — это операции консенсуса первого класса (`pm_*`), а не смарт-контракты: цензуроустойчиво,
+композируемо, блоки ~3 секунды, без оракул-моста. Протокол не эмитит токены — только перераспределяет.
+:::
+
+## Как работает ставка
+
+1. **Ты ставишь** `X` на исход. `X` входит в кривую; кривая возвращает твой **вес** — больше веса, если
+ зашёл раньше, до сдвига цены.
+2. **Борд обновляется.** Живой коэффициент стороны = `1 + чужой_пул × (1 − комиссия) / свой_пул`, причём
+ комиссия (оракул + создатель + LP) уже зашита.
+3. **На резолюции** ставки проигравших (минус комиссия) делятся между победителями по весу. Твоя выплата
+ = возврат ставки **+** твоя доля проигравшего пула. Принципал LP возвращается нетронутым.
+
+## Сравнение
+
+| | CLOB / AMM (Polymarket, Kalshi) | Голый parimutuel (тотализатор) | **Onix** |
+|---|---|---|---|
+| Живая цена | да | нет (только соотношение пулов) | **да (CPMM / LMSR)** |
+| Кэф зафиксирован при ставке | да | нет | нет (честный parimutuel) |
+| LP / мейкер может обанкротиться | **да** (IL, снайперы, gap) | n/a | **нет (структурно)** |
+| Доходный слой ликвидности | хрупкий | нет | **Lazy Pool, без IL** |
+| Где живёт | контракты / бэкенд | бэкенд | **консенсус (`pm_*`)** |
+| Эмиссия токенов | бывает | нет | **нет (zero-sum)** |
+
+## Честный компромисс
+
+::: warning Кэфы — parimutuel: они плывут до закрытия
+Onix **не** фиксирует твой коэффициент в момент ставки. Борд движется по мере притока денег, и финальный
+кэф известен только на закрытии — ровно как в тотализаторе. Это не баг для заплатки: *единственный* способ
+зафиксировать кэф — чтобы риск нёс контрагент (букмекер или AMM-LP, который может потерять). Drift в Onix —
+прямая цена его LP-гарантии: риск живёт **между беттерами**, поэтому ничья ликвидность не может сгореть.
+:::
+
+## Что новаторского
+
+- **AMM-вес + parimutuel-сеттлмент** в одном движке — непрерывный price discovery *без* инвентарного
+ риска мейкера.
+- **Структурная, доказуемая безопасность LP** вместо застрахованной или субсидируемой ликвидности.
+- **Мьютуализированный доходный слой ликвидности** (Lazy Pool), который ещё и фондирует опциональное
+ плечо — ликвидация идёт по pre-bet резервам, поэтому пул всегда остаётся целым.
+- **Опциональный анти-MEV** (batch / commit-reveal ставки) и **прозрачное управление** (bonded-оракулы,
+ публичные слушания споров с изменяемыми голосами) — всё поверх безопасной базы, не трогая LP-гарантию.
+
+## Подробнее
+
+- Операции протокола — [Прогнозные рынки](../protocol/operations/prediction-markets)
+- API плагина — [API прогнозных рынков](../plugins/prediction-market-api)
diff --git a/@l10n/ru/docs/prediction-markets/specification.md b/@l10n/ru/docs/prediction-markets/specification.md
new file mode 100644
index 0000000000..8b74bc0a5f
--- /dev/null
+++ b/@l10n/ru/docs/prediction-markets/specification.md
@@ -0,0 +1,1013 @@
+---
+title: Onix Protocol — Спецификация
+description: Формальная техническая спецификация протокола Onix, реализованного как операции консенсуса на VIZ DLT (HF14).
+---
+
+# Спецификация Onix Protocol
+
+**Версия:** 2.0 (on-chain / HF14)
+**Статус:** Формальная техническая спецификация — реализована как операции консенсуса на VIZ DLT
+
+---
+
+> **On-chain (HF14).** Реализовано как операции консенсуса первого класса (`pm_*`) на VIZ DLT и проверено
+> в `consensus_sim`. **Все проценты — базисные пункты (bp): 10000 = 100.00%**; все длительности —
+> governance-параметры в **секундах / блоках**. Медиана-голосуемые параметры живут в структуре
+> `chain_properties_pm` (§3); per-market поля — операция `pm_create_market`; всё состояние — в
+> chainbase-объектах §17. Комиссии оракула — offer→quote (потолок создателя → оракул фиксирует котировку
+> при акцепте, эмитя `pm_market_accepted`). У споров два режима — комитет (`dispute_mode = 0`, по
+> умолчанию: stake-weighted **публичный** `pm_dispute_vote`, изменяем до закрытия, стейк Lazy-Pool
+> считается) и аккаунт (`dispute_mode = 1`: именованный `dispute_resolver`).
+
+## Оглавление
+
+1. Определения и роли
+2. Валюта и точность
+3. Системные параметры
+4. Машина состояний рынка
+5. Onix Binary: Constant Product Market Maker
+6. Onix Multi: LMSR с parimutuel-расчётом
+7. Структура комиссий
+8. Time penalty для поздних ставок
+9. Предоставление ликвидности
+10. Резолюция и выплаты
+11. Отмена ставки
+12. Система споров
+13. Штраф оракула за пропуск резолюции
+14. Скоринг репутации оракула
+15. Трансфер позиций
+16. Lazy-пул ликвидности
+16a. Опциональное плечо
+16b. Batch / Commit-Reveal ставки
+17. On-chain модель объектов
+
+---
+
+## 1. Определения и роли
+
+| Роль | Определение |
+|------|-----------|
+| **Создатель рынка** | Платит `pm_market_creation_fee` (`pm_create_market`); задаёт вопрос, исходы, ликвидность, потолки комиссий и параметры тайминга |
+| **Оракул** | Регистрируется (комиссия: `pm_oracle_registration_fee`), вносит страховку (мин.: `pm_min_oracle_insurance`), котирует свои условия комиссии в **базисных пунктах** (≤ потолка создателя) + фиксированную комиссию при акцепте, принимает/отклоняет рынки, даёт решения по исходам |
+| **Беттер** | Ставит на исходы; получает токены пропорционально стейку и текущим резервам |
+| **Поставщик ликвидности (LP)** | Поставляет капитал в пулы рынка; зарабатывает time-weighted долю комиссий ликвидности + штрафной пул |
+| **Поставщик Lazy Pool** | Вносит VIZ в Lazy-пул ликвидности с lock-периодом; пул авто-аллоцирует в рынки и распределяет награды через аккумулятор `reward_per_share` |
+| **Резолвер споров** | Только режим аккаунта (`dispute_mode = 1`): per-market аккаунт `dispute_resolver` арбитрирует. Режим комитета (`dispute_mode = 0`) резолвера не использует — голосует электорат SHARES |
+| **Фонд DAO / комитета** | Существующий фонд комитета цепи. Получает `pm_market_creation_fee` и доп. штрафы оракулов |
+
+---
+
+## 2. Валюта и точность
+
+Все суммы хранятся как целые с точностью = 1/1000 (милли-VIZ). `1000` внутренних единиц = 1.000 VIZ.
+
+Значения time penalty используют точность = 1/1 000 000 (микро-единицы).
+
+---
+
+## 3. Системные параметры
+
+### Медиана-голосуемые параметры (`chain_properties_pm`)
+
+Все экономические параметры голосуются медианой делегатов (без хардфорка для настройки) и живут в on-chain
+структуре `chain_properties_pm`. Каждый делегат публикует свои предпочтительные значения через стандартную
+**`versioned_chain_properties_update_operation`** (op ID 46) — `chain_properties_pm` является текущей
+(v5, HF14) версией этой versioned-структуры — а сеть применяет **медиану по каждому полю** активных
+делегатов. Две ручки риск-покрытия (`pm_listing_min_coverage_percent`, `pm_betting_min_coverage_percent`)
+входят в ту же v5-структуру и настраиваются ровно так же. **Все проценты — базисные пункты
+(bp, 10000 = 100.00%); длительности — в секундах или блоках** — за исключением двух ручек покрытия, которые
+измеряются в проценте от объёма (100 = 1.0×). Точные дефолты и диапазоны — в
+[Chain Properties](../governance/chain-properties#pm-parameters); авторитетный источник — сама структура.
+
+| Группа | Параметры |
+|---|---|
+| Регистрация и полы | `pm_oracle_registration_fee`, `pm_min_oracle_insurance`, `pm_market_creation_fee`, `pm_min_liquidity`, `pm_max_outcomes`, `pm_max_market_duration` |
+| Комиссии и штрафы (bp) | `pm_max_oracle_fee_percent`, `pm_oracle_penalty_percent`, `pm_no_contest_penalty_percent`, `pm_default_time_penalty_percent`, `pm_max_time_penalty` |
+| Окно акцепта | `pm_oracle_accept_window_sec` (по умолчанию 3600 = 1 ч; пендинг-рынки, не принятые/отклонённые в этот срок, аннулируются кроном — сид возвращён, комиссия за создание удержана) |
+| Риск / покрытие (% от объёма) | `pm_listing_min_coverage_percent` (250 = 2.5×; рынки с покрытием ниже этого скрыты из каталога по умолчанию, показываются через `show_risky`), `pm_betting_min_coverage_percent` (150 = 1.5×; рекомендательный клиентский порог подтверждения риска, `≤` листингового, on-chain не навязывается) |
+| Споры | `pm_dispute_fee`, `pm_dispute_grace_sec`, `pm_oracle_dispute_response_sec`, `pm_dispute_vote_period_sec`, `pm_dispute_auto_close_sec`, `pm_dispute_approve_min_percent` (bp), `pm_dispute_reward_multiplier` (bp) |
+| Lazy-пул | `pm_lazy_pool_enabled`, `pm_lazy_alloc_percent`, `pm_lazy_max_total_alloc_percent`, `pm_lazy_recall_step_percent`, `pm_lazy_lock_sec`, `pm_lazy_emergency_penalty_percent`, `pm_lazy_min_liquidity_fee_percent` (по умолчанию 200 = 2%; пул пропускает рынки, чей `liquidity_fee_percent` ниже этого порога вознаграждения) |
+| Плечо | `pm_leverage_enabled`, `pm_leverage_fund_percent`, `pm_leverage_max_per_position_bp`, `pm_leverage_max_position_ratio_percent`, `pm_leverage_min_market_liquidity`, `pm_leverage_safety_margin_percent`, `pm_leverage_max_slippage_percent`, `pm_leverage_m_factor_percent`, `pm_leverage_pool_profit_percent`, `pm_leverage_expiration_buffer_sec`, `pm_conversion_profit_cost_percent` |
+| Batch / commit-reveal | `pm_commit_reveal_enabled`, `pm_batch_epoch_blocks`, `pm_reveal_window_blocks`, `pm_commit_no_reveal_penalty_percent` (bp), `pm_min_batch_bet` |
+| Обработка | `pm_processing_cap_per_block` |
+
+Получатель `pm_market_creation_fee` и доп. штрафов оракулов — существующий фонд комитета/DAO цепи, а не
+отдельный PM-аккаунт.
+
+### Per-market параметры (операция `pm_create_market`)
+
+Задаются создателем при создании; поля комиссии оракула — это **потолок**, против которого оракул котирует
+при акцепте (offer→quote). Полный референс полей: [Операции прогнозных рынков](../protocol/operations/prediction-markets).
+
+| Поле | Описание |
+|---|---|
+| `oracle`, `market_type` (0 binary / 1 multi), `outcomes`, `url` | определение рынка |
+| `oracle_fee_percent`, `oracle_fixed_fee` | **потолок** комиссии оракула (bp + фикс.); оракул фиксирует котировку ≤ него (и ≤ медианного `pm_max_oracle_fee_percent`) при акцепте |
+| `creator_fee_percent`, `liquidity_fee_percent` | комиссии создателя и LP (bp от пула проигравших) |
+| `liquidity`, `lmsr_b` | сид-ликвидность; `lmsr_b` для мульти-рынков |
+| `betting_expiration`, `result_expiration` | таймеры |
+| `time_penalty_type`, `time_penalty_value`, `penalty_curve_type` | форма штрафа за поздние ставки |
+| `allow_early_resolution`, `allow_cancellation` | переключатели |
+| `allow_batch`, `allow_instant_bet` | режимы ставок (бинарные) |
+| `endogeneity_tier` | 1 эконом-данные / 2 спорт / 3 политика (подсказка отображения/риска) |
+| `dispute_mode` (0 комитет / 1 аккаунт), `dispute_resolver` | маршрутизация споров |
+| `dispute_penalty_percent` | политика штрафа оракула при удовлетворённом споре (bp, со знаком) |
+| `metadata` | свободный клиентский JSON (консенсус-непрозрачен; парсится off-chain) |
+
+---
+
+## 4. Машина состояний рынка
+
+### Состояния
+
+| Status | Имя | Описание |
+|--------|------|-------------|
+| -1 | Deleted | Оракул отклонил **или** истекло окно акцепта (`pm_oracle_accept_window_sec`); сид-ликвидность возвращена создателю (комиссия за создание удержана) |
+| 0 | Waiting | Ожидает проверки оракула |
+| 1 | Active | Принимает ставки до `betting_expiration` |
+| 2 | Closed | Приём ставок закончен, ожидает резолюции оракула |
+| 3 | Resolved | Исход определён, выплаты рассчитаны |
+
+### Состояния выплат
+
+| payout_status | Имя | Описание |
+|---------------|------|-------------|
+| 0 | Not calculated | До резолюции |
+| 1 | Calculated | Выплаты ожидают (grace-период активен) |
+| 2 | Paid | Все выплаты обработаны |
+| 3 | Disputed | Подан спор, выплаты заморожены |
+
+### Переходы
+
+```mermaid
+stateDiagram-v2
+ direction LR
+ state "Waiting (0)" as Waiting
+ state "Active (1)" as Active
+ state "Closed (2)" as Closed
+ state "Resolved (3)" as Resolved
+ state "Deleted (-1)" as Deleted
+ state "Paid out" as Paid
+ [*] --> Waiting
+ Waiting --> Active: оракул принял
+ Waiting --> Deleted: оракул отклонил
+ Waiting --> Deleted: окно акцепта истекло (pm_market_expired)
+ Active --> Closed: betting_expiration
+ Active --> Resolved: раннее разрешение (если разрешено)
+ Closed --> Resolved: оракул разрешает
+ Resolved --> Paid: окно ожидания (12ч), без спора
+ Deleted --> [*]
+ Paid --> [*]
+```
+
+**Предусловия:**
+
+| Переход | Предусловия |
+|-----------|---------------|
+| 0 → 1 | Страховка оракула ≥ `min_oracle_insurance`; оракул принимает |
+| 0 → 1 (self-oracle) | Создатель = оракул; проверка страховки; авто-одобрение при создании |
+| 0 → -1 | Оракул отклоняет; сид-ликвидность возвращена создателю |
+| 0 → -1 (экспирация) | `now ≥ created_time + pm_oracle_accept_window_sec` без действия оракула; крон аннулирует рынок, возвращает сид (комиссия за создание удержана), эмитит `pm_market_expired` |
+| 1 → 3 | Оракул подаёт резолюцию с исходом (0, 1 или -1 для no-contest); `allow_early_resolution=1` или `time ≥ betting_expiration` |
+| 2 → 3 | Оракул подаёт резолюцию; `time ≤ result_expiration` |
+| 3 → paid | Grace-период прошёл без спора; крон обрабатывает выплаты |
+
+### Флоу создания рынка
+
+1. Списать `market_creation_fee` с создателя → фонд DAO (невозвратно)
+2. Записать `oracle_fixed_fee` из профиля оракула на рынок
+3. Заблокировать `liquidity` с баланса создателя
+4. Инициализировать резервы: `reserve_a = floor(liquidity/2)`, `reserve_b = liquidity − reserve_a`
+5. Вычислить `k = reserve_a × reserve_b`
+6. Если self-oracle: авто-одобрение до status=1 с проверкой страховки
+7. Если внешний оракул: войти в status=0 и установить `accept_deadline = created_time + pm_oracle_accept_window_sec`
+
+### Флоу акцепта оракула
+
+Пендинг-рынок должен быть разрешён своим оракулом в течение окна акцепта
+(`pm_oracle_accept_window_sec`, по умолчанию 1 ч). Три исхода:
+
+- **Принятие** (status 0 → 1): (1) перевести `oracle_fixed_fee` с баланса создателя на баланс оракула
+ (пропускается для self-oracle); (2) инкремент `markets_accepted`; (3) обновить `last_active_time`;
+ (4) запуск авто-аллокации Lazy Pool (если у пула есть свободный баланс **и** `liquidity_fee_percent
+ рынка ≥ pm_lazy_min_liquidity_fee_percent`).
+- **Отклонение** (status 0 → -1): сид-ликвидность возвращена создателю; без vop.
+- **Экспирация** (status 0 → -1): если ничего не произошло к `accept_deadline`, крон каждого блока
+ аннулирует рынок, возвращает сид-ликвидность (**не** комиссию за создание) и эмитит `pm_market_expired`.
+
+### Audit Trail
+
+Каждое state-changing действие — это операция консенсуса или виртуальная операция, навсегда записываемая в
+block log и запрашиваемая через `account_history`. Ставки, отмены, добавление/вывод ликвидности,
+accept/reject, резолюция, спор, разрешение спора, выплата и штраф появляются как `pm_*`-операции/vop,
+вместе с резервами рынка, которых они касаются.
+
+---
+
+## 5. Onix Binary: Constant Product Market Maker
+
+### Инвариант
+
+```
+k = reserve_a × reserve_b
+```
+
+`k` меняется только при операциях добавления/вывода ликвидности.
+
+### Размещение ставки (сторона A)
+
+```
+new_reserve_b = reserve_b + amount
+new_reserve_a = floor(k / new_reserve_b)
+tokens_received = reserve_a − new_reserve_a
+price = amount × 1,000,000 / tokens_received
+```
+
+Симметрично для стороны B (поменять a/b).
+
+### Защита от слиппеджа
+
+Опциональный параметр `min_tokens` в `place-bet`. Если `tokens_received < min_tokens`, транзакция
+отвергается.
+
+### Инициализация рынка
+
+```
+reserve_a = floor(liquidity / 2)
+reserve_b = liquidity − reserve_a
+k = reserve_a × reserve_b
+```
+
+Минимальная начальная ликвидность: 100,000 mVIZ (100 VIZ).
+
+### Семантика веса (токена)
+
+- `weight` = число токенов исхода, полученных беттером (задаётся CPMM на момент ставки)
+- `weight` — это **относительная претензия**, не выплата в VIZ. Расчёт **parimutuel** (идентично Onix
+ Multi): победители получают назад стейк плюс пропорциональную долю пула проигравших, по весу.
+- Если ставка на сторону A и выигрывает исход A: `payout = bet_amount + (weight / total_winning_weight) × winners_pool − time_penalty_on_profit`
+- Если исход A проигрывает: payout = 0 (стейк форфейтится в пул победителей)
+
+CPMM — это **движок ценообразования** (вероятность + назначение веса); он больше не гейтит выплату. Это
+заставляет два типа рынков разделять одну модель расчёта: *AMM назначает веса (CPMM для бинарных, LMSR
+для мульти); проигравшие финансируют победителей пропорционально весу.*
+
+### Отображение цены
+
+```
+implied_probability_A = reserve_b / (reserve_a + reserve_b) × 100%
+implied_probability_B = reserve_a / (reserve_a + reserve_b) × 100%
+```
+
+### Гарантия принципала LP (доказательство)
+
+При parimutuel-расчёте гарантия точна и не опирается на геометрию кривой:
+
+```
+Money OUT = L (LP principal) + Σ(winning bet_amount) + winners_pool + fees
+ = L + winning_bets + (losers_sum − fees) + fees
+ = L + winning_bets + losing_bets = L + all_bets = Money IN
+```
+
+Суммарная выплата ограничена `losers_sum` независимо от весов, поэтому принципал LP `L` возвращается
+безусловно, а победители фондируются целиком проигравшими. (Легаси AM-GM граница
+`reserve_a + reserve_b ≥ L` больше не нужна для платёжеспособности; она остаётся свойством ценовой
+кривой.)
+
+---
+
+## 6. Onix Multi: LMSR с parimutuel-расчётом
+
+### Функция цены (softmax)
+
+Для N исходов с параметрами количества q_1, ..., q_N и параметром ликвидности b:
+
+```
+price(i) = exp(q_i / b) / Σ_j exp(q_j / b)
+```
+
+**Инвариант:** `Σ_i price(i) = 1` (по определению softmax).
+
+### Функция стоимости
+
+```
+C(q) = b × ln(Σ_j exp(q_j / b))
+```
+
+Стоимость покупки Δ токенов на исход i:
+
+```
+cost = C(q + Δ·e_i) − C(q)
+ = b × [ln(Σ_j exp(q'_j / b)) − ln(Σ_j exp(q_j / b))]
+where q'_i = q_i + Δ, all other q'_j = q_j
+```
+
+Численная устойчивость (трюк log-sum-exp):
+
+```
+ln(Σ exp(x_j)) = max(x) + ln(Σ exp(x_j − max(x)))
+```
+
+### Параметр ликвидности
+
+```
+b = S / ln(N)
+```
+
+где S = депозит субсидии LP, N = число исходов.
+
+### Расчёт (на резолюции)
+
+```
+1. Oracle declares winning outcome
+2. losers_sum = Σ bet_amount for all non-winning bets
+3. oracle_fee = floor(losers_sum × oracle_fee_percent / 10000)
+4. creator_fee = floor(losers_sum × creator_fee_percent / 10000)
+5. liq_fee = floor(losers_sum × liquidity_fee_percent / 10000)
+6. winners_pool = losers_sum − oracle_fee − creator_fee − liq_fee
+7. For each winning bettor:
+ payout = bet_amount + (their_tokens / total_winning_tokens × winners_pool) − time_penalty
+8. LP subsidy returned unconditionally
+9. LP earns time-weighted share of liq_fee
+```
+
+### Гарантия принципала LP (доказательство)
+
+1. LP вносит S VIZ как субсидию. Это задаёт b = S / ln(N).
+2. Во время ставок пользователи платят VIZ → получают токены. VIZ накапливается как пул ставок.
+3. На резолюции: проигравшие форфейтят 100% → `losers_sum`. Победителям платят из `losers_sum` (не из
+ субсидии).
+4. Субсидия LP S возвращается **безусловно** — она архитектурно отделена от потока выплат.
+
+### Граничные случаи
+
+| Сценарий | Исход |
+|----------|---------|
+| Все ставки на выигрышный исход | `losers_sum=0`, `winners_pool=0`. Каждый беттер получает назад `bet_amount`. Субсидия LP возвращена. |
+| Нет ставок на выигрышный исход | `losers_sum=total_bets`. Нераспределённый `winners_pool` → бонус LP. |
+| Рынок с нулевым объёмом | Субсидия LP возвращена. Ни комиссий, ни выплат. |
+| Выигрывает единственный беттер | Беттер получает `bet_amount + winners_pool`. Субсидия LP возвращена. |
+
+### Операции
+
+| Операция | Описание |
+|-----------|-------------|
+| `pm_create_market_multi { oracle, outcomes, liquidity, fees, ... }` | Создать рынок с N исходами |
+| `pm_place_bet_multi { market, outcome_index, amount, min_tokens }` | Купить токены исхода |
+| `pm_cancel_bet_multi { bet_id, min_return }` | Продать токены назад через reverse LMSR |
+| `pm_add_liquidity_multi { market, amount }` | Добавить субсидию LP (увеличивает b) |
+| `pm_withdraw_liquidity_multi { liquidity_id }` | Вывести субсидию LP (мин. floor навязан) |
+| `pm_resolve_multi { market, winning_outcome }` | Оракул объявляет победителя, запускает расчёт |
+
+Бинарные рынки (N=2) используют Onix Binary (CPMM). LMSR используется только для N > 2.
+
+---
+
+## 7. Структура комиссий
+
+### Вычисление комиссий на момент резолюции
+
+Все процентные комиссии считаются на резолюции от **общего объёма проигравшей стороны**:
+
+```
+losers_sum = Σ bet_amount for all losing bets
+
+oracle_fee = floor(losers_sum × oracle_fee_percent / 10000)
+creator_fee = floor(losers_sum × creator_fee_percent / 10000)
+liquidity_fee = floor(losers_sum × liquidity_fee_percent / 10000)
+winners_pool = losers_sum − oracle_fee − creator_fee − liquidity_fee
+```
+
+Комиссии НЕ удерживаются со ставок при размещении. Полная сумма ставки входит в резервы CPMM/LMSR.
+
+### Фиксированная комиссия оракула
+
+Разовая комиссия на рынок. Задаётся оракулом в профиле. Платится создателем оракулу при акцепте рынка.
+Полностью пропускается для self-oracle рынков (балансовой операции не происходит).
+
+### Поля учёта комиссий
+
+- `oracle_fee_earned` — не используется на резолюции; комиссия считается из losers_sum
+- `liquidity_fee_earned` — накопленные комиссии LP, уже выплаченные рано вышедшим LP; на резолюции: `LP fee pool = max(0, floor(losers_sum × liquidity_fee_percent / 10000) − liquidity_fee_earned) + penalty_pool`
+- Per-bet `oracle_fee` и `liquidity_fee` записываются для аудита; не аккумулируются на рынке
+
+### Округление
+
+Все вычисления используют `floor()`. Нераспределённый dust (< 1 mVIZ) отправляется в фонд DAO при
+финальной выплате.
+
+---
+
+## 8. Time penalty для поздних ставок
+
+### Окно штрафа
+
+| Тип | Вычисление окна |
+|------|-------------------|
+| Fixed (type=0) | `penalty_window = time_penalty_value` секунд до экспирации |
+| Percentage (type=1) | `penalty_window = time_penalty_value / 100 × (betting_expiration − market_creation_time)` |
+
+### Вычисление штрафа
+
+```
+time_to_expiration = betting_expiration − current_time
+
+if time_to_expiration < penalty_window:
+ ratio = 1 − (time_to_expiration / penalty_window)
+
+ if penalty_curve_type == 1: // quadratic
+ penalty_ratio = ratio × ratio
+ else: // linear
+ penalty_ratio = ratio
+
+ time_penalty = floor(penalty_ratio × max_time_penalty)
+else:
+ time_penalty = 0
+```
+
+### Применение при выплате (только к прибыли)
+
+```
+profit = floor(winners_pool × weight / total_winning_weight) // parimutuel share of losers' pool
+penalty_deduction = floor(profit × time_penalty / 1,000,000)
+net_payout = bet_amount + profit − penalty_deduction
+```
+
+**Инвариант:** `net_payout ≥ bet_amount` — штраф применяется только к доле прибыли, поэтому победители
+всегда получают не меньше принципала. (Идентично для Onix Binary и Onix Multi.)
+
+---
+
+## 9. Предоставление ликвидности
+
+### Добавление ликвидности
+
+```
+add_a = amount × reserve_a / (reserve_a + reserve_b)
+add_b = amount − add_a
+new_reserve_a = reserve_a + add_a
+new_reserve_b = reserve_b + add_b
+new_k = new_reserve_a × new_reserve_b
+```
+
+Записывает `sec_to_expiration = betting_expiration − current_time` на момент депозита.
+
+### Time-weighted распределение комиссий (на резолюции)
+
+```
+fee_pool = remaining_liquidity_fee + total_penalty_pool
+
+weight_i = amount_i × max(1, sec_to_expiration_i)
+total_weight = Σ weight_i
+fee_share_i = floor(fee_pool × weight_i / total_weight)
+lp_payout_i = principal_i + fee_share_i
+```
+
+Каждый депозит — независимая позиция. Несколько депозитов одного пользователя отслеживаются раздельно.
+
+### Ранний вывод
+
+**Предусловия:** market status=1, `time < betting_expiration`, `resulting liquidity_sum ≥ 100,000 mVIZ`.
+
+```
+// Fractional withdrawal
+fraction = withdraw_amount / lp_amount
+withdraw_weight_a = floor(weight_a × fraction)
+withdraw_weight_b = floor(weight_b × fraction)
+
+// Reverse reserves
+new_reserve_a = reserve_a − withdraw_weight_a
+new_reserve_b = reserve_b − withdraw_weight_b
+new_k = new_reserve_a × new_reserve_b
+
+// Time-ratio discount
+time_served = current_time − lp_deposit_time
+market_duration = betting_expiration − market_creation_time
+time_ratio = min(1, time_served / market_duration)
+
+// Fee share (conservative: min of both sides)
+fee_from_a = floor(a_bets_sum × liquidity_fee_percent / 10000)
+fee_from_b = floor(b_bets_sum × liquidity_fee_percent / 10000)
+estimated_pool = min(fee_from_a, fee_from_b) − already_paid_to_early_lps
+lp_tw = withdraw_amount × max(1, sec_to_expiration)
+total_tw = Σ (active LP time-weights)
+raw_fee_share = floor(estimated_pool × lp_tw / total_tw)
+fee_share = floor(raw_fee_share × time_ratio)
+
+returned = withdraw_amount + fee_share
+```
+
+**Post-expiration lock:** Вывод LP блокируется при `time ≥ betting_expiration`. Все позиции LP заблокированы
+до резолюции.
+
+### Безопасность принципала при раннем выводе
+
+Вывод вычитает исходные `weight_a` и `weight_b` (не пропорциональную долю текущих резервов). Если
+`reserve_a < weight_a` или `reserve_b < weight_b`, вывод **блокируется**.
+
+### Создатель как первый LP
+
+Создатель рынка автоматически первый LP. Его `sec_to_expiration` равен полной длительности рынка, давая
+максимальный time-weight.
+
+---
+
+## 10. Резолюция и выплаты
+
+### Приоритет выплат
+
+| Приоритет | Тип | Получатель | Сумма |
+|----------|------|-----------|--------|
+| 1 | Oracle fee (2) | Оракул | `floor(losers_sum × oracle_fee_percent / 10000)` |
+| 1.5 | Creator fee (7) | Создатель | `floor(losers_sum × creator_fee_percent / 10000)` |
+| 2 | Creator LP (1) | Создатель | `principal + time-weighted fee share` |
+| 3 | LP return (1) | LP | `principal + time-weighted fee share` |
+| 4 | Winner bets (0) | Победители | `bet_amount + floor(winners_pool × weight / total_winning_weight) − penalty_deduction` (parimutuel) |
+| 5 | Dispute refund (5) | Участники спора | (если применимо) |
+| 6 | Oracle penalty bonus (6) | Все участники | (если оракул оштрафован) |
+
+### Проигравшая сторона
+
+Выплата = 0. Стейки поглощены в резервный пул.
+
+### Рынки с нулевым объёмом
+
+LP получают полный принципал. Оракул получает фикс. комиссию (если есть). Все аккумуляторы комиссий
+остаются 0.
+
+---
+
+## 11. Отмена ставки
+
+### Предусловия
+
+| Условие | Проверка |
+|-----------|-------|
+| Ставка активна | `bet.status == 0` |
+| Пользователь владеет ставкой | `bet.user == current_user.id` |
+| Рынок активен | `market.status == 1` |
+| Приём открыт | `current_time < market.betting_expiration` |
+| Отмена разрешена | `market.allow_cancellation == 1` |
+
+### Механика reverse CPMM
+
+Для ставки на сторону A (side=0):
+
+```
+new_reserve_a = reserve_a + tokens
+new_reserve_b = floor(k / new_reserve_a)
+amount_returned = reserve_b − new_reserve_b
+if amount_returned <= 0: amount_returned = 0
+```
+
+Симметрично для стороны B.
+
+### Защита от слиппеджа
+
+Опциональный параметр `min_return`. Если `amount_returned < min_return`, транзакция отвергается.
+
+### Изменения состояния (атомарно)
+
+1. Статус ставки → 1 (отменена), записан `returned_amount`
+2. Резервы рынка обновлены
+3. Суммы ставок рынка уменьшены на исходную сумму ставки
+4. Баланс пользователя увеличен на `amount_returned`, `bets_balance` уменьшен
+5. Запись истории (type=4)
+6. Запись market log с резервами до/после
+
+---
+
+## 12. Система споров
+
+### Предусловия подачи
+
+- Подающий разместил ставку на рынке
+- В течение `pm_dispute_grace_sec` после резолюции
+- Маршрутизация по режиму: **комитет** (`dispute_mode = 0`) — аккаунт-резолвер не нужен, электорат SHARES голосует через `pm_dispute_vote` (публично, изменяемо до `voting_end_time`, вес = `effective_vesting_shares` + стейк Lazy-Pool→shares), подсчёт кроном `pm_dispute_finalize`; **аккаунт** (`dispute_mode = 1`) — именованный `dispute_resolver` рынка выносит `pm_dispute_resolve`
+- Нет открытого спора на рынке
+- Подающий платит `pm_dispute_fee`
+
+### Ответ оракула
+
+Обязателен в течение `pm_oracle_dispute_response_sec`. При пропуске `pm_dispute_fee` авто-слешится из
+страховки и записывается на объект оракула.
+
+Оракул публикует свой контраргумент через **`pm_dispute_oracle_respond`** (op ID 98). Поскольку спор — это
+открытое публичное слушание, текст хранится **на объекте спора** (`oracle_response` + `oracle_response_time`,
+читается через `get_dispute`), чтобы каждый голосующий комитета или аккаунт-резолвер мог взвесить его перед
+решением. Отвечать может только оракул рынка, только пока спор открыт и `now ≤ oracle_response_deadline`;
+повторная публикация перезаписывает предыдущий ответ.
+
+### Жизненный цикл спора
+
+```
+Resolution (T=0) → Grace period (T to T+12h) → Dispute filed (T≤12h)
+ → Oracle response (12h window) → Resolver decision (up to 14 days)
+ → After verdict: recalculate or unfreeze → Auto-payout after new grace period
+ → Auto-close fallback (T+14 days): full refund + oracle penalty
+```
+
+### Спор удовлетворён (оракул неправ — переворот)
+
+Награда диспутёру — carve-out из слэша; **остаток слэша финансирует победивших бетторов** (через
+`forfeit_pool`), не резолвера и не ДАО. **Ни голосующие комитета, ни аккаунт-резолвер не оплачиваются.**
+
+```
+reward_target = floor(dispute_fee × pm_dispute_reward_multiplier / 10000) // bp; 30000 = ×3
+bonus = max(0, reward_target − dispute_fee), ограничен слэшем
+
+1. Disputer ← dispute_fee (escrow назад) + bonus // bonus берётся из слешнутой страховки
+2. forfeit_pool += (slash − bonus) // → победителям, через winners_pool на сеттлменте
+```
+
+Размер слэша: режим комитета масштабирует `dispute_penalty_percent` оракула на `consensus_strength`;
+режим аккаунта берёт `penalty_amount` резолвера (оба ограничены остатком страховки).
+
+### Спор отклонён (оракул прав — подтверждён)
+
+```
+Disputer отдаёт всю dispute_fee → оракулу (100%, компенсация). // без 50/50 резолвер/ДАО
+```
+
+### Процесс пересчёта (оракул неправ)
+
+1. Валидировать штраф (ограничен оставшейся страховкой)
+2. Выплачена награда диспутёру (fee + bonus); остаток слэша → `forfeit_pool` (победителям)
+3. Слешинг страховки оракула
+5. Применены баны (если запрошены)
+6. Удалить все существующие невыплаченные payouts
+7. Перевернуть выигрышный исход (A↔B)
+8. Заново сгенерировать выплаты с исправленным исходом
+9. Записан audit trail
+
+### Полномочия резолвера — баны это compliance/регуляторная функция (только режим аккаунта)
+
+Санкции ниже — это поля **аккаунт-режимной** `pm_dispute_resolve` (op ID 80), выносимой именованным
+`dispute_resolver` рынка. Когда этот резолвер — **регулятор или лицензированный арбитр**, именно так он
+принуждает к off-chain-правилам: одним вердиктом он может слешить страховку **и забанить как оракула, так и
+создателя рынка** на платформе, временно или навсегда. **Режим комитета/DAO (`dispute_mode = 0`) по замыслу
+не имеет полномочий бана** — публичное слушание только слешит страховку (масштабируемую силой консенсуса) и
+корректирует репутацию через `pm_dispute_finalize`; оно никогда не банит.
+
+| Параметр | Тип | Описание |
+|-----------|------|-------------|
+| `penalty_amount` | mVIZ | Дополнительный слеш страховки (0 до остатка) → фонд DAO |
+| `ban_oracle` | 0/1 | Забанить оракула |
+| `ban_oracle_until` | unix ts / 0 | 0=навсегда, >0=истекает |
+| `ban_creator` | 0/1 | Забанить создателя |
+| `ban_creator_until` | unix ts / 0 | 0=навсегда, >0=истекает |
+
+Бан записывает выносящего `resolver` в поле `banned_by` цели. **Снятие бана:** тот же резолвер может снять
+его **досрочно** через **`pm_unban`** (op ID 99, `unban_oracle` / `unban_creator`); иначе он просто истекает
+на `banned_until`, после чего per-block крон очищает его и эмитит виртуальную операцию **`pm_ban_expired`**
+(ID 100), чтобы история/индексаторы наблюдали снятие.
+
+### Авто-закрытие (14-дневный fallback)
+
+| Действие | Описание |
+|--------|-------------|
+| Истец | Dispute fee возвращена |
+| Оракул | `dispute_fee` слешится из страховки |
+| Ставки | Все возвращены (исходные суммы) |
+| LP | Все возвращены (только принципал) |
+| Распределение штрафа | Слешнутая сумма распределена пропорционально всем участникам |
+| Статус спора | Установлен в 3 (авто-закрыт) |
+
+### Объявление No-Contest
+
+Оракул вызывает `oracle-no-contest` с `market_id` и `reason`.
+
+1. Все ставки → pending refund payouts (полная исходная сумма)
+2. Все позиции LP → pending refund payouts (только принципал)
+3. Штраф: `oracle_no_contest_penalty_percent`% от `dispute_fee` из страховки
+4. Штраф распределён пропорционально участникам
+5. Рынок: `resolved_outcome = -1`, `payout_status = 1`
+6. Стартует grace-период (оспариваемый)
+
+### Резолюция с 3 исходами (спор no-contest)
+
+Резолвер выбирает одно из:
+- `correct_outcome = 0` — A побеждает (пересчёт выплат)
+- `correct_outcome = 1` — B побеждает (пересчёт выплат)
+- `correct_outcome = -1` — Подтвердить no-contest (сохранить refund-выплаты)
+
+Если оракул неправ: pending refund payouts удаляются, заменяются корректными выплатами победителям.
+Применяются стандартные штрафы спора.
+
+---
+
+## 13. Штраф оракула за пропуск резолюции
+
+Если оракул не разрешает к `result_expiration`:
+
+```
+penalty_amount = floor(oracle_insurance × oracle_penalty_percent / 100)
+```
+
+### Распределение
+
+```
+stakes[user_id] += bet_amount (for each active bet)
+stakes[user_id] += liquidity_amount (for each active LP position)
+total_stakes = Σ stakes[user_id]
+
+bonus_i = floor(penalty_amount × stakes[user_id] / total_stakes)
+```
+
+Каждый участник получает: полный рефанд (принципал) + пропорциональный бонус.
+
+Рынок финализирован: status=3, payout_status=2.
+
+---
+
+## 14. Скоринг репутации оракула
+
+### Сырые метрики (14 счётчиков на оракула)
+
+| Метрика | Тип | Источник |
+|--------|------|--------|
+| `markets_accepted` | counter | oracle-accept-market |
+| `markets_resolved` | counter | resolve-market |
+| `markets_no_contest` | counter | oracle-no-contest |
+| `markets_missed` | counter | cron (missed deadline) |
+| `disputes_received` | counter | create-dispute |
+| `disputes_lost` | counter | resolve-dispute (status=1) |
+| `disputes_won` | counter | resolve-dispute (status=2) |
+| `disputes_auto_closed` | counter | cron (14-day auto-close) |
+| `dispute_responses_missed` | counter | cron (12h response deadline) |
+| `total_volume_resolved` | mVIZ | resolve-market (сумма bets_sum) |
+| `total_insurance_slashed` | mVIZ | все события штрафов |
+| `avg_resolution_time` | seconds | resolve-market |
+| `bans_received` | counter | resolve-dispute |
+| `active_since` | timestamp | register-oracle |
+| `last_active_time` | timestamp | accept/resolve/no-contest |
+
+### Производные доли
+
+Знаменатель: `total_outcomes = markets_resolved + markets_no_contest + markets_missed`
+
+| Доля | Формула |
+|------|---------|
+| `resolution_rate` | `markets_resolved / total_outcomes` |
+| `dispute_loss_rate` | `disputes_lost / disputes_received` |
+| `no_contest_rate` | `markets_no_contest / total_outcomes` |
+| `deadline_miss_rate` | `markets_missed / total_outcomes` |
+| `dispute_response_rate` | `1 − (dispute_responses_missed / disputes_received)` |
+
+### Reliability Score (0–100)
+
+```
+reliability_score = clamp(0, 100,
+ BASE_SCORE
+ − W_DISPUTE_LOSS × dispute_loss_rate × 100
+ − W_NO_CONTEST × excess_no_contest × 100
+ − W_DEADLINE_MISS × deadline_miss_rate × 100
+ − W_NO_RESPONSE × (1 − dispute_response_rate) × 100
+ + W_VOLUME_BONUS × volume_tier
+ + W_EXPERIENCE × experience_tier × freshness_multiplier
+ − W_BAN_PENALTY × bans_received
+)
+```
+
+Где `excess_no_contest = max(0, no_contest_rate − 0.10)`.
+
+### Веса по умолчанию
+
+| Вес | Значение |
+|--------|-------|
+| BASE_SCORE | 50 |
+| W_DISPUTE_LOSS | 0.40 |
+| W_NO_CONTEST | 0.10 |
+| W_DEADLINE_MISS | 0.20 |
+| W_NO_RESPONSE | 0.15 |
+| W_VOLUME_BONUS | 0–25 (тиры: ≥10K→+5, ≥100K→+10, ≥500K→+15, ≥1M→+20, ≥5M→+25) |
+| W_EXPERIENCE | 0–25 (тиры: ≥7d→+5, ≥30d→+10, ≥90d→+15, ≥180d→+20, ≥365d→+25) |
+| W_BAN_PENALTY | 15 за бан |
+
+### Freshness Decay
+
+| Дней с последней активности | Множитель |
+|----------------------|-----------|
+| ≤ 30 | 1.00 |
+| 31–90 | 0.75 |
+| 91–180 | 0.50 |
+| > 180 | 0.25 |
+
+### Composite Trust Score
+
+```
+trust_score = reliability_score × risk_factor
+```
+
+| Risk score (страховка/ставки) | risk_factor |
+|---------------------------|-------------|
+| ≥ 3.0× | 1.00 |
+| ≥ 2.0× | 0.95 |
+| ≥ 1.0× | 0.85 |
+| < 1.0× | 0.70 |
+
+### Детекция нового оракула
+
+`total_outcomes < 5` → `is_new = true`. Отдельный бейдж в UI.
+
+Score считается на чтение через `compute_oracle_reliability_score()`, не хранится.
+
+---
+
+## 15. Трансфер позиций
+
+### Операция
+
+```
+pm_transfer_position { bet_id, to_user, amount, memo }
+```
+
+- Перевести все или часть токенов ставки на другой аккаунт
+- Переведённые токены сохраняют исходный рынок и исход
+- Выплата идёт текущему держателю на резолюции
+- Без слиппеджа, без рыночного влияния — чистая переуступка записи
+- Работает и для позиций Onix Binary, и Onix Multi
+
+### Модель приватности memo
+
+| Режим | Формат | Видимость |
+|------|--------|------------|
+| Plaintext | Строка, не начинающаяся с `#` | Публично on-chain |
+| Encrypted | Строка, начинающаяся с `#` | Приватно — расшифровать могут только отправитель и получатель |
+
+Шифрование: ECIES shared-secret `ECDH(sender_memo_private, recipient_memo_public)` с использованием
+memo-ключей аккаунтов VIZ (стандартная модель Graphene). Шифрование/расшифровка на стороне клиента.
+
+---
+
+## 16. Lazy-пул ликвидности
+
+### Параметры
+
+| Медиана-голосуемый параметр | Роль |
+|---------|-------------|
+| `pm_lazy_pool_enabled` | kill-switch пула |
+| `pm_lazy_alloc_percent` | доля свободного баланса, аллоцируемая на рынок (bp) |
+| `pm_lazy_max_total_alloc_percent` | кап доли пула на активных рынках (bp) |
+| `pm_lazy_recall_step_percent` | шаг graduated-recall на простаивающих рынках (bp) |
+| `pm_lazy_lock_sec` | lock-период депозита (секунды) |
+| `pm_lazy_emergency_penalty_percent` | штраф на заблокированную прибыль при экстренном выводе (bp) |
+| `pm_lazy_min_liquidity_fee_percent` | минимальный `liquidity_fee_percent` рынка (bp) для аллокации пула; ниже него рынок не получает ликвидности пула (порог вознаграждения) |
+| `pm_min_liquidity` | мин. аллокация на рынок (он же пол сида рынка) |
+
+### Депозит
+
+- Первый депозитор: `shares = amount`
+- Последующие: `new_shares = amount × total_shares / free_balance`
+- Таймер блокировки: `unlock_time = now + pm_lazy_lock_sec`
+- Расчёт наград перед вычислением долей: `pending += shares × (pool.rps − user.snapshot) / PRECISION`
+
+### Авто-аллокация
+
+На активации рынка (status → 1):
+
+```
+alloc_amount = free_balance × allocation_percent / 100
+× (1 − active_market_penalty_pct / 100) ^ oracle_active_market_count
+× (1 − fault_penalty_pct / 100) ^ oracle_active_fault_stamps
+```
+
+Гейт порога вознаграждения (проверяется первым): если у рынка `liquidity_fee_percent <
+pm_lazy_min_liquidity_fee_percent`, пул не аллоцирует **ничего** — он субсидирует только рынки, чья
+LP-комиссия платит ему достаточно. Единственной ликвидностью остаётся собственный сид создателя.
+
+Проверки: `alloc_amount ≥ min_market_allocation`, `allocated + alloc_amount ≤ total × max_total_allocation / 100`.
+
+Pool LP вставляется с `user=0`. Участвует идентично в time-weighted распределении комиссий.
+
+### Распределение наград (ленивый учёт)
+
+На резолюции рынка с прибылью pool LP:
+
+```
+profit = lp_return − allocation_amount
+if profit > 0 AND total_shares > 0:
+ pool.reward_per_share += profit × PRECISION / total_shares
+```
+
+Награда пользователя (считается на чтение):
+
+```
+live_reward = pending_rewards + shares × (pool.rps − user.snapshot) / PRECISION
+```
+
+### Плановый вывод
+
+Из консолидированной разблокированной записи (полный или частичный):
+
+```
+1. Run unlock consolidation
+2. Settle rewards: pending += shares × (rps − snapshot) / PRECISION
+3. Share value = shares_to_burn × free_balance / total_shares
+4. Reward portion = pending_rewards × withdraw_percent / 100
+5. Total payout = share value + reward portion
+```
+
+### Экстренный вывод
+
+Все депозиты (locked + unlocked):
+
+```
+1. Settle rewards
+2. total_value = shares × free_balance / total_shares + pending_rewards
+3. profit = total_value − principal_deposited
+4. if profit > 0: penalty = profit × (locked_shares / total_shares) × emergency_penalty / 100
+5. Penalty → pool reward_per_share
+6. User receives: total_value − penalty
+```
+
+### Защита от opportunity-cost
+
+> **Governance vs хардкод.** Медиана-голосуется только **размер шага** отзыва —
+> `pm_lazy_recall_step_percent`. Остальное **захардкожено** (нужен хардфорк): деление на **10 шагов**
+> (`window/10`, `check_step ≥ 10`), критерий простоя (шаг простаивает, если *нет новых ставок* с
+> прошлой проверки — `bets_sum ≤ bets_sum_at_check`) и **штраф 5% за активный рынок** (`alloc × 95/100`).
+> Штраф fault-штампа и его окно истечения тоже захардкожены.
+
+**A. Graduated Recall:** Длительность рынка делится на **10 фиксированных шагов**. На каждом шаге, если с
+прошлой проверки **не пришло новых ставок**, отозвать `pm_lazy_recall_step_percent` (bp, governance)
+текущей аллокации в пул.
+
+**B. Active Market Penalty:** `factor = (1 − 5%) ^ active_market_count` — захардкоженное рекурсивное
+снижение на 5% за каждый одновременный активный рынок того же оракула.
+
+**C. Fault Stamps:** При плохих исходах рынка (no-contest, пропуск дедлайна, нулевой объём, проигрыш
+спора, отсутствие ответа, авто-закрытие) оракул получает fault-штамп, авто-истекающий после фиксированного
+окна чистой работы; каждый активный штамп дополнительно снижает аллокацию. (Размер штрафа и окно — хардкод.)
+
+---
+
+## 16a. Опциональное плечо (фондируется Lazy-пулом)
+
+Live с HF14; опционально, управляется медианным kill-switch `pm_leverage_enabled` (по умолчанию off).
+
+- **Open** (`pm_leverage_open`) — беттер вносит collateral; Lazy-пул **выдаёт займ** маржи из
+ `free_balance` (ограничено `leverage_fund_used`; проверяется против `pm_leverage_fund_percent`,
+ `…_max_per_position_bp`, `…_max_position_ratio_percent`, `…_min_market_liquidity` на момент открытия).
+ Без эмиссии токенов — позиция полностью обеспечена с точки зрения системы. Открытие с плечом **не**
+ создаёт `pm_bet`; вес кривой держится на `pm_leverage_position_object`.
+- **Liquidation** — идёт по **pre-bet резервам**, поэтому пул возвращает `min(cancel_value,
+ obligation) ≥ loan`: opposing-bet каскад (`pm_place_bet`) и settlement force-close всегда
+ full-recovery (заём + проценты → пул); **единственный** ограниченный путь bad-debt — same-side
+ `pm_cancel_bet` (Case B). Каскад **не** гейтится `pm_leverage_enabled` (флаг блокирует только новые
+ открытия), поэтому выключение плеча никогда не снимает защиту с открытых позиций.
+- **Виртуальные операции** — `pm_leverage_resolve` (force-close на расчёте, с исходом + плечом),
+ `pm_leverage_liquidate` (mid-market, `reason` 0 opposing / 1 cancel).
+- **Governance-вес** — депозитчики Lazy-пула сохраняют вес голоса в PM-спорах и DAO-комитете (NAV пула →
+ vesting-shares через `get_vesting_share_price`, гейт HF14).
+
+API: `get_account_leverage_positions`, `get_market_leverage_positions`, `get_lazy_pool`.
+
+## 16b. Batch / Commit-Reveal ставки (анти-MEV)
+
+Live с HF14 для **бинарных** рынков (мульти форсит `allow_instant_bet`, пока не появится LMSR-батч);
+опционально per-market (`allow_batch` / `allow_instant_bet`), медианный kill-switch
+`pm_commit_reveal_enabled`.
+
+- `pm_place_bet` с `mode = 1` ставит **batch**-ставку в очередь; `pm_commit_bet` (commitment hash +
+ escrow) → `pm_reveal_bet` запускает **commit-reveal** флоу. Нераскрытые commitments форфейтят
+ `pm_commit_no_reveal_penalty_percent` (bp) через `pm_commit_forfeit`.
+- На каждой границе эпохи (`pm_batch_epoch_blocks`, reveal-окно `pm_reveal_window_blocks`) ставки из
+ очереди сеттлятся по **единой цене** через крон `pm_batch_settle` — AMM двигает только нетто-остаток,
+ поэтому внутрибатчевый порядок не даёт преимущества, а инвариант `Σ reserve ≥ L` сохраняется.
+
+## 17. On-chain модель объектов
+
+Всё состояние живёт в **chainbase-объектах**, зарегистрированных как core-индексы на HF14 — никакой SQL-БД
+нет. Определения полей живут в заголовках операций/объектов и доступны
+на чтение через [плагин `prediction_market_api`](../plugins/prediction-market-api). Счётчики репутации,
+которые прототип держал в `users` table, теперь поля на `pm_oracle_object`.
+
+| Объект (индекс) | Хранит | Ищется по |
+|---|---|---|
+| `pm_oracle_object` | регистрацию оракула, страховку, 14 счётчиков репутации, fault-штампы, бан (`banned_until` + `banned_by`) | owner |
+| `pm_market_object` | конфиг рынка, CPMM-резервы (`reserve_a/b`, `k`), `*_fee_percent` (bp), `status` / `payout_status`, таймеры, `dispute_mode`, `a_bets_sum` / `b_bets_sum`, заявление оракула о разрешении (`decision_url` / `decision_reason`) | id / creator / oracle / result_expiration |
+| `pm_outcome_object` | per-outcome LMSR `q`, `bets_sum`, `bets_count` (мульти-рынки) | market + outcome |
+| `pm_bet_object` | ставку — account, `side` / `outcome_index`, `amount`, вес кривой `weight`, `time_penalty`, `status`, `mode` | market / account |
+| `pm_liquidity_object` | позицию LP — принципал, время депозита, time-weight; `provider` пуст ⇒ lazy-pool LP | market |
+| `pm_commit_object` | commitment-хэш + escrow (batch / commit-reveal) | market / account |
+| `pm_dispute_object` | спор — disputer, `proposed_outcome`, escrow комиссии, таймеры, `status`, `dispute_mode`, контраргумент оракула (`oracle_response` / `oracle_response_time`) | market |
+| `pm_dispute_vote_object` | один бюллетень комитета — voter, `vote_outcome`, `vote_percent` (изменяем до закрытия) | market + voter |
+| `pm_lazy_pool_object` | синглтон-пул — `free_balance` / `allocated_balance` / `earned_balance`, `reward_per_share`, `leverage_fund_used`, `total_shares` | синглтон (id 0) |
+| `pm_lazy_deposit_object` | депозитчика — shares, snapshot наград, unlock time | account |
+| `pm_lazy_allocation_object` | тихую LP-аллокацию пула в один рынок + состояние graduated-recall (`bets_sum_at_check`, `check_step`, `recalled_amount`) | market |
+| `pm_leverage_position_object` | открытую позицию с плечом — collateral, loan, obligation, вес кривой, `status` | account / market + status |
+| `pm_creator_ban_object` | забаненного создателя — `banned_until`, `ban_count`, `banned_by` | ban account |
+
+Метрики репутации считаются на чтение (`compute_oracle_reliability_score()` — §14), не хранятся. Все
+процентные поля — базисные пункты (`*_percent`, bp). Аллокации lazy-пула на рынок и состояние
+graduated-recall живут на `pm_lazy_allocation_object`; fault-штампы и счётчики репутации оракула — на
+`pm_oracle_object`.
+
+**Только в плагине (не консенсус):** `pm_market_meta_object` — off-chain распарсенные метаданные рынка
+(категория / теги / запрещённые юрисдикции) для discovery и фильтрации по юрисдикции; строится плагином
+[`prediction_market_api`](../plugins/prediction-market-api) из непрозрачной строки `metadata` рынка и
+никогда не участвует в консенсусе.
+
+Полные определения полей этих объектов см. в
+[Операциях прогнозных рынков](../protocol/operations/prediction-markets).
diff --git a/@l10n/ru/docs/prediction-markets/whitepaper.md b/@l10n/ru/docs/prediction-markets/whitepaper.md
new file mode 100644
index 0000000000..1f54c8e96d
--- /dev/null
+++ b/@l10n/ru/docs/prediction-markets/whitepaper.md
@@ -0,0 +1,807 @@
+---
+title: Onix Protocol — Whitepaper
+description: "Индустриальный whitepaper протокола Onix: прогнозные рынки с гарантированной ликвидностью на VIZ DLT."
+---
+
+# Onix Protocol: прогнозные рынки с гарантией LP на VIZ DLT
+
+**Индустриальный whitepaper**
+
+*Анатолий Пискунов (On1x)*
+*Версия 2.0 — июнь 2026 (on-chain / HF14)*
+
+---
+
+> **On-chain статус (HF14).** Этот документ изначально писался против централизованного прототипа.
+> Теперь протокол работает как **операции консенсуса первого класса (`pm_*`) на VIZ DLT**, проверено в
+> `consensus_sim`. Live с HF14: оба типа рынков (CPMM binary + LMSR multi), parimutuel zero-sum
+> расчёт, **Lazy Pool**, опциональный сабсистем **плеча**, опциональные **batch / commit-reveal
+> ставки** (бинарные), bonded-оракулы и двухрежимная система споров (комитет / аккаунт). Все
+> процентные параметры — в **базисных пунктах (bp): 10000 = 100.00%** (прототип использовал permille).
+> Разделы ниже снабжены пометками там, где живой дизайн отличается от исходного текста прототипа.
+
+## Аннотация
+
+Прогнозные рынки агрегируют рассеянную информацию в цены, давая оценки вероятностей, которые стабильно
+превосходят опросы, экспертные панели и статистические модели. Но внедрение упирается в одну структурную
+проблему: **поставщики ликвидности теряют деньги.**
+
+LP в Uniswap v3 страдают от impermanent loss. Маркет-мейкеры LMSR рискуют всей своей субсидией.
+Маркет-мейкеры CLOB сталкиваются с adverse selection. Каждая существующая модель просит поставщиков
+капитала принять downside-риск в обмен на неопределённую доходность — и данные показывают, что
+большинство из них теряет.
+
+**Onix Protocol** устраняет риск LP полностью. Это архитектура прогнозного рынка, где принципал LP
+**структурно гарантирован** — не страховкой, не хеджированием, а самой механикой выплат. Победителям
+платят исключительно из проигранных ставок. Капитал LP даёт глубину рынка, но никогда не используется для
+расчёта по ставкам.
+
+Документ описывает два типа рынков Onix — **Onix Binary** (Constant Product Market Maker) и **Onix Multi**
+(LMSR-ценообразование с parimutuel-расчётом) — а также архитектуру рынка, систему оракулов и разрешения
+споров, lazy-пул ликвидности, опциональное плечо, модель управления и реализацию на VIZ DLT как операций
+уровня консенсуса.
+
+---
+
+## 1. Проблема: риск LP в прогнозных рынках
+
+Любому прогнозному рынку нужна ликвидность. Без неё цены бессмысленны — ставка, двигающая рынок на 20%,
+раскрывает капитал беттера, а не мудрость толпы. Фундаментальный вопрос: **кто даёт эту ликвидность и чем
+рискует?**
+
+### 1.1 Текущий ландшафт
+
+| Платформа | Модель LP | Риск LP | Источник дохода |
+|----------|----------|---------|-------------|
+| **Uniswap v3** | Концентрированный AMM | Impermanent loss (часто >5% годовых; >50% LP v3 проигрывают buy-and-hold) | Торговые комиссии |
+| **Aave / Compound** | Lending-пул | Риск смарт-контракта, каскады ликвидаций | Проценты заёмщика |
+| **Curve** | Stableswap AMM | Низкий IL для привязанных активов, риск смарт-контракта | Комиссии + эмиссия CRV |
+| **Standard LMSR** | Субсидия маркет-мейкера | Убыток до `b × ln(N)` — вся субсидия | Bid-ask спред |
+| **Polymarket (CLOB)** | Активный маркет-мейкинг | Инвентарный риск, adverse selection | Bid-ask спред |
+| **Kalshi** | Концепции LP нет | N/A (биржевая модель) | N/A |
+
+Закономерность очевидна: предоставление ликвидности прогнозным рынкам требует либо навыка активного
+управления (CLOB), либо терпимости к потере капитала (LMSR), либо принятия impermanent loss (AMM). Ни
+одно из этого не подходит ритейл-участникам.
+
+### 1.2 Почему это важно
+
+Прогнозные рынки работают лучше всего, когда они глубокие и ликвидные. Глубокие рынки дают точные цены,
+привлекают информированных трейдеров и генерируют информационную ценность, делающую прогнозные рынки
+полезными как общественное благо. Но глубина требует капитала, а капитал требует компенсации за риск.
+
+Результат — проблема курицы и яйца:
+- Тонкие рынки → высокий слиппедж → плохой UX → мало бетторов → низкие комиссии → нет стимула для LP → тонкие рынки
+
+Чтобы разорвать этот цикл, нужно убрать риск со стороны LP. Если предоставление ликвидности безрисковое,
+барьер входа падает до нуля, и маховик может раскрутиться.
+
+---
+
+## 2. Onix Protocol
+
+### 2.1 Принципы дизайна
+
+Onix Protocol построен на трёх архитектурных инвариантах:
+
+1. **Принципал LP структурно безопасен.** Это не стратегия снижения риска — это свойство архитектуры
+ выплат. Капитал LP и расчёт по ставкам берутся из физически раздельных пулов.
+
+2. **Проигравшие финансируют победителей.** Все выплаты (прибыль победителей, комиссии оракула, комиссии
+ создателя, комиссии LP) берутся исключительно из проигранных ставок. Комиссии считаются на резолюции
+ как `floor(losers_sum × fee_bp / 10000)` (bp: 10000 = 100.00%), никогда не удерживаются при ставке.
+
+3. **Два типа рынков, одна гарантия.** Бинарные рынки (Onix Binary) и мульти-исходные рынки (Onix Multi)
+ используют разные формулы ценообразования, но разделяют одну модель расчёта и одну гарантию LP.
+
+### 2.2 Onix Binary (CPMM + parimutuel-расчёт)
+
+Onix Binary использует формулу Constant Product Market Maker — тот же инвариант `x * y = k`, что и
+Uniswap — как **движок ценообразования** для бинарных исходов, с **parimutuel-расчётом** (проигравшие
+финансируют победителей пропорционально весу), той же моделью расчёта, что и в Onix Multi.
+
+**Механика:**
+
+Рынок поддерживает два резерва, `reserve_a` и `reserve_b`, с постоянным произведением `k`:
+
+```
+k = reserve_a × reserve_b
+```
+
+Когда пользователь ставит `amount` на исход A (в реализации side 0 → `reserve_a`), стейк входит в резерв
+этой стороны, а токены берутся из **противоположного** резерва:
+
+```
+new_reserve_a = reserve_a + amount
+new_reserve_b = floor(k / new_reserve_a)
+tokens_received = reserve_b − new_reserve_b
+```
+
+`tokens_received` (называемые `weight`) — это **относительная претензия** пользователя на пул победителей,
+если выигрывает исход A (расчёт parimutuel — см. ниже, идентично Onix Multi). Подразумеваемая вероятность
+растёт для той стороны, на которую ставят (больше денег на A → `reserve_a` растёт → `P(A)` растёт):
+
+```
+P(A) = reserve_a / (reserve_a + reserve_b)
+P(B) = reserve_b / (reserve_a + reserve_b)
+```
+
+**Расчёт и доказательство безопасности LP (parimutuel):**
+
+На резолюции победители получают назад свой стейк плюс пропорциональную долю пула проигравших, по весу
+(идентично Onix Multi):
+
+```
+winners_pool = losers_sum − fees
+payout = bet_amount + (weight / total_winning_weight) × winners_pool − time_penalty_on_profit
+```
+
+Принципал LP `L` возвращается безусловно, и гарантия точна:
+
+```
+Money OUT = L + winning_bets + winners_pool + fees = L + winning_bets + losing_bets = L + all_bets = Money IN
+```
+
+Суммарная выплата ограничена `losers_sum` независимо от весов, поэтому капитал LP никогда не используется
+для расчёта по ставкам. CPMM — это **движок ценообразования** (вероятность + вес); он не гейтит выплату.
+(Соотношение AM-GM `reserve_a + reserve_b ≥ 2√k = L` по-прежнему держится для ценовой кривой, но на него
+больше не опираются для платёжеспособности.)
+
+**Разобранный пример:**
+
+```
+Setup: 200 VIZ liquidity → reserve_a = 100, reserve_b = 100, k = 10,000
+Fees (bp): oracle 50 (0.5%), creator 50 (0.5%), liquidity 100 (1%)
+
+Alice bets 50 VIZ on A → receives weight 33.33 (price moves from 50% to 69%)
+Bob bets 80 VIZ on B → receives weight 81.82
+
+Resolution: A wins
+ Losers (Bob): 80 VIZ forfeited → losers_sum = 80
+ oracle_fee = floor(80 × 50/10000) = 0.4 VIZ
+ creator_fee = floor(80 × 50/10000) = 0.4 VIZ
+ liq_fee = floor(80 × 100/10000) = 0.8 VIZ
+ winners_pool = 80 − 1.6 = 78.4 VIZ
+
+ Alice (only winner, weight 33.33 of 33.33):
+ payout = 50 (stake) + 78.4 × (33.33/33.33) = 128.4 VIZ (minus any time penalty on profit)
+ LP return: 200 VIZ principal + share of 0.8 VIZ fee pool
+```
+
+### 2.3 Onix Multi (LMSR + parimutuel-расчёт)
+
+Onix Multi — инновация протокола для рынков с 3–10 исходами. Он сочетает Logarithmic Market Scoring Rule
+Хэнсона (LMSR, 2003) для ценообразования в реальном времени с parimutuel-расчётом ради безопасности LP.
+
+**Ценообразование (LMSR softmax):**
+
+Для рынка с исходами {1, 2, ..., N}, каждый со своим параметром количества `q_i`:
+
+```
+price(i) = exp(q_i / b) / Σ_j exp(q_j / b)
+```
+
+Это функция softmax — цены всегда суммируются ровно в 1.0 by construction. Никакого механизма арбитража
+или операции split/merge не нужно.
+
+Стоимость покупки Δ токенов на исход i:
+
+```
+C(q) = b × ln(Σ_j exp(q_j / b))
+
+cost = C(q + Δ·e_i) − C(q)
+```
+
+Параметр `b` управляет чувствительностью цены (выше b = меньше price impact на ставку). Он фондируется
+субсидией LP: `b = S / ln(N)`, где S — суммарная субсидия.
+
+**Инновация — parimutuel-расчёт:**
+
+В **стандартном LMSR** маркет-мейкер — контрагент всех ставок. Если толпа верно предсказывает исход,
+маркет-мейкер теряет до `b × ln(N)` — потенциально всю субсидию. Именно поэтому LMSR мало внедряли вне
+корпоративных прогнозных рынков (Microsoft, Inkling), где оператор поглощает убыток.
+
+**Onix Multi меняет источник выплат.** На резолюции:
+
+```
+1. Oracle declares the winning outcome
+2. Losers forfeit 100% → losers_sum
+3. Fees deducted from losers_sum (bp; 10000 = 100.00%):
+ oracle_fee = floor(losers_sum × oracle_fee_bp / 10000)
+ creator_fee = floor(losers_sum × creator_fee_bp / 10000)
+ liq_fee = floor(losers_sum × liquidity_fee_bp / 10000)
+ winners_pool = losers_sum − fees
+4. Winners receive:
+ payout = bet_amount + (tokens / total_winning_tokens × winners_pool) − time_penalty
+5. LP subsidy returned unconditionally
+```
+
+Победителям платят проигравшие, а не LP. Субсидия архитектурно отделена от потока расчёта.
+
+**Доказательство гарантии принципала LP:**
+
+1. LP вносит `S` VIZ как субсидию, которая фондирует глубину рынка.
+2. Во время ставок пользователи платят VIZ → получают токены исходов. VIZ накапливается как пул ставок.
+3. На резолюции проигранные ставки фондируют выплаты победителям и комиссии. Субсидия `S` никогда не была
+ в платёжном пуле.
+4. Субсидия возвращается LP безусловно, независимо от исхода.
+
+**Сравнение:**
+
+| Измерение | Standard LMSR | Onix Multi |
+|-----------|---------------|------------|
+| Роль LP | Контрагент всех ставок | Депозит глубины (не контрагент) |
+| Макс. убыток LP | `b × ln(N)` (вся субсидия) | **Ноль** |
+| Выплата победителю | 1 токен = 1 единица валюты | Токен = пропорциональная претензия на пул проигравших |
+| Нужен ли CTF split/merge? | Да (обеспечить сумму цен = 1) | Нет (softmax гарантирует это) |
+
+**Разобранный пример (выборы с 3 исходами):**
+
+```
+Setup: b = 1000, outcomes = [A, B, C], subsidy = 1000 VIZ
+Initial: price(A) = price(B) = price(C) = 33.3%
+
+Alice bets 50 VIZ on A → ~47 tokens (price: 33% → ~38%)
+Bob bets 100 VIZ on B → ~88 tokens
+Carol bets 30 VIZ on C → ~29 tokens
+
+Resolution: A wins
+ Losers: Bob (100) + Carol (30) = 130 VIZ
+ Fees (200 bp = 2% total): 2.6 VIZ
+ winners_pool = 127.4 VIZ
+
+ Alice: 50 + (47/47 × 127.4) = 177.4 VIZ
+ LP: 1000 VIZ returned in full + share of liquidity fees
+```
+
+### 2.4 Граничные случаи
+
+| Сценарий | Исход |
+|----------|---------|
+| Все ставки на победителя | `losers_sum = 0` → каждый беттер получает назад ровно свою ставку. Субсидия LP возвращена. Zero-sum. |
+| Нет ставок на победителя | Весь пул проигравших нераспределён → бонус LP. LP в максимальном плюсе. |
+| Рынок с нулевым объёмом | Субсидия LP возвращена полностью. Ни комиссий, ни выплат. |
+| Выигрывает единственный беттер | Этот беттер получает `bet_amount + winners_pool`. Субсидия LP возвращена. |
+
+---
+
+## 3. Архитектура рынка
+
+### 3.1 Жизненный цикл рынка
+
+```mermaid
+stateDiagram-v2
+ direction LR
+ state "Waiting (0)" as Waiting
+ state "Active (1)" as Active
+ state "Closed (2)" as Closed
+ state "Resolved (3)" as Resolved
+ state "Deleted (-1)" as Deleted
+ state "Paid out" as Paid
+ [*] --> Waiting
+ Waiting --> Active: оракул принял
+ Waiting --> Deleted: оракул отклонил
+ Active --> Closed: ставки закрылись
+ Active --> Resolved: раннее разрешение (если разрешено)
+ Closed --> Resolved: оракул разрешает
+ Resolved --> Paid: окно ожидания (12ч)
+ Deleted --> [*]
+ Paid --> [*]
+```
+
+Рынки создаёт создатель рынка, проверяет и принимает оракул (ставящий страховку), они открыты для ставок,
+разрешаются исходом и выплачиваются после grace-периода для споров.
+
+### 3.2 Модель комиссий (извлечение только из проигравших)
+
+Отличительная черта Onix Protocol — **комиссии не удерживаются при ставке**. Полная сумма ставки входит в
+резервы рынка. Комиссии считаются только на резолюции, исключительно из проигранных ставок:
+
+```mermaid
+flowchart TD
+ LS["losers_sum (100% проигранных ставок)"]
+ LS --> OF["oracle_fee = floor(losers_sum × oracle_fee_bp / 10000)"]
+ LS --> CF["creator_fee = floor(losers_sum × creator_fee_bp / 10000)"]
+ LS --> LF["liquidity_fee = floor(losers_sum × liquidity_fee_bp / 10000)"]
+ LS --> WP["winners_pool = losers_sum − все комиссии"]
+```
+
+Это даёт структурную гарантию: комиссии и выплаты победителям берутся из совершенно раздельных источников.
+Извлечение комиссий никогда не конкурирует с обязательствами перед победителями.
+
+**Условия комиссии оракула фиксируются при акцепте (offer→quote).** Создатель публикует *максимум*,
+который оракул может взять (потолок `oracle_fee_percent` в bp + потолок `oracle_fixed_fee`); при акцепте
+оракул котирует свои фактические условия (≤ потолка создателя и ≤ медианного governance-капа
+`pm_max_oracle_fee_percent`), которые замораживаются на рынке, и эмитится виртуальная операция
+`pm_market_accepted`. Self-оракул фиксирует условия при создании. **Фиксированная комиссия оракула**
+(на рынок) выплачивается из остатка пула проигравших (никогда не печатается). **Комиссия за создание
+рынка** идёт в фонд DAO как защита от спама.
+
+### 3.3 Time-weighted распределение LP
+
+Доли комиссий LP распределяются пропорционально `amount × max(1, seconds_to_expiration)`:
+
+```
+weight_i = amount_i × max(1, sec_to_expiration_i)
+fee_share_i = floor(total_fee_pool × weight_i / Σ weight_j)
+```
+
+Ранние LP зарабатывают драматически больше на единицу капитала, чем поздние. В 48-часовом рынке LP,
+внёсший на 1-м часу, зарабатывает ~2400× больше на VIZ, чем внёсший на 47-м.
+
+Каждый депозит отслеживается как независимая позиция — несколько депозитов одного пользователя взвешиваются
+и оплачиваются раздельно. Принципал LP всегда возвращается полностью, независимо от исхода рынка.
+
+### 3.4 Time penalty для поздних ставок
+
+Чтобы дестимулировать ставки в последнюю минуту (несущие меньше риска неопределённости), к ставкам близ
+экспирации применяется настраиваемый time penalty:
+
+```
+if time_to_expiration < penalty_window:
+ ratio = 1 − (time_to_expiration / penalty_window)
+ penalty_ratio = ratio² // quadratic (default)
+ time_penalty = floor(penalty_ratio × max_penalty)
+```
+
+Штраф применяется **только к прибыли**, никогда к принципалу. Победивший беттер всегда получает не меньше
+своей исходной ставки. Квадратичная кривая мягкая в начале окна штрафа и крутая в конце, награждая
+«немного поздно» над «очень поздно».
+
+### 3.5 Трансфер позиций
+
+Позиции переводимы между аккаунтами через нативную операцию протокола:
+
+```
+pm_transfer_position { bet_id, to_user, amount, memo }
+```
+
+Без слиппеджа, без рыночного влияния — чистая переуступка записи. Поле `memo` поддерживает и открытый, и
+зашифрованный режим (ECIES через memo-ключи аккаунтов VIZ), позволяя P2P-сделки, OTC-торговлю и приватные
+аннотации.
+
+Это единственная фича композируемости из Conditional Tokens Framework (Polymarket/Gnosis), дающая реальную
+пользу. CTF split/merge архитектурно не нужен — формулы ценообразования Onix гарантируют когерентность цен
+by construction.
+
+---
+
+## 4. Оракул и разрешение споров
+
+### 4.1 Модель bonded-оракула
+
+Оракулы в Onix Protocol не доверены по умолчанию — они **bonded**. Каждый оракул обязан:
+
+- Зарегистрироваться с разовой комиссией (по умолчанию 10 VIZ)
+- Внести страховку (минимум 5000 VIZ)
+- Явно принимать рынки (ставя свою страховку на каждый акцепт)
+- Разрешать рынки исходом и подтверждающими доказательствами (decision URL)
+
+Страховой бонд создаёт подотчётность: оракулы, которые неверно разрешают, пропускают дедлайны или
+проигрывают споры, теряют часть страховки (slashing). Бонд должен превышать потенциальную прибыль
+манипуляции оракула, чтобы модель экономической безопасности держалась.
+
+Доход оракула из двух источников:
+1. **Фиксированная комиссия** (на рынок) — компенсирует ставку страховки и предоставление резолюции
+2. **Процентная комиссия** (из пула проигравших на резолюции) — масштабируется с объёмом рынка
+
+### 4.2 Арбитраж споров
+
+Любой беттер может оспорить резолюцию в течение grace-периода, заплатив dispute fee. Во время споров все
+выплаты заморожены.
+
+Разрешение идёт в одном из **двух per-market режимов**, выбираемом при создании:
+
+- **Режим комитета (`dispute_mode = 0`, по умолчанию)** — *весь электорат SHARES* решает
+ **stake-weighted голосованием** (`pm_dispute_vote`), детерминированно подсчитываемым кроном
+ `pm_dispute_finalize` на `voting_end_time`. Это **открытые публичные слушания**: живой тали
+ запрашиваем, и голоса **не** скрыты за commit-reveal (намеренный, постоянный выбор — DAO разрешает
+ споры максимально прозрачно). Поскольку по ходу слушаний всплывают новые доводы, **бюллетень изменяем**
+ до закрытия (повторный голос перезаписывает прежний). Вес голосующего — его `effective_vesting_shares`
+ **плюс стейк Lazy-Pool, конвертированный в vesting-shares**, поэтому участники DAO, паркующие VIZ в
+ пуле, сохраняют свой вес в управлении.
+- **Режим аккаунта (`dispute_mode = 1`)** — единственный именованный `dispute_resolver`
+ (рекомендуется multisig) выносит вердикт (`pm_dispute_resolve`).
+
+Логика вердикта в любом режиме:
+
+**Если оракул был неправ (переворот):**
+- Применяется верный исход, выплаты пересчитываются.
+- Диспутёр получает назад свою fee **плюс carve-out награды** из слешнутой страховки, размером
+ `dispute_fee × pm_dispute_reward_multiplier` (bp; напр. 30000 = ×3), ограниченный слэшем.
+- **Остаток слэша добавляется в пул победителей** (через `forfeit_pool`) — он идёт выигравшим бетторам,
+ **не** резолверу и **не** в ДАО. Ни голосующие комитета, ни аккаунт-резолвер награды **не получают**
+ (голосование комитета — неоплачиваемая governance-обязанность).
+- Страховка оракула слешится (масштаб — по силе консенсуса в режиме комитета или по `penalty_amount`
+ резолвера в режиме аккаунта), с опциональным баном.
+
+**Если оракул был прав (подтверждён):**
+- Диспутёр **отдаёт всю dispute fee оракулу** (компенсация за недобросовестное оспаривание).
+- Исходные выплаты идут без изменений.
+
+**Предотвращение denial-of-resolution:** Если резолвер не действует в течение 14 дней, споры
+авто-закрываются: все ставки и LP возвращаются, оракул штрафуется, fee диспутёра возвращается. Это
+гарантирует, что средства никогда не заморожены бессрочно.
+
+### 4.3 Скоринг репутации оракула
+
+Протокол отслеживает 14 on-chain метрик на оракула и считает reliability score (0–100):
+
+```
+reliability_score = clamp(0, 100,
+ 50 (base)
+ − 0.40 × dispute_loss_rate × 100
+ − 0.10 × excess_no_contest × 100
+ − 0.20 × deadline_miss_rate × 100
+ − 0.15 × (1 − dispute_response_rate) × 100
+ + volume_bonus (0–25)
+ + experience_bonus × freshness_multiplier (0–25)
+ − 15 × bans_received
+)
+```
+
+Ключевые дизайн-решения:
+- **Доли, а не счётчики** — 1 проигранный спор из 100 (1%) лучше, чем 1 из 2 (50%)
+- **Нейтральный старт на 50** — новые оракулы должны заработать репутацию, а не стартовать со 100
+- **Freshness decay** — неактивные оракулы со временем теряют experience-бонус
+- **Тиры объёма** — высокообъёмные оракулы получают бонусные очки за доказанный track record
+
+Reliability score сочетается с risk factor (отношение страховки к ставкам), давая **composite trust
+score** — основную метрику, показываемую пользователям.
+
+### 4.4 No-contest и резолюция с 3 исходами
+
+Оракул, который не может верифицировать исход, может добровольно объявить **no-contest**, запуская
+рефанды с пониженным штрафом (50% dispute fee из страховки — гораздо дешевле, чем проиграть спор). Это
+создаёт градиент стимулов:
+
+| Сценарий | Стоимость для оракула | Риск бана |
+|----------|------------|----------|
+| Добровольный no-contest | 500 VIZ | Нет |
+| Проигрыш спора | 1000+ VIZ + доп. штраф | Постоянный или временный |
+| Пропущенный дедлайн | 250 VIZ (авто-штраф) | Нет (но урон репутации) |
+
+Если пользователи считают, что оракул злоупотребил no-contest, они могут это оспорить. Тогда резолвер
+выбирает из **трёх** возможных верных исходов: побеждает A, побеждает B или подтвердить no-contest. Это
+не даёт оракулам использовать no-contest, чтобы избежать выплат победившим бетторам.
+
+---
+
+## 5. Lazy-пул ликвидности
+
+### 5.1 Проблема развёртывания капитала
+
+Индивидуальное предоставление LP требует активного выбора рынков. Большинство пользователей не станут
+вручную оценивать конкретные рынки и вкладываться в них. Итог: большинство рынков стартует только с
+начальной ликвидностью создателя, давая тонкие книги и высокий слиппедж.
+
+### 5.2 Авто-аллокация пул→рынок
+
+Lazy-пул ликвидности решает это, принимая депозиты и **автоматически аллоцируя** процент свободного
+баланса пула в каждый новый рынок при его активации:
+
+```
+alloc_amount = free_balance × allocation_percent / 100
+```
+
+Аллокации считаются от текущего свободного баланса (не от исходной суммы), создавая геометрический спад —
+пул никогда нельзя полностью истощить:
+
+```
+After 50 markets (2% allocation each): ~357 VIZ free from original 1,000
+After 100 markets: ~133 VIZ still free
+```
+
+Максимальный кап суммарной аллокации (по умолчанию 70%) даёт дополнительную безопасность.
+
+### 5.3 Распределение наград (один общий аккумулятор)
+
+Проблема: когда рынок разрешается с прибылью пула, эту прибыль надо разделить между **всеми** текущими
+вкладчиками пропорционально их долям — но проходить по каждому вкладчику на каждом рынке было бы O(N) и
+неограниченно. Пул избегает этого **одним глобальным бегущим итогом** `reward_per_share` («rps»):
+
+```
+// When a market resolves with pool LP profit, the per-share value of the pool rises once:
+pool.reward_per_share += profit × PRECISION / total_shares
+
+// A depositor's earnings = their shares × how much rps has risen since they last touched the pool:
+live_reward = pending + shares × (pool.reward_per_share − user.snapshot) / PRECISION
+```
+
+Простыми словами: каждый вкладчик «владеет» долей каждого роста `reward_per_share`, и его награда =
+`доли × (текущий rps − rps на момент его последнего депозита/вывода)`. Запись самого вкладчика трогается
+**только когда он действует** (депозит/вывод); до тех пор его право копится тихо в глобальном числе. Поэтому
+раздача прибыли тысячам вкладчиков — **O(1)** (одно сложение), и ничего не выплачивается до клейма. Это
+известный паттерн-аккумулятор из
+[контракта MasterChef SushiSwap](https://github.com/sushiswap/masterchef/blob/master/contracts/MasterChef.sol)
+(и индекса cToken у Compound); `PRECISION` (1e9) держит целочисленное деление точным.
+
+### 5.4 Защита от opportunity-cost
+
+Пул авто-аллоцирует в каждый рынок, создавая вектор атаки: вредоносный оракул мог бы создавать длинные
+рынки с нулевым объёмом, чтобы запереть капитал пула. Это адресуют три механизма:
+
+**Graduated Early Recall:** Длительность рынка делится на 10 шагов. На каждом шаге, если объём ставок ниже
+порога (1% аллокации), 10% текущей аллокации отзывается в пул. Полностью простаивающий 30-дневный рынок
+теряет ~60% своей аллокации.
+
+**Active Market Penalty:** Каждый дополнительный активный рынок того же оракула снижает его аллокацию на
+5% (рекурсивно). Оракул с 10 активными рынками получает ~60% базовой аллокации на рынок, стимулируя
+качество над количеством.
+
+**Fault Penalty Stamps:** Плохие исходы (пропущенные дедлайны, проигранные споры, резолюции с нулевым
+объёмом) генерируют штрафные штампы, дополнительно снижающие будущие аллокации. Штампы авто-истекают после
+10 дней чистой работы.
+
+### 5.5 Опциональное плечо (фондируется Lazy-пулом)
+
+Lazy-пул играет **две роли из одного `free_balance`**: тихие market-LP аллокации *и* фондирование
+сабсистемы **опционального плеча**. Беттер может открыть позицию с плечом (`pm_leverage_open`), где маржа
+— это **займ из пула**: без эмиссии токенов, позиция остаётся полностью обеспеченной с точки зрения
+системы. Бинарный «jump risk», ломающий liquidation-движки CLOB, решается **ликвидацией по pre-bet
+резервам**: opposing-bet или settlement force-close возвращает `min(cancel_value, obligation) ≥ loan`,
+поэтому пул получает назад заём плюс проценты; единственный ограниченный путь bad-debt — same-side
+`pm_cancel_bet`. Медианный kill-switch (`pm_leverage_enabled`, по умолчанию off) блокирует *новые*
+открытия, но защитный каскад ликвидации намеренно **не** гейтится им — выключение плеча никогда не снимает
+защиту с открытых позиций. Пул зарабатывает проценты по плечу вдобавок к LP-доходности, учёт тем же
+способом MasterChef. Расчёт по займам пула эмитит `pm_leverage_resolve` / `pm_leverage_liquidate`.
+
+---
+
+## 6. Управление
+
+### 6.1 Делегат-голосуемые параметры цепи
+
+VIZ использует консенсус Delegated Proof of Stake (DPoS), где избранные делегаты (валидаторы) управляют
+параметрами цепи через механизм медианного голоса:
+
+1. Каждый делегат публикует предпочитаемые значения всех параметров
+2. Сеть вычисляет **медиану** голосов всех активных делегатов
+3. Параметры меняются автоматически при сдвиге медианы — без хардфорка, без деплоя
+
+Все параметры прогнозных рынков (комиссии, штрафы, требования к страховке, окна споров, настройки
+lazy-пула, ручки плеча, тайминг batch/commit-reveal) делегат-голосуемы. Все процентные параметры — в
+**базисных пунктах (bp), 10000 = 100.00%**:
+
+| Примеры | Управление |
+|----------|-----------|
+| `pm_dispute_fee`, `pm_max_oracle_fee_percent` (bp) | Медианный голос делегатов |
+| `pm_dispute_grace_sec`, `pm_dispute_vote_period_sec` | Медианный голос делегатов |
+| `pm_dispute_approve_min_percent`, `pm_dispute_reward_multiplier` (bp) | Медианный голос делегатов |
+| `pm_lazy_*` аллокация/recall, `pm_leverage_*` (enabled, fund %, max position) | Медианный голос делегатов |
+| `pm_commit_reveal_enabled`, `pm_batch_epoch_blocks`, `pm_reveal_window_blocks` | Медианный голос делегатов |
+
+Хардфорки нужны только для структурных изменений (новые типы операций, изменения формул), не для
+экономической настройки. Kill-switches (`pm_leverage_enabled`, `pm_commit_reveal_enabled`) позволяют
+управлению отключить целую подсистему медианным голосом без форка.
+
+### 6.2 Модель юрисдикционного клиента
+
+VIZ DLT — инфраструктура, не оператор — аналогично тому, как Bitcoin это леджер, а не money transmitter.
+Протокол нейтрален и permissionless. Правовые обязательства привязаны к **клиентским приложениям**, не к
+алгоритму консенсуса.
+
+Любая юрисдикция может построить compliant-клиент на VIZ DLT:
+
+| Компонент клиента | Реализация |
+|-----------------|---------------|
+| Предодобренные оракулы | Whitelist клиента из лицензированных, KYC-верифицированных оракулов |
+| Предодобренные резолверы | Госорганы разрешения споров |
+| KYC/AML | Идентификация на уровне клиента |
+| Маршрутизация комиссий как налоговый доход | `dao_fund_account_id` → счёт госказны |
+| Ограничения рынков | Клиент фильтрует по разрешённым категориям |
+| Лимиты ставок | Лимиты на пользователя, навязанные клиентом |
+
+Те же операции протокола (`pm_place_bet`, `pm_resolve`, `pm_dispute`) работают одинаково для
+permissionless и регулируемых клиентов. Разница целиком на слое клиента.
+
+---
+
+## 7. Конкурентный ландшафт
+
+### 7.1 Сравнение платформ
+
+| Измерение | Onix (Forecaster) | Polymarket | Kalshi | Standard LMSR |
+|-----------|-------------------|------------|--------|---------------|
+| **Ценообразование** | CPMM (binary) / LMSR softmax (multi) | CLOB | CLOB | LMSR |
+| **Риск LP** | **Ноль** (структурная гарантия) | Инвентарный риск | N/A | До `b × ln(N)` |
+| **Знания LP** | Низкие (внёс и зарабатывай) | Высокие (управляй ордерами) | N/A | Средние |
+| **Модель комиссий** | % пула проигравших на резолюции | Bid-ask спред | Биржевые комиссии (1-7%) | Спред |
+| **Оракул** | Per-market bonded + диспут комитета | UMA Optimistic Oracle | Kalshi (регулятор CFTC) | Оператор |
+| **Штраф за поздние ставки** | Квадратичный, настраиваемый | Нет | Нет | Нет |
+| **Трансфер позиций** | Нативная операция + зашифрованное memo | CTF (ERC-1155) | Нет | Нет |
+| **Управление** | Делегат-голосуемые параметры | Multisig команды | Процесс CFTC | Оператор |
+| **Инфраструктура** | VIZ DLT (уровень консенсуса) | Polygon (смарт-контракты) | Проприетарные серверы | Разное |
+
+### 7.2 Почему CTF split/merge не нужен
+
+Polymarket использует Gnosis Conditional Tokens Framework (CTF), где позиции — это ERC-1155 токены,
+которые можно split и merge для обеспечения когерентности цен (сумма цен = $1).
+
+В Onix Protocol этот механизм архитектурно не нужен:
+
+- **Onix Binary (CPMM):** `price(A) + price(B) = reserve_b/(reserve_a+reserve_b) + reserve_a/(reserve_a+reserve_b) = 1` — по определению
+- **Onix Multi (LMSR softmax):** `Σ price(i) = Σ exp(q_i/b) / Σ exp(q_j/b) = 1` — по определению softmax
+
+Механизм арбитража не нужен. Когерентность цен — математическое свойство формул, а не внешний слой
+энфорсмента.
+
+### 7.3 Маховик
+
+```
+Risk-free LP → lower barrier for retail LPs
+ → more liquidity deposited
+ → deeper markets, less slippage
+ → better UX for bettors
+ → more volume
+ → more fees for LPs
+ → attracts even more LPs
+```
+
+«Пассивная доходность без impermanent loss» — ценностное предложение, которого Uniswap, Balancer и Curve
+дать не могут. Для crypto-native аудитории это убедительный нарратив: зарабатывай доход, предоставляя
+ликвидность прогнозным рынкам, с нулевым риском для принципала.
+
+---
+
+## 8. VIZ DLT: от прототипа к протоколу
+
+### 8.1 Текущее состояние
+
+Протокол начинался как Telegram WebApp с централизованным бэкендом (вся логика рынка на сервере) —
+рабочий прототип с известными ограничениями: нет sybil-устойчивости сверх Telegram-аккаунтов, нет
+цензуроустойчивости, нет композируемости. **Эта миграция теперь выполнена:** полная логика рынка работает
+**на VIZ DLT как consensus-validated операции `pm_*`** (HF14), отработана end-to-end в `consensus_sim`.
+Остаток раздела описывает эту on-chain архитектуру, теперь реализованную.
+
+### 8.2 Архитектура миграции
+
+VIZ DLT — Distributed Ledger Technology с ~3-секундными блоками, DPoS-консенсусом, именованными аккаунтами
+(в стиле Graphene) и без general-purpose смарт-контрактов. Операции прогнозного рынка реализованы как
+**операции консенсуса первого класса** — не смарт-контракты, не `custom_json`-нагрузки.
+
+| Слой | Примеры | Validated консенсусом? |
+|-------|---------|---------------------|
+| **Операции протокола** | `pm_create_market`, `pm_oracle_accept_market`, `pm_place_bet`, `pm_commit_bet`/`pm_reveal_bet`, `pm_resolve_market`, `pm_dispute_create`/`pm_dispute_vote`/`pm_dispute_resolve`, `pm_lazy_deposit`/`pm_lazy_withdraw`, `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert` | Да — валидирует каждый узел |
+| **Виртуальные операции** | `pm_payout` (на беттера), `pm_auto_payout`, `pm_market_accepted`, `pm_dispute_finalize`, `pm_dispute_auto_close`, `pm_oracle_missed_penalty`, `pm_lazy_recall`, `pm_batch_settle`, `pm_commit_forfeit`, `pm_leverage_resolve`/`pm_leverage_liquidate` | Да — детерминированы, генерируются на этапе блока |
+| **metadata / custom_json** | Комментарии споров, описания рынков, UI-метаданные | Нет — только отображение/индексация |
+
+Каждое финансовое действие (ставки, добавление ликвидности, резолюция рынков, изъятие страховки)
+валидируется каждым валидатором. Невалидные операции отвергаются до включения в блок. Никакого Solidity,
+оценки газа, деплоя байткода.
+
+Фронтенд — **полностью headless web-клиент** — без бэкенд-сервера, без БД, без сессий. Приватные ключи в
+браузере (зашифрованы), транзакции подписываются локально и вещаются на публичные узлы VIZ. Нет зависимости
+от Telegram; ядро приложения платформонезависимо.
+
+### 8.3 Реализовано с момента прототипа и оставшаяся дорожная карта
+
+**Реализовано on-chain (HF14):**
+
+| Фича | Статус |
+|---------|--------|
+| Commit-reveal + batch ставки (бинарные, опциональные, медианный kill-switch) | ✅ Live |
+| Опциональное плечо (фондируется Lazy-пулом, ликвидация по pre-bet резервам) | ✅ Live |
+| Lazy Pool (авто-аллокация, graduated recall, MasterChef-учёт) | ✅ Live |
+| Виртуальные операции расчёта на беттера / плечо + API плагина | ✅ Live |
+| Стейк lazy-пула как governance-вес (PM-споры + заявки DAO) | ✅ Live |
+
+**Оставшаяся дорожная карта:**
+
+| Приоритет | Фича | Влияние |
+|----------|---------|--------|
+| Высокий | Shared liquidity pools (category-level AMMs) | Решает фрагментацию ликвидности на уровне архитектуры |
+| Высокий | Automated data oracles (экзогенные фиды) | Устраняет манипуляцию для объективных рынков |
+| Средний | Тиры окон споров (малые vs крупные рынки) | Лучшая калибровка UX |
+| Средний | LMSR batch settlement (расширить batch/commit-reveal на multi) | Multi-рынки сейчас форсят instant-ставки |
+| — | Commit-reveal голосование в **спорах** | **Намеренно отклонено** — споры остаются публичными слушаниями (см. §4.2) |
+
+---
+
+## 9. Что Onix НЕ заявляет
+
+Честное раскрытие компромиссов и ограничений:
+
+- **Прибыль LP не гарантирована.** Если у рынка ноль проигранных ставок, комиссий для распределения нет.
+ LP получает назад принципал, но не зарабатывает ничего.
+
+- **Платформенный риск существует.** Баги, эксплойты и атаки на управление отделены от модели
+ маркет-мейкера. Гарантия LP структурна (архитектура выплат), не застрахована (нет внешнего гарантийного
+ фонда).
+
+- **Токены Onix Multi — не инструменты фиксированной стоимости.** В стандартном LMSR 1 выигрышный токен =
+ 1 единица валюты. В Onix Multi токены — пропорциональные претензии на пул проигравших. Если все беттеры
+ выбрали победителя, все выходят в ноль.
+
+- **Доходность LP зависит от объёма, не от глубины.** Рынок с субсидией 100 000 VIZ и рынок с 1000 VIZ
+ заработают одинаковую абсолютную комиссию, если у обоих идентичные объём ставок и ставки комиссий.
+ Субсидия даёт глубину, не доходность.
+
+- **У DPoS-управления есть известные компромиссы.** Меньше валидаторов, чем у PoW/PoS, риски концентрации
+ делегатов, token-weighted голосование. Это присуще модели DPoS (общей с EOS, Hive, Tron), не специфично
+ для VIZ.
+
+- **Ликвидность токена VIZ сейчас низкая.** Экономические гарантии (страховые бонды, dispute fee)
+ масштабируются с ценой токена. Протокол предполагает, что полезность со временем создаёт спрос — та же
+ ставка, что делает любой protocol-native токен-проект.
+
+---
+
+## 10. Заключение
+
+Onix Protocol адресует фундаментальный барьер внедрения прогнозных рынков: риск LP. Структурно отделяя
+капитал LP от расчёта по ставкам — и в бинарных (CPMM), и в мульти-исходных (LMSR + parimutuel) рынках —
+Onix делает предоставление ликвидности безрисковым и доступным для ритейл-участников.
+
+Ключевые инновации:
+
+1. **Гарантия принципала LP** как архитектурный инвариант, а не страховка
+2. **Проигравшие-финансируют-победителей** расчёт, устраняющий конкуренцию комиссий с выплатами
+3. **LMSR-ценообразование с parimutuel-расчётом** (Onix Multi) — сочетание проверенного price discovery
+ с безопасностью LP
+4. **Time-weighted распределение LP**, награждающее раннюю фиксацию капитала
+5. **Квадратичный time penalty** на прибыль (никогда принципал) для поздних ставок
+6. **Lazy-пул ликвидности** с авто-аллокацией, graduated recall и MasterChef-учётом — также фондирующий
+ сабсистему **плеча** (маржа из пула, ликвидация по pre-bet резервам, нет bad-debt вне ограниченного
+ cancel-bet пути)
+7. **Модель bonded-оракула** со скорингом репутации, offer→quote заморозкой комиссии и двухрежимной
+ системой споров — диспуты комитета это **публичные слушания** с изменяемыми, lazy-pool-взвешенными
+ голосами
+8. **Опциональный анти-MEV** — batch / commit-reveal ставки (бинарные) с медианным kill-switch
+9. **Реализация на уровне консенсуса** на VIZ DLT — без смарт-контрактов, газа и внешних keeper'ов;
+ строго **zero-sum** (протокол никогда не печатает токен)
+
+Ставка проста: если безрисковый LP привлекает капитал, капитал создаёт глубину, глубина улучшает цены, а
+цены привлекают бетторов — то Onix Protocol решает проблему ликвидности прогнозных рынков. Механика
+протокола математически проверяема. Экономическую гипотезу проверит рынок.
+
+---
+
+## 11. Автор и раскрытие
+
+### Автор
+
+**Анатолий Пискунов** (On1x) — российский IT-инноватор, Web3/DLT-разработчик, создатель блокчейна VIZ.
+Его работа охватывает distributed ledger technology, децентрализованные социальные протоколы и
+экономические модели цифровых сообществ.
+
+Ключевые вклады: VIZ Blockchain (Fair DPoS, примитивы социального капитала), Onix Protocol (прогнозные
+рынки с гарантией LP), Voice Protocol (цензуроустойчивый обмен сообщениями) и обширные публикации по
+экономике блокчейна и Web3-архитектуре.
+
+Полный список публикаций и проектов: [https://on1x.com](https://on1x.com)
+
+### Раскрытие
+
+Автор Forecaster и Onix Protocol также создатель VIZ DLT. Дорожная карта миграции предлагает перенести
+платформу на блокчейн, спроектированный и построенный автором.
+
+Это раскрыто заранее. Это также норма: Polymarket зависит от инфраструктуры Polygon Labs, Kalshi работает
+на своих серверах, Augur спроектировал токен REP, на котором работает. Каждая платформа аргументирует за
+свою инфраструктуру. Вопрос не в том, есть ли у автора интерес — он всегда есть — а в том, фальсифицируемы
+ли технические заявления. Каждая формула, доказательство и механизм в этом документе математически
+проверяемы, а кодовая база — открытая.
+
+---
+
+## 12. Ссылки
+
+1. Hanson, R. (2003). *Combinatorial Information Market Design.* Information Systems Frontiers, 5(1), 107–119. — Logarithmic Market Scoring Rule (LMSR).
+
+2. Adams, H., Zinsmeister, N., Robinson, D. (2020). *Uniswap v2 Core.* — Constant Product Market Maker (`x * y = k`).
+
+3. Adams, H., et al. (2021). *Uniswap v3 Core.* — Концентрированная ликвидность и анализ impermanent loss.
+
+4. Gnosis. *Conditional Tokens Framework (CTF) Documentation.* https://docs.gnosis.io/conditionaltokens/ — ERC-1155 позиции прогнозного рынка.
+
+5. UMA Protocol. *Optimistic Oracle Documentation.* — Механизм эскалации споров, используемый Polymarket.
+
+6. Leshner, R., Hayes, G. (2019). *Compound: The Money Market Protocol.* — Паттерн аккумулятора cToken (основа для reward_per_share).
+
+7. SushiSwap. *MasterChef Contract.* — Паттерн ленивого учёта для распределения наград.
+
+8. Piskunov, A. (2019). *VIZ blockchain system: technical description.* — Архитектура VIZ DLT, DPoS-консенсус, именованные аккаунты.
+
+9. Piskunov, A. (2019). *What is Fair DPoS.* — Инновация управления в delegated proof of stake.
+
+10. Piskunov, A. (2023). *VIZ as a Digital Representative Self-Governing State.* — Рамка для блокчейн-систем как цифровых государств.
diff --git a/@l10n/ru/docs/prediction-markets/workflows.md b/@l10n/ru/docs/prediction-markets/workflows.md
new file mode 100644
index 0000000000..85b97c9440
--- /dev/null
+++ b/@l10n/ru/docs/prediction-markets/workflows.md
@@ -0,0 +1,741 @@
+---
+title: Прогнозные рынки — воркфлоу и диаграммы взаимодействия
+description: Один канонический бинарный рынок Onix, прослеженный через каждого участника, с zero-sum мастер-леджером для нормального и спорного разрешения.
+---
+
+# Воркфлоу и диаграммы взаимодействия
+
+Один **канонический сценарий**, прослеженный через каждого участника. Каждая роль отправляет конкретные
+**подписанные операции**, её затрагивают конкретные **виртуальные операции**, и всё завершается таблицей
+**отправлено / получено токенов** для двух исходов:
+
+- **Нормальное разрешение** — оракул резолвит, проходит grace, `pm_auto_payout` рассчитывает. Без спора.
+- **Спорное разрешение** — оракул резолвит **A**, спор **переворачивает на B**, затем идёт расчёт.
+
+Все суммы — абстрактные **VIZ**. Все проценты — **bp** (10000 = 100.00%). Расчёт строго **zero-sum** —
+токены никогда не печатаются, `current_supply` не трогается:
+
+```
+Σ winner_payout + oracle_take + creator_take + lp_bonus + LP_principal
+ == Σ all bet amounts + LP_principal + forfeit_pool (+ insurance slash, в споре)
+```
+
+## Канонический рынок **M** (binary CPMM, A vs B)
+
+| Параметр | Значение |
+|------|-------|
+| Движок | binary CPMM (`x·y=k`), `weight = tokens_out` |
+| Сид-ликвидность (marketmaker) | **2000** → резервы A=1000 / B=1000 |
+| `oracle_fee_percent` (котировка оракула) | **1000** (10%) |
+| `creator_fee_percent` | **500** (5%) |
+| `liquidity_fee_percent` | **500** (5%) |
+| `oracle_fixed_fee` (котировка оракула) | **10** |
+| `dispute_penalty_percent` | **+10000** (слеш до 100% страховки ×consensus) |
+
+Иллюстративные chain props: `pm_market_creation_fee` 5, `pm_oracle_registration_fee` 10,
+`pm_min_oracle_insurance` 5000, `pm_dispute_fee` 1000, `pm_dispute_reward_multiplier` 30000 (**3×**),
+`pm_no_contest_penalty_percent` 5000, `pm_oracle_penalty_percent` 500, `pm_lazy_emergency_penalty_percent`
+5000, `pm_leverage_pool_profit_percent` **R = 10%**, `pm_lazy_alloc_percent` 2000 (20%).
+
+### Состав
+
+| Актор | Роль | Стейк / действие |
+|-------|------|----------------|
+| **maker** | создатель + первый LP | сид 2000 ликвидности |
+| **orac** | внешний оракул | страховка 5000; котирует 10% + фикс 10 |
+| **LP1** | поставщик ликвидности в рынке | добавляет 1000 |
+| **A** | беттор — победитель | 100 на **A**, рано; вес 100 |
+| **C** | беттор — поздний победитель | 100 на **A** при T+85%; вес 100; time-penalty **50%** |
+| **B** | беттор — проигравший | 200 на **B**; вес 200 |
+| **D** | leverage **×10** победитель | collateral 10 + loan 90 (рынок **L**) |
+| **E** | leverage **×5** ликвидирован | collateral 20 + loan 80 (рынок **L**) |
+| **LZ1** | депозитчик lazy-пула | вносит 1000 |
+| **disp** | диспутёр | escrow dispute fee 1000 |
+
+> Веса кривой (100 / 100 / 200) выписаны явно, чтобы арифметика parimutuel читалась; реальный CPMM выдаёт
+> чуть меньше веса по мере сдвига резервов.
+
+## Диаграммы взаимодействия
+
+**Жизненный цикл рынка.**
+
+```mermaid
+flowchart LR
+ W["Waiting (0)"] -->|оракул принял| A["Active (1)"]
+ W -->|оракул отклонил| X["Deleted (-1)"]
+ A -->|betting_expiration| C["Closed (2)"]
+ A -->|ранняя резолюция| R["Resolved (3)"]
+ C -->|оракул резолвит| R
+ R -->|grace, без спора| P["Выплачено"]
+ R -->|подан спор| D["Disputed"]
+ D -->|finalize / резолвер| P
+```
+
+**Расчёт — НОРМАЛЬНО (побеждает A).** Проигравшие финансируют победителей; принципал LP не тронут (zero-sum).
+
+```mermaid
+flowchart TD
+ B["B проиграл 200 (пул проигравших)"] --> POOL{"делёж 200"}
+ POOL -->|oracle_fee 20 + fixed 10| OR["оракул +30"]
+ POOL -->|creator_fee 10| CR["создатель +10"]
+ POOL -->|liq_fee 10 + штраф 37| LPS["LP +47"]
+ POOL -->|winners_pool 150 → профит 75| A["A → выплата 175"]
+ POOL -->|профит 75 − time-penalty 37| C["C → выплата 138"]
+ MK["maker + LP1 принципал 3000"] -.возвращён полностью.-> MK
+```
+
+**Спор — оракул сказал A, перевёрнуто на B.** Наказание — это слеш страховки (отдельные деньги).
+
+```mermaid
+sequenceDiagram
+ participant O as Оракул
+ participant D as Диспутёр
+ participant V as Комитет / Резолвер
+ O->>O: резолвит A
+ D->>V: pm_dispute_create (escrow dispute_fee)
+ O-->>V: обязательный ответ (дедлайн)
+ V->>V: pm_dispute_vote / pm_dispute_resolve → переворот на B
+ V-->>O: страховка слешнута (5000)
+ V-->>D: fee назад + награда (2000 из слэша)
+ V->>V: расчёт перезапускается → побеждает B
+```
+
+## Мастер-леджер — НОРМАЛЬНОЕ разрешение (A побеждает)
+
+`losers_sum = 200` (B). Комиссии с пула проигравших:
+`oracle_fee = 200×10% = 20`, `creator_fee = 200×5% = 10`, `liq_fee = 200×5% = 10`, `oracle_fixed = 10`.
+`winners_pool = 200 − 20 − 10 − 10 − 10 = 150`. `Σ выигрышного веса = 200` (A 100 + C 100).
+
+- **A**: профит `150×100/200 = 75`, штраф 0 → **выплата 175**.
+- **C**: профит 75, time-penalty `75×50% = 37` (→ LP) → **выплата 138**.
+- **LP-бонус** = `liq_fee 10 + штрафы 37 = 47`, делёж по времени в рынке: **maker ~31 / LP1 ~16**.
+- **oracle_take** = `oracle_fee 20 + fixed 10 = 30`. **creator_take** = `creator_fee 10`.
+
+| Актор | отправил | получил | нетто (этот рынок) |
+|-------|-------|----------|-------------------|
+| maker | 2000 ликвидности + 5 creation-fee | 2000 принципал + 10 creator-fee + 31 LP-бонус | **+36** |
+| orac | (10 reg-fee, 5000 страховка заблок.) | 30 oracle-take | **+30** |
+| LP1 | 1000 ликвидности | 1000 принципал + 16 LP-бонус | **+16** |
+| A | 100 | 175 | **+75** |
+| C | 100 | 138 | **+38** |
+| B | 200 | 0 | **−200** |
+
+**Zero-sum:** in `= ставки 400 + LP принципал 3000 = 3400`; out `= 175+138+0 + 30 + 10 + 47 + 3000 = 3400`. ✔
+5 creation-fee + 10 reg-fee уходят в **фонд DAO** (не часть пула рынка).
+
+## Мастер-леджер — СПОРНОЕ разрешение (оракул сказал A → перевёрнуто на B)
+
+`disp` эскроит `dispute_fee 1000`. Вердикт переворачивает на **B**; оракула слешат.
+При `dispute_penalty_percent = 10000` и силе консенсуса **100%**: `slash = 5000×100%×100% = 5000`.
+Carve-out награды: `reward_target = fee×3 = 3000` → `bonus = 3000 − 1000 = 2000` (≤ slash). Диспутёр
+получает `fee 1000 + bonus 2000 = 3000`. Остаток `slash − bonus = 3000 → forfeit_pool`.
+
+Теперь **B побеждает**. `losers_sum = 200` (A 100 + C 100). Комиссии 20/10/10 + fixed 10.
+`winners_pool = 200 − 50 + forfeit 3000 = 3150`. `Σ выигрышного веса = 200` (B).
+- **B**: профит `3150×200/200 = 3150` → **выплата 3350**.
+- **oracle_take** всё ещё `30` (*рыночная* комиссия платится из замороженной конфигурации даже при
+ перевороте — наказание это **слеш страховки**, отдельные деньги). **creator_take** 10. **LP-бонус** = liq 10.
+
+| Актор | отправил | получил | нетто (этот рынок) |
+|-------|-------|----------|-------------------|
+| maker | 2000 + 5 | 2000 принципал + 10 creator-fee + ~6 LP-бонус | **+11** |
+| orac | страховка −**5000** слешнута | 30 oracle-take | **−4970** |
+| LP1 | 1000 | 1000 принципал + ~4 LP-бонус | **+4** |
+| A | 100 | 0 | **−100** |
+| C | 100 | 0 | **−100** |
+| B | 200 | 3350 | **+3150** |
+| disp | 1000 dispute-fee | 3000 (fee назад + 2000 награда) | **+2000** |
+
+**Zero-sum:** in `= ставки 400 + LP принципал 3000 + dispute_fee 1000 + слеш 5000 = 9400`;
+out `= B 3350 + оракул 30 + создатель 10 + lp_bonus 10 + LP принципал 3000 + диспутёр 3000 = 9400`. ✔
+Слеш 5000 делится на бонус диспутёра 2000 + forfeit 3000 (→ B через пул победителей).
+
+## Статус реализации (сверено с кодом)
+
+**Обычные операции — все 21 присутствуют** в variant `operation` (`operations.hpp`), валидируются +
+оцениваются в `pm_evaluator.cpp`:
+`pm_oracle_register`, `pm_oracle_update`, `pm_create_market`, `pm_oracle_accept_market`, `pm_place_bet`,
+`pm_commit_bet`, `pm_reveal_bet`, `pm_cancel_bet`, `pm_add_liquidity`, `pm_withdraw_liquidity`,
+`pm_resolve_market`, `pm_no_contest`, `pm_dispute_create`, `pm_dispute_vote`, `pm_dispute_resolve`,
+`pm_transfer_position`, `pm_lazy_deposit`, `pm_lazy_withdraw`, `pm_leverage_open`, `pm_leverage_close`,
+`pm_leverage_convert`. ✔
+
+**Виртуальные операции** — эмитятся `database::process_pm_markets()` / эвалюаторами:
+
+| Виртуальная op | Фаерится? | Триггер (код) |
+|------------|--------|----------------|
+| `pm_market_accepted` | ✔ | при `pm_oracle_accept_market` **и** self-oracle `pm_create_market` |
+| `pm_payout` | ✔ | **на каждую активную ставку** при расчёте — несёт `account`, `market_id`, `bet_id`, `side`/`outcome_index`, `amount` (стейк), `payout` (**0 при проигрыше**) |
+| `pm_auto_payout` | ✔ | **раз на рынок** при расчёте — сводный маркер (`bets_sum`) рядом с per-bet `pm_payout` |
+| `pm_commit_forfeit` | ✔ | нераскрытый commit после `reveal_deadline` |
+| `pm_dispute_finalize` | ✔ | `voting_end_time` комитета |
+| `pm_dispute_auto_close` | ✔ | `auto_close_time` (анти-фриз) |
+| `pm_oracle_missed_penalty` | ✔ | оракул пропустил `result_expiration` |
+| `pm_lazy_recall` | ✔ | шаг graduated recall простаивающей аллокации |
+| `pm_batch_settle` | ✔ | граница эпохи |
+| `pm_leverage_liquidate` | ✔ | mid-market ликвидация: reason **0** opposing-bet, **1** cancel-bet (`cascade_liquidate`) |
+| `pm_leverage_resolve` | ✔ | **расчёт** leverage-позиции: несёт `market_id`, `outcome_index`, `won`, `pool_received`/`bettor_received`, `leverage` (= `total_bet/collateral`) |
+
+См. [API плагина](../plugins/prediction-market-api) для read-методов
+(`get_account_leverage_positions`, `get_market_leverage_positions`, `get_creator_ban`, `get_dispute_votes`, …);
+per-bettor результаты (`pm_payout`) и расчёты плеча (`pm_leverage_resolve`) видны и в `account_history`.
+
+## Роли в каноническом сценарии
+
+Каждый участник прослежен через рынок **M** (и leverage-подрынок **L**): его диаграмма взаимодействия,
+**подписанные** операции, **виртуальные** операции, которые его касаются, леджер
+**отправлено / получено** для обоих исходов и указатель на проверку в коде. Каждый леджер по роли —
+срез двух мастер-леджеров выше.
+
+### Маркет-мейкер (создатель + первый LP)
+
+Мейкер создаёт рынок M, вносит **2000** ликвидности (становясь первым `pm_liquidity_object`) и
+предлагает **потолок-оферту** оракула. Он **не** разрешает (это делает оракул).
+
+```mermaid
+flowchart LR
+ maker -->|pm_create_market| M[(pm_market_object status=0)]
+ maker -->|seed 2000| LP0[(pm_liquidity_object provider=maker)]
+ M -. fee 5 .-> DAO[(committee_fund)]
+ orac -->|pm_oracle_accept_market| M2[(M status=1)]
+ M2 -. VIRTUAL .-> VA[[pm_market_accepted]]
+ M2 ==>|pm_auto_payout| RET[principal 2000 + creator_fee + LP bonus]
+ RET --> maker
+```
+
+- **Отправляет:** `pm_create_market` (задаёт `oracle_fee_percent`/`oracle_fixed_fee` как **потолок-оферту**
+ плюс свои `creator_fee_percent` 5% и `liquidity_fee_percent` 5%; платит `pm_market_creation_fee` 5 → DAO,
+ блокирует `liquidity` 2000); опц. `pm_add_liquidity` / `pm_withdraw_liquidity` (принципал-сейф, заблокирован
+ от `betting_expiration` до резолюции).
+- **Касаются:** `pm_market_accepted` (оракул акцептует, либо self-oracle при создании); `pm_auto_payout`
+ (возвращает принципал LP + взвешенную по времени долю LP-бонуса).
+
+| исход | отправляет | получает | нетто |
+|-------|------------|----------|-------|
+| нормальный (A) | 2000 ликвидности + 5 fee-создания (→DAO) | 2000 принципала + **creator_fee 10** + **LP-бонус ~31** | **+36** |
+| спорный (→B) | 2000 + 5 | 2000 принципала + creator_fee 10 + LP-бонус ~6 | **+11** |
+
+Creator fee **всё равно платится** из замороженной конфигурации рынка при перевороте — спор наказывает
+**оракула** (слеш страховки), а не мейкера. Принципал LP возвращается безусловно.
+
+- **Self-oracle:** `oracle == creator` → активен при создании, `pm_market_accepted` с `self_oracle=true`,
+ и мейкер дополнительно получает `oracle_take`.
+- **Проверка:** `pm_create_market_evaluator`; LP через `settle_liquidity`; `committee_fund += pm_market_creation_fee`.
+ **Наблюдать:** `get_market`, `list_markets_by_creator`, `get_market_liquidity` (`earned_fee`), `get_market_meta`.
+
+### Оракул (регистрация → акцепт-котировка → резолюция)
+
+Внешний оракул **orac** вносит страховку, **котирует** свою fee при акцепте (≤ оферты мейкера и ≤
+`pm_max_oracle_fee_percent`) и разрешает. Его рыночная fee платится из пула проигравших; бонд под риском
+только при пропуске дедлайна или проигранном споре.
+
+```mermaid
+flowchart LR
+ orac -->|pm_oracle_register insurance 5000| O[(pm_oracle_object)]
+ orac -. reg-fee 10 .-> DAO[(committee_fund)]
+ orac -->|pm_oracle_accept_market quote fee 10% + fixed 10| M[(M status=1)]
+ M -. VIRTUAL .-> VA[[pm_market_accepted]]
+ orac -->|pm_resolve_market A| M3[(M status=3)]
+ M3 ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|oracle_take 30| orac
+```
+
+- **Отправляет:** `pm_oracle_register` (блокирует страховку 5000, платит reg-fee 10 → DAO, задаёт advisory
+ прайс-лист); `pm_oracle_accept_market` (**котирует** fee 10% + fixed 10, каждое ≤ оферты создателя и ≤
+ медианного кэпа; замораживает на M); `pm_resolve_market` (задаёт `winning_outcome`, открывает grace);
+ опц. `pm_oracle_update` / `pm_no_contest`. Стоячий прайс-лист также может авто-акцептовать рынки live
+ при создании — см. документ операций оракула.
+- **Касаются:** `pm_market_accepted`; `pm_auto_payout` (зачисляет `oracle_take`); `pm_oracle_missed_penalty`
+ (не разрешил → слеш `pm_oracle_penalty_percent` страховки → DAO, возврат всех ставок).
+
+| исход | отправляет | получает | нетто |
+|-------|------------|----------|-------|
+| нормальный (A) | страховка 5000 (заблокир.) + reg-fee 10 (→DAO) | **oracle_take 30** = fee 20 + fixed 10 | **+30** |
+| спорный (→B) | страховка −**5000 слеш** | oracle_take 30 | **−4970** |
+
+Даже при перевороте оракул сохраняет небольшую **рыночную fee** (замороженный конфиг); наказание — это
+**слеш страховки**, разбиваемый на награду диспутёра и `forfeit_pool` победителей. Котировать **ниже**
+оферты можно (цена = репутация); **выше** — отклоняется.
+
+- **Проверка:** `pm_oracle_register_evaluator`, `pm_oracle_accept_market_evaluator` (≤ оферты, ≤ кэпа,
+ заморозка), скан пропущенного дедлайна в `process_pm_markets`. **Наблюдать:** `get_oracle`, `list_oracles`,
+ `get_market` (замороженные условия).
+
+### Оракул — поддержан в споре (победитель спора)
+
+Оракул разрешил **A**; диспутёр оспорил, но вердикт **поддерживает** A. Оракул сохраняет рыночную fee
+**и** забирает потерянный `dispute_fee`; страховка нетронута, `disputes_won++`.
+
+```mermaid
+flowchart LR
+ orac -->|pm_resolve_market A| M[(market resolved A)]
+ disp -->|pm_dispute_create| D[(dispute)]
+ D ==>|uphold A| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|dispute_fee 1000| orac
+ FIN --> AUTO[[pm_auto_payout settles A]]
+ AUTO -->|oracle_take 30| orac
+```
+
+| исход | отправляет | получает | нетто |
+|-------|------------|----------|-------|
+| спор, поддержан | страховка 5000 (**не** слеш) | oracle_take 30 + **dispute_fee 1000** | **+1030** |
+| нормальный (без спора) | страховка 5000 (заблокир.) | oracle_take 30 | **+30** |
+
+Оспаривание оборачивается против диспутёра и **платит оракулу**. Рынок «доброй воли»
+(`dispute_penalty_percent < 0`) может даже выдать оракулу бонус к fee при смене исхода — признавая честную
+ошибку. **Проверка:** ветка uphold в `pm_dispute_finalize` / `pm_dispute_resolve`. **Наблюдать:**
+`get_oracle` (`disputes_won`), `get_dispute`.
+
+### Оракул — перевёрнут + слеш (проигравший спор)
+
+Оракул разрешил **A**; спор **переворачивает на B**, страховка **слешится**. Он всё ещё забирает
+крошечную замороженную рыночную fee (fee и наказание — разные деньги), но теряет большую долю бонда и
+репутацию.
+
+```mermaid
+flowchart LR
+ orac -->|pm_resolve_market A| M[(resolved A)]
+ disp -->|pm_dispute_create proposed=B| D[(dispute)]
+ D ==>|overturn to B| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|slash 5000| INS[oracle.insurance ↓]
+ INS --> SPLIT[bonus 2000 → disputer 3000 → forfeit_pool → B]
+ FIN --> AUTO[[pm_auto_payout settles B]]
+ AUTO -->|oracle_take 30| orac
+```
+
+`slash = страховка 5000 × dispute_penalty_percent (100%) × consensus_strength (100%) = 5000`,
+**перераспределяется**, не сжигается: `bonus 2000 →` диспутёр, `3000 → forfeit_pool →` новые победители (B).
+Нетто **−4970** против **+30** без спора. Слеш масштабируется с **силой консенсуса** (`winning_rshares /
+max_rshares`); `dispute_penalty_percent < 0` (добрая воля) → **нет** слеша. Слешнутый оракул часто ещё и
+**забанен** (следующая роль). **Проверка:** ветка overturn `pm_dispute_finalize` / `pm_dispute_resolve`;
+fee по-прежнему из `mkt.oracle_fee_percent`. **Наблюдать:** `get_oracle` (`total_insurance_slashed`,
+`banned_until`), `get_dispute`.
+
+### Забаненный оракул (и забаненный создатель)
+
+Бан — это **статус**, а не перевод: `pm_oracle_object.banned_until` (а для создателей —
+`pm_creator_ban_object`) блокирует актора от **новых** рынков, пока не пройдёт таймстамп. Обычно идёт вместе
+со слешем переворота, но сам по себе токены не двигает.
+
+```mermaid
+flowchart LR
+ resolver -->|pm_dispute_resolve ban_oracle| O[(pm_oracle_object banned_until = T)]
+ orac -->|pm_create_market / accept| CHK{now < banned_until?}
+ CHK -->|yes| REJ[REJECTED: 'Oracle is banned']
+ CHK -->|no, expired| OK[allowed again]
+ resolver -->|ban_creator| CB[(pm_creator_ban_object)]
+ maker -->|pm_create_market| CHK2{banned?}
+ CHK2 -->|yes| REJ2[REJECTED: 'Creator is banned']
+```
+
+- **Кто ставит:** account-режим → `pm_dispute_resolve` (`ban_oracle`/`ban_creator` + `…_until`);
+ committee-режим → `pm_dispute_finalize` масштабирует бан по консенсусу при перевороте. `banned_until =
+ time_point_sec::maximum()` ⇒ **перманентный**.
+- **Токены:** сам бан — **0** (чистый статус); сопутствующий слеш — это случай переворота выше. Страховка
+ остаётся заблокированной, возвратна после снятия бана и при отсутствии активных рынков.
+- Баны переживают снапшоты и ключуются по аккаунту — повторная регистрация бан не стирает. **Проверка:**
+ `pm_create_market_evaluator` (`"Oracle is banned"` / `"Creator is banned"`). **Наблюдать:** `get_oracle`
+ (`banned_until`, `bans_received`), **`get_creator_ban(account)`**.
+
+### Беттор A — ранний победитель
+
+**A** ставит **100 на сторону A рано** (без штрафа за время) и выигрывает, когда M разрешается в A.
+Выплата = ставка + взвешенная по весу доля пула победителей.
+
+```mermaid
+flowchart LR
+ A -->|pm_place_bet side=A 100| BET[(pm_bet_object weight 100)]
+ BET --> M[(market M reserves shift)]
+ M ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|payout 175| A
+```
+
+| исход | отправляет | получает | нетто |
+|-------|------------|----------|-------|
+| нормальный (A) | 100 | **175** | **+75** |
+| спорный (→B) | 100 | **0** | **−100** |
+
+`profit = winners_pool 150 × weight 100 / Σweight 200 = 75`; без штрафа → выплата `100 + 75`. Переворот
+делает A **проигравшей** стороной. Выигрыши идут **только** из ставок проигравших (+ forfeit), никогда из
+эмиссии. **Отправляет:** `pm_place_bet` (`side=0`, instant); опц. `pm_transfer_position` / `pm_cancel_bet`.
+**Проверка:** `pm_place_bet_evaluator`, `settle_market`. **Наблюдать:** `get_account_positions`
+(`expected_payout`), `get_market_weight_sums`; реализованный `pm_payout` в `account_history`.
+
+### Беттор B — проигравший
+
+**B** ставит **200 на сторону B**. Когда M разрешается в **A**, ставка B финансирует победителей, а B не
+получает ничего. В спорном пути B становится победителем.
+
+```mermaid
+flowchart LR
+ B -->|pm_place_bet side=B 200| BET[(pm_bet_object status active)]
+ BET --> M[(market M)]
+ M ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|status=resolved, payout 0| BET
+```
+
+| исход | отправляет | получает | нетто |
+|-------|------------|----------|-------|
+| нормальный (A) | 200 | **0** | **−200** |
+| спорный (→B) | 200 | **3350** | **+3150** |
+
+200 от B **и есть** `losers_sum` (платит 40 fee + 150 пул победителей + LP-бонус) — паримутюэль-правило
+«проигравшие финансируют победителей». При перевороте B выигрывает, и forfeit оракула 3000 вливается в пул
+B (`payout = 200 + 3150`). Проигравшая ставка тоже фиксируется через `pm_payout` с **payout=0**.
+**Проверка:** ветка проигравшего в `settle_market`. **Наблюдать:** `get_account_positions`,
+`get_market_bets`, `get_dispute`.
+
+### Беттор C — поздний победитель (штраф за время)
+
+**C** ставит **100 на сторону A**, но **поздно** (T+85% окна ставок), поэтому **штраф за время** урезает
+*только прибыль* (не принципал). Тот же вес, что у A, но получает меньше; урезанное идёт LP.
+
+```mermaid
+flowchart LR
+ C -->|pm_place_bet side=A 100 at T+85%| BET[(pm_bet_object weight 100 time_penalty 50%)]
+ BET --> M[(market M)]
+ M ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|payout 138| C
+ VP -. penalty 37 .-> LPb[LP bonus]
+```
+
+| исход | отправляет | получает | нетто |
+|-------|------------|----------|-------|
+| нормальный (A) | 100 | **138** | **+38** |
+| спорный (→B) | 100 | **0** | **−100** |
+
+`profit = 75`; `penalty = 75 × 50% = 37` (→ LP); `payout = 100 + 75 − 37 = 138` — **−37** против +75 у A
+при том же весе. Узел штампует `time_penalty` при размещении из штрафной кривой рынка
+(`time_penalty_type/value`, `penalty_curve_type`). Это сдерживает снайпинг в последнюю секунду и
+субсидирует ликвидность, а не протокол. **Проверка:** `pm_place_bet_evaluator` (eval кривой),
+`compute_settlement`. **Наблюдать:** `get_account_positions` (`time_penalty`), `get_market_bets`.
+
+### Беттор D — leverage ×10 победитель
+
+**D** открывает позицию **×10**: **10 коллатерала + 90 займа** из lazy-пула = **100** на стороне A, в
+изолированном leverage-рынке **L** (`pm_leverage_enabled=true`, `R = 10%`). Когда A выигрывает, D
+сохраняет апсайд на всей сотне после погашения займа + процентов.
+`pool_profit = loan 90 × R 10% = 9`; `obligation = 90 × 1.10 = 99`.
+
+```mermaid
+flowchart LR
+ D -->|pm_leverage_open collateral 10 + loan 90| POS[(pm_leverage_position total_bet 100, obligation 99)]
+ POOL[(lazy pool)] -.loan 90.-> POS
+ POS --> L[(market L, side A)]
+ L ==>|settle: force_close at cancel_value| VR[[pm_leverage_resolve won=true, leverage=10]]
+ VR -->|min(cv,obligation) 99| POOL
+ VR -->|cv 200 − 99 = 101| D
+```
+
+| исход | отправляет | получает | нетто |
+|-------|------------|----------|-------|
+| нормальный (A) | коллатерал **10** | cancel_value 200 − obligation 99 = **101** | **+91** |
+| спорный (→B) | коллатерал 10 | 0 | **−10** |
+
+Прибыльная позиция закрывается по `cancel_value`; пул забирает `obligation 99` (заём 90 + **9 процентов**),
+D оставляет остаток на своих 10 → **+91** (пул **+9**). Leverage рассчитывается ликвидацией, **никогда** не
+через `pm_auto_payout`. Zero-sum (L): in `10 + 90 + 100 = 200`; out `101 + 99 = 200`. **Отправляет:**
+`pm_leverage_open`; опц. `pm_leverage_close` (только при `cv ≥ obligation`) / `pm_leverage_convert`.
+**Касается:** `pm_leverage_resolve` (force-close при расчёте, `reason=expiration`). **Проверка:**
+`force_close_positions` → `liquidate_position(reason=2)`. **Наблюдать:**
+**`get_account_leverage_positions`** / **`get_market_leverage_positions`**, `get_lazy_pool`.
+
+### Беттор E — leverage ×5 ликвидирован
+
+**E** открывает позицию **×5**: **20 коллатерала + 80 займа** = **100** на стороне B (рынок **L**,
+`R = 10%`). До резолюции **встречная ставка** двигает кривую против B; **каскадная ликвидация** принудительно
+закрывает позицию. **E теряет коллатерал, но пул всегда остаётся целым.** `obligation = 80 × 1.10 = 88`.
+
+```mermaid
+flowchart LR
+ E -->|pm_leverage_open collateral 20 + loan 80| POS[(pm_leverage_position obligation 88)]
+ POOL[(lazy pool)] -.loan 80.-> POS
+ X -->|pm_place_bet side=A| L[(market L)]
+ L ==>|cascade at PRE-bet reserves cv 88 ≤ threshold| VL[[pm_leverage_liquidate reason=opposing_bet]]
+ VL -->|pool_received 88 = loan 80 + profit 8| POOL
+ VL -->|bettor_received 0| E
+```
+
+E ликвидируется **до** резолюции, поэтому финальный результат A/B (спорный или нет) на неё не влияет:
+
+| актор | отправляет | получает | нетто |
+|-------|------------|----------|-------|
+| **E** | коллатерал **20** | **0** | **−20** |
+| **пул** | заём 80 | **88** (заём 80 + R% 8) | **+8** |
+
+Ликвидации по встречной ставке идут на **до-ставочных** резервах, где `cancel_value ≥ loan`, поэтому
+`pool_received = min(cv, obligation)` возвращает как минимум заём — пул **никогда** не теряет.
+
+> **Единственный путь в минус** — same-side **`pm_cancel_bet` (Case B)**: отмена разворачивает *прежнюю,
+> крупную* ставку той же стороны (за пределами per-bet слиппедж-кэпа) и ради честности к отменителю
+> исполняется **первой** по его цене — так каскад может оказаться `cancel_value < loan`:
+> `shortfall = obligation − cancel_value`, `lazy_pool.free_balance −= shortfall`. Этот **bad debt**
+> **ограничен** (`≤ cancel_value_before × SL%`) и **редок** (R% пула со всех прочих позиций его перекрывает).
+> Покрыт тестом `leverage_cancel_bet_cascade_bad_debt`.
+
+Защита пула структурна (`max_per_position`, `max_position_ratio`, `safety_margin`, слиппедж-кэп,
+`expiration_buffer`). `pm_leverage_enabled=false` блокирует **только новые** открытия — каскад ликвидации
+**не** гейтится флагом, поэтому управление не может снять защиту пула на лету
+(`leverage_disabled_keeps_liquidation_protection`). **Проверка:** `pm_place_bet` →
+`cascade_liquidate(reason=0)`; `pm_cancel_bet` → `cascade_liquidate(reason=1)`; `liquidate_position`.
+**Наблюдать:** **`get_account_leverage_positions`** (`status=1`, `pool_received`, `bettor_received`),
+`get_lazy_pool`.
+
+### Поставщик ликвидности в рынке (LP1)
+
+**LP1** добавляет **1000** ликвидности в активный M (после сида мейкера). Принципал **всегда** возвращается;
+сверху он зарабатывает **взвешенную по времени** долю LP-бонуса (liquidity fee + штрафы за время + пыль).
+Отличается от поставщика lazy-пула, который депонирует один раз и авто-аллоцируется по многим рынкам.
+
+```mermaid
+flowchart LR
+ LP1 -->|pm_add_liquidity 1000| L1[(pm_liquidity_object provider=LP1)]
+ L1 --> M[(market M reserves)]
+ M ==>|settle| SL[[settle_liquidity]]
+ SL -->|principal 1000 + bonus ~16| LP1
+ LP1 -->|pm_withdraw_liquidity after resolution| OUT[principal-safe exit]
+```
+
+| исход | отправляет | получает | нетто |
+|-------|------------|----------|-------|
+| нормальный (A) | 1000 | **1000 принципала + ~16 бонуса** | **+16** |
+| спорный (→B) | 1000 | 1000 принципала + ~4 бонуса | **+4** |
+
+Пул LP-бонуса = `liq_fee 10 + штрафы 37 = 47`, делится по `principal × секунды-в-рынке` (ранний мейкер ~31,
+поздний LP1 ~16). **Гарантия принципала** архитектурна — сид возвращается до выплаты любому победителю; LP
+может лишь недополучить бонус, но не потерять принципал. Вывод заблокирован от `betting_expiration` до
+резолюции. **Проверка:** `pm_add_liquidity_evaluator` (фиксирует `deposit_time`), `settle_liquidity` →
+`distribute_lp`. **Наблюдать:** `get_market_liquidity` (`earned_fee`), `get_market_weight_sums`.
+
+### Lazy-пул ликвидности (системный объект)
+
+**Синглтон** `pm_lazy_pool_object` — не аккаунт. Депозитчики финансируют его один раз; пул
+**авто-аллоцирует** долю в каждый акцептованный рынок как молчаливый LP (`pm_liquidity_object` с пустым
+`provider`), **финансирует leverage-займы** и **отзывает** простаивающие аллокации. Зарабатывает LP-доход +
+проценты по плечу, учёт в стиле MasterChef (один глобальный `reward_per_share`, O(1) — см. белую бумагу).
+Поля: `total_shares`, `free_balance`, `allocated_balance`, `earned_balance`, `reward_per_share`,
+`leverage_fund_used`.
+
+```mermaid
+flowchart TD
+ LZ1 -->|pm_lazy_deposit 1000| POOL[(pm_lazy_pool free 1000 / shares 1000)]
+ POOL ==>|on market accept alloc 20% = 200| ALLOC[(pm_lazy_allocation + pm_liquidity provider=∅)]
+ ALLOC -->|market settles| YLD[route_pool_lp_return principal 200 + yield 20]
+ YLD --> POOL
+ POOL -->|leverage loan 90| Dpos[D position]
+ Dpos -->|close/resolve: 90 + interest 9| POOL
+ POOL -. idle market .-> VR[[pm_lazy_recall]]
+ VR -->|step back to free| POOL
+```
+
+| денежный поток пула | эффект |
+|---------------------|--------|
+| `pm_lazy_deposit` | `free_balance += amount`, минт shares |
+| авто-аллокация (при акцепте) | `free → allocated` (молчаливый LP) |
+| рынок рассчитывается | `route_pool_lp_return`: принципал + доход → `free`; доход → `earned` & `reward_per_share` |
+| leverage open (D/E) | `free −= loan`, `leverage_fund_used += loan` |
+| leverage close / resolve / ликвидация по встречной ставке | `min(cv, obligation) → free`; `cv ≥ loan` ⇒ **никогда не убыток** |
+| ликвидация cancel-bet (только Case B) | возвращает `cv`, который **может быть < loan** → ограниченный **bad debt** |
+| `pm_lazy_recall` (простой рынок) | один шаг 10% простаивающей аллокации → `free` |
+| `pm_lazy_withdraw` | сжечь shares → принципал + pending; emergency-штраф остаётся в пуле |
+
+За канонический сценарий пул в нетто **+37 earned** (доход рынка M +20, проценты leverage D +9, возврат по
+встречной ставке leverage E +8). Как рыночный LP принципал возвращается безусловно; с диспутом меняется лишь
+*бонусный* доход.
+
+> Пул выполняет **обе** роли из единого `free_balance`: рыночные LP-аллокации (`maybe_allocate_lazy`) и
+> leverage-займы (`leverage_fund_used` ограничивает последние). Все leverage-параметры проверяются **в момент
+> `pm_leverage_open`** против текущей медианы, поэтому позднейшие изменения свойств влияют лишь на *новые*
+> открытия, а не на уже выданные займы.
+
+VIZ в пуле **ликвидны**, не vested → **нет** веса для планирования валидаторов или committee-request.
+**Исключение (HF14):** для **PM-споров комитета** стейк депозитчика в пуле **учитывается** — конвертируется в
+vesting-shares через `get_vesting_share_price()` и добавляется к весу его `pm_dispute_vote` (см. резолвер-
+комитет ниже). **Проверка:** `apply_hardfork(CHAIN_HARDFORK_14)` (синглтон), `maybe_allocate_lazy`,
+`route_pool_lp_return`. **Наблюдать:** `get_lazy_pool`.
+
+### Поставщик ликвидности в lazy-пуле (LZ1)
+
+**LZ1** депонирует **1000** в пул **один раз** и даёт ему распределить по рынкам + leverage-займам. Он
+зарабатывает долю агрегированного дохода пула (`reward_per_share`), а не исход одного рынка. Два выхода:
+**плановый** (после лока) и **аварийный** (до лока, со штрафом на *прибыль*).
+
+```mermaid
+flowchart LR
+ LZ1 -->|pm_lazy_deposit 1000| DEP[(pm_lazy_deposit_object shares 1000, unlock=+7d)]
+ DEP --> POOL[(lazy pool)]
+ POOL -. yield accrues .-> RPS[reward_per_share ↑]
+ LZ1 -->|pm_lazy_withdraw| OUT{planned or emergency?}
+ OUT -->|planned, t≥unlock| P[principal 1000 + pending 29]
+ OUT -->|emergency, t|pm_dispute_create proposed=B escrow fee 1000| D[(pm_dispute_object status open)]
+ D --> VOTE{committee vote or account resolve}
+ VOTE ==>|overturn to B| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|fee 1000 + bonus 2000| disp
+ FIN -.slash 5000 from oracle.-> SPLIT[bonus 2000 → disp 3000 → forfeit_pool → winners]
+```
+
+| исход | отправляет | получает | нетто |
+|-------|------------|----------|-------|
+| спор, перевёрнут («победа») | dispute_fee **1000** | fee 1000 назад + **bonus 2000** | **+2000** |
+
+`reward_target = fee × pm_dispute_reward_multiplier (3×) = 3000` → `bonus = 3000 − 1000 = 2000`, **ограничен
+фактическим слешем**; остаток (3000) → `forfeit_pool` → новые победители. disp рисковал 1000, уходит
+**+2000**. (Committee-режим: disp **не** голосует сам — это делает электорат SHARES.) **Проверка:**
+`pm_dispute_create_evaluator`, ветка overturn `pm_dispute_finalize`/`pm_dispute_resolve`. **Наблюдать:**
+`get_dispute`, `get_dispute_votes`.
+
+### Диспутёр — fee потеряна (оракул поддержан)
+
+**disp** оспаривает **A** оракула, но вердикт **поддерживает оракула**. Эскроу-fee **переходит оракулу** как
+компенсация, а рынок рассчитывается как изначально (A выигрывает).
+
+```mermaid
+flowchart LR
+ disp -->|pm_dispute_create proposed=B escrow fee 1000| D[(pm_dispute_object)]
+ D --> VOTE{committee vote or account resolve}
+ VOTE ==>|uphold oracle A| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|dispute_fee 1000| orac[oracle compensation]
+ FIN -->|market settles as A| AUTO[[pm_auto_payout]]
+```
+
+| исход | отправляет | получает | нетто |
+|-------|------------|----------|-------|
+| спор, поддержан («проигрыш») | dispute_fee **1000** | **0** | **−1000** |
+
+Fee — это skin-in-the-game диспутёра: неверный/легкомысленный спор платит оракулу. Эта асимметрия (потерять
+fee при ошибке, выиграть кратное при правоте) держит канал честным. Спор, который так и **не решён** (оракул
+молчит / нет кворума), принудительно закрывается, а fee **возвращается** (нетто 0) — см. авто-закрытие спора
+ниже, что отличается от проигрыша по существу. **Проверка:** ветка uphold
+`pm_dispute_finalize`/`pm_dispute_resolve`. **Наблюдать:** `get_dispute`, `get_oracle` (получает fee,
+`disputes_won++`).
+
+### Резолвер — комитет (взвешенный по стейку, dispute_mode = 0)
+
+*Весь электорат SHARES* решает **взвешенным по стейку голосованием**; единого резолвер-аккаунта нет. Вердикт
+детерминированно подсчитывается `pm_dispute_finalize` в `voting_end_time`.
+
+**Вес голоса** = живые **`effective_vesting_shares`** (`vesting − delegated + received`) **плюс стейк
+lazy-пула, сконвертированный в vesting-shares**, поскольку многие члены DAO держат VIZ в пуле (где они
+ликвидны):
+
+```
+pool_claim_viz = pool_NAV × deposit.shares / pool.total_shares
+pool_weight = pool_claim_viz × get_vesting_share_price()
+voter_weight = effective_vesting_shares + pool_weight
+```
+
+Знаменатель кворума участия — `total_vesting_shares + (pool_NAV → vesting-shares)`. 7-дневный лок депозита
+предотвращает игру депозит-голос-вывод.
+
+```mermaid
+flowchart LR
+ V1[voter · eff_vshares] -->|pm_dispute_vote outcome,percent| D[(pm_dispute_votes)]
+ V2[voter · eff_vshares] -->|pm_dispute_vote| D
+ D ==>|voting_end_time| FIN[[pm_dispute_finalize]]
+ FIN -->|argmax rshares, threshold check| VERDICT{uphold / overturn}
+ VERDICT -->|consensus_strength scales slash & bans| OUT[settle]
+```
+
+- **Отправляют (голосующие):** `pm_dispute_vote` — **auth `regular`**. `vote_outcome = -1` поддерживает,
+ иначе предлагает верный исход; `vote_percent ∈ [-10000, 10000]`. Голосующий может **пересматривать**
+ бюллетень сколько угодно раз, пока голосование открыто — повторный голос **перезаписывает** прежний
+ (побеждает последний, без «Already voted»).
+- **Без commit-reveal — намеренно, меняться НЕ будет.** Спор комитета — **открытое публичное слушание**:
+ текущий подсчёт виден (`get_dispute_votes`), голоса не скрыты. Ценность DAO — разрешать споры максимально
+ правдиво и прозрачно; новые аргументы всплывают в ходе голосования, и голосующие *должны* обновляться; а
+ голосующим **не платят** за совпадение с большинством, поэтому обычный анти-стадный довод (beauty contest)
+ для commit-reveal здесь неприменим.
+
+| актор | отправляет | получает |
+|-------|------------|----------|
+| каждый голосующий | 0 | **0** — голосование это управленческий долг, не оплачиваемое действие |
+
+Голосующие никогда не получают токены; влияние — чистый вес стейка. Экономические потоки приходятся на
+диспутёра, оракула и бетторов согласно спорному мастер-леджеру выше. Нишевые рынки могут не пройти порог →
+переход к авто-закрытию спора ниже. **Проверка:** `pm_dispute_vote_evaluator` (modify-or-create на
+`by_market_voter`); `pm_dispute_finalize` (`lazy_vote_weight`, `get_vesting_share_price`, кворум, argmax,
+`consensus_strength`). **Наблюдать:** `get_dispute_votes` (живой подсчёт + проекция finalize:
+`quorum_percent_bp`, `expected_uphold`, `expected_outcome`, `expected_consensus_strength_bp`). Тесты:
+`committee_dispute_lazy_pool_voting_weight`, `committee_dispute_flips_outcome`.
+
+### Резолвер — один аккаунт (централизованный, dispute_mode = 1)
+
+Рынок называет один аккаунт `dispute_resolver` (например, мультисиг регулятора), который решает в одиночку —
+**без веса стейка, без голосования DAO**. Задан при создании, должен отличаться и от `oracle`, и от
+`creator` (анти-самосуд). Тот же набор операций, что и в комитете; различается лишь *кто решает*.
+
+```mermaid
+flowchart LR
+ disp -->|pm_dispute_create| D[(dispute, mode=1)]
+ resolver -->|pm_dispute_resolve correct_outcome=B penalty_amount, ban flags| FIN[[verdict]]
+ FIN -->|slash penalty_amount| orac[oracle.insurance ↓]
+ FIN -->|fee + reward| disp
+ FIN --> AUTO[[pm_auto_payout settles B]]
+```
+
+- **Отправляет:** `pm_dispute_resolve` — **auth `active` названного `dispute_resolver`** только:
+ `correct_outcome`, `penalty_amount` (страховка к слешу — фиксированная сумма, **не** масштабируется
+ стейком), `ban_oracle`/`ban_creator` (+ `…_until`).
+
+| актор | отправляет | получает |
+|-------|------------|----------|
+| резолвер | 0 | **0** — нейтральный арбитр |
+
+Пост-вердиктный канон идентичен комитет-режиму; различается лишь размер слеша (заданный резолвером
+`penalty_amount`, без масштабирования `consensus_strength`, поскольку решает один). KYC/whitelisting
+резолвера — забота **клиентского слоя**. **Проверка:** `pm_dispute_resolve_evaluator` (только названный
+резолвер, `dispute_mode==1`). **Наблюдать:** `get_dispute`, `get_oracle`, **`get_creator_ban(account)`**.
+
+### Спор принудительно завершён (анти-фриз авто-закрытие)
+
+Спор, который так и **не решён** — оракул молчит и (в комитете) нет кворума — не может заморозить рынок
+навсегда. В `auto_close_time` обработчик `pm_dispute_auto_close` принудительно его завершает: **всем
+возврат**, fee диспутёра **возвращается**, неотзывчивый оракул наказывается. Победитель не выбирается.
+
+```mermaid
+flowchart LR
+ disp -->|pm_dispute_create escrow fee 1000| D[(dispute, status open)]
+ D -. oracle silent / no quorum .-> WAIT[auto_close_time reached]
+ WAIT ==>|VIRTUAL| AC[[pm_dispute_auto_close]]
+ AC -->|refund all bets| bettors
+ AC -->|fee 1000 back| disp
+ AC -->|insurance slash → DAO| orac
+```
+
+| актор | отправляет | получает | нетто |
+|-------|------------|----------|-------|
+| A / B / C | ставка | полный возврат | **0** |
+| maker / LP1 | ликвидность | принципал назад | **0** (без бонуса) |
+| disp | dispute_fee 1000 | **1000 назад** | **0** |
+| orac | страховка −слеш → DAO | — | **− слеш** |
+
+Это **не** «диспутёр проиграл»: возвращённая fee (нетто 0) отличается от потерянной fee (диспутёр-проигравший,
+нетто −1000). Никто не зарабатывает; рынок аннулируется, чтобы снять заморозку, издержки падают на не
+ответившего оракула. Та же форма аннуляции-и-возврата покрывает `pm_oracle_missed_penalty` и `pm_no_contest`.
+Настройте `pm_dispute_auto_close_sec` (14 д) против `pm_dispute_vote_period_sec` (3 д), чтобы честные споры
+решались раньше. **Проверка:** скан авто-закрытия в `process_pm_markets` (`refund_all_bets` +
+`return_liquidity` + зачёт fee; `disputes_auto_closed++`). **Наблюдать:** `get_dispute` (статус →
+авто-закрыт), `get_market`, `get_oracle`.
diff --git a/@l10n/ru/docs/protocol/operations/overview.md b/@l10n/ru/docs/protocol/operations/overview.md
index e2dcdb3200..dcfc0a81ab 100644
--- a/@l10n/ru/docs/protocol/operations/overview.md
+++ b/@l10n/ru/docs/protocol/operations/overview.md
@@ -52,6 +52,32 @@
| 58 | `use_invite_balance_operation` | active | [Инвайты](./invites.md) |
| 60 | `fixed_award_operation` | regular | [Награды](./awards.md) |
| 61 | `target_account_sale_operation` | master | [Рынок аккаунтов](./account-market.md) |
+| 64 | `set_reward_sharing_operation` | active | [Валидаторы](./validators.md) |
+| 66 | `pm_oracle_register_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 67 | `pm_oracle_update_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 68 | `pm_create_market_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 69 | `pm_oracle_accept_market_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 70 | `pm_place_bet_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 71 | `pm_commit_bet_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 72 | `pm_reveal_bet_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 73 | `pm_cancel_bet_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 74 | `pm_add_liquidity_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 75 | `pm_withdraw_liquidity_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 76 | `pm_resolve_market_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 77 | `pm_no_contest_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 78 | `pm_dispute_create_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 79 | `pm_dispute_vote_operation` | regular | [Прогнозные рынки](./prediction-markets.md) |
+| 80 | `pm_dispute_resolve_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 81 | `pm_transfer_position_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 82 | `pm_lazy_deposit_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 83 | `pm_lazy_withdraw_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 91 | `pm_leverage_open_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 92 | `pm_leverage_close_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 93 | `pm_leverage_convert_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 98 | `pm_dispute_oracle_respond_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+| 99 | `pm_unban_operation` | active | [Прогнозные рынки](./prediction-markets.md) |
+
+> ID — это фиксированный индекс в едином `operation`-варианте цепи (только добавление). Пропуски в этой таблице — **виртуальные** операции (ниже), чередующиеся по ID — например, 62–63, 65, 84–90, 94–97, 100.
---
@@ -83,6 +109,19 @@
| 59 | `expire_escrow_ratification_operation` | Истёк дедлайн эскроу | [Виртуальные операции](../virtual-operations.md) |
| 62 | `bid_operation` | Сделана ставка на аукционе | [Виртуальные операции](../virtual-operations.md) |
| 63 | `outbid_operation` | Перебитая ставка на аукционе | [Виртуальные операции](../virtual-operations.md) |
+| 65 | `stakeholder_reward_operation` | Выплата reward-sharing участнику | [Валидаторы](./validators.md) |
+| 84 | `pm_batch_settle_operation` | Рассчитана граница batch-эпохи | [Прогнозные рынки](./prediction-markets.md) |
+| 85 | `pm_commit_forfeit_operation` | Escrow commit-reveal форфейтнут (не раскрыт) | [Прогнозные рынки](./prediction-markets.md) |
+| 86 | `pm_auto_payout_operation` | Рынок рассчитан (порыночный маркер выплаты) | [Прогнозные рынки](./prediction-markets.md) |
+| 87 | `pm_dispute_finalize_operation` | Подсчитано голосование комитета | [Прогнозные рынки](./prediction-markets.md) |
+| 88 | `pm_dispute_auto_close_operation` | Анти-фриз авто-закрытие спора | [Прогнозные рынки](./prediction-markets.md) |
+| 89 | `pm_oracle_missed_penalty_operation` | Оракул пропустил дедлайн разрешения | [Прогнозные рынки](./prediction-markets.md) |
+| 90 | `pm_lazy_recall_operation` | Шаг поэтапного отзыва lazy-пула | [Прогнозные рынки](./prediction-markets.md) |
+| 94 | `pm_leverage_liquidate_operation` | Плечевая позиция ликвидирована | [Прогнозные рынки](./prediction-markets.md) |
+| 95 | `pm_leverage_resolve_operation` | Плечевая позиция рассчитана на резолюции | [Прогнозные рынки](./prediction-markets.md) |
+| 96 | `pm_market_accepted_operation` | Рынок запущен (оракул принял / self / авто) | [Прогнозные рынки](./prediction-markets.md) |
+| 97 | `pm_payout_operation` | Паримутюэль-выплата на беттера | [Прогнозные рынки](./prediction-markets.md) |
+| 100 | `pm_ban_expired_operation` | Истёк временный бан оракула/создателя | [Прогнозные рынки](./prediction-markets.md) |
---
diff --git a/@l10n/ru/docs/protocol/operations/prediction-markets.md b/@l10n/ru/docs/protocol/operations/prediction-markets.md
index 8d3ca48bc2..0210f0b1d7 100644
--- a/@l10n/ru/docs/protocol/operations/prediction-markets.md
+++ b/@l10n/ru/docs/protocol/operations/prediction-markets.md
@@ -106,6 +106,8 @@ flowchart TD
Оракул принимает (`status → active`) или отклоняет (ликвидность возвращается создателю; `status → deleted`) ожидающий рынок. При принятии оракул **котирует свои фактические условия** через `oracle_fee_percent` + `oracle_fixed_fee` — каждое должно быть `≤` оферты создателя на рынке, а `oracle_fee_percent ≤ pm_max_oracle_fee_percent`. Котировка **замораживается в рынок**, эмитится виртуальная `pm_market_accepted` (чтобы парсеры истории видели запуск + условия). Расчёт позже читает только эти замороженные поля — никогда живую медиану.
+Оракул обязан действовать в течение `pm_oracle_accept_window_sec` (по умолчанию 1 ч) от создания. Если он не сделал ни того, ни другого к `accept_deadline` рынка, крон каждого блока аннулирует рынок (`status → deleted`), возвращает создателю сид-ликвидность (**не** невозвратную комиссию за создание) и эмитит `pm_market_expired` (см. Виртуальные операции).
+
| Поле | Тип | Описание |
|------|-----|----------|
| `market_id` | `int64` | Ожидающий рынок |
@@ -155,7 +157,14 @@ Commit-reveal фаза 2: раскрывает ставку и ставит её
### `pm_resolve_market_operation` (ID 76)
**Auth:** `active` аккаунта `oracle`
-Оракул разрешает в `winning_outcome`. Открывает окно ожидания спора (`result_expiration + pm_dispute_grace_sec`); по его истечении `pm_auto_payout` рассчитывает.
+Оракул разрешает в `winning_outcome`. Открывает окно ожидания спора (`result_expiration + pm_dispute_grace_sec`); по его истечении `pm_auto_payout` рассчитывает. Заявление оракула о разрешении **сохраняется на рынке** (как `rules_url` оракула), чтобы клиент мог прочитать его прямо через `get_market`, не сканируя историю.
+
+| Поле | Тип | Описание |
+|------|-----|----------|
+| `market_id` | `int64` | Целевой рынок |
+| `winning_outcome` | `int16_t` | Индекс выигравшего исхода |
+| `decision_url` | `string` | Ссылка на доказательства, `≤ MAX_PM_DECISION_URL_LEN`; сохраняется на рынке |
+| `decision_reason` | `string` | Свободное обоснование, `≤ MAX_PM_DISPUTE_REASON_LEN`; сохраняется на рынке (`decision_reason`) |
### `pm_no_contest_operation` (ID 77)
**Auth:** `active` аккаунта `oracle`
@@ -177,7 +186,39 @@ Commit-reveal фаза 2: раскрывает ставку и ставит её
### `pm_dispute_resolve_operation` (ID 80)
**Auth:** `active` аккаунта `resolver`
-Вердикт в режиме аккаунта от заданного рынком `dispute_resolver`. Может слешить `penalty_amount` страховки и банить оракула/создателя до заданных времён.
+Вердикт в режиме аккаунта от заданного рынком `dispute_resolver`. Может слешить `penalty_amount` страховки и банить оракула/создателя до заданных времён (`ban_*_until = time_point_sec::maximum()` = навсегда).
+
+> **Баны — это фича комплаенса/регулятора, эксклюзивная для режима аккаунта.** Когда рынок направляет свои споры на `dispute_resolver` режима аккаунта (например, регулятора или лицензированного арбитра), этот резолвер может санкционировать **и оракула, и создателя рынка** — временно или навсегда — тем же вердиктом, поверх слэша страховки: это позволяет регулятору-резолверу отстранить недобросовестного оракула или создателя-рецидивиста от платформы. **Режим комитета/DAO (`dispute_mode == 0`) не имеет права бана по замыслу** — это прозрачные публичные слушания, которые лишь слешат страховку и корректируют репутацию (`pm_dispute_finalize`), но никогда не банят. Установленный здесь бан записывает выдавшего `resolver` в `banned_by` цели, так что снять его досрочно через `pm_unban` может только этот резолвер; иначе бан истекает на `banned_until` (крон эмитит `pm_ban_expired`).
+
+| Поле | Тип | Описание |
+|------|-----|----------|
+| `market_id` | `int64` | Оспариваемый рынок |
+| `correct_outcome` | `int16_t` | Финальный верный исход (`-1` = void/no-contest) |
+| `penalty_amount` | `asset` (VIZ) | Страховка оракула к слэшу |
+| `ban_oracle` / `ban_oracle_until` | `bool` / `time_point_sec` | Забанить оракула до заданного времени |
+| `ban_creator` / `ban_creator_until` | `bool` / `time_point_sec` | Запретить создателю создавать рынки до заданного времени |
+
+### `pm_dispute_oracle_respond_operation` (ID 98)
+**Auth:** `active` аккаунта `oracle`
+
+Оракул рынка публикует **публичное опровержение** на открытый спор. Поскольку спор — это публичные слушания, текст сохраняется на объекте спора (`oracle_response` / `oracle_response_time`, читается через `get_dispute`), чтобы каждый голосующий/резолвер мог его учесть. Разрешено только пока спор открыт и `now ≤ oracle_response_deadline`; повторная публикация перезаписывает предыдущий ответ.
+
+| Поле | Тип | Описание |
+|------|-----|----------|
+| `market_id` | `int64` | Оспариваемый рынок |
+| `response` | `string` | Текст опровержения, непустой, `≤ MAX_PM_DISPUTE_REASON_LEN` |
+
+### `pm_unban_operation` (ID 99)
+**Auth:** `active` аккаунта `resolver`
+
+Снимает бан, наложенный `pm_dispute_resolve` режима аккаунта, **досрочно**. Снять его может только аккаунт, записанный в `banned_by` цели (резолвер, установивший бан); хотя бы один из `unban_oracle` / `unban_creator` должен быть задан, а соответствующий бан — быть активным сейчас. Ставит `banned_until` в прошлое и очищает `banned_by`. (Баны, не снятые здесь, просто истекают на `banned_until` — тогда крон эмитит `pm_ban_expired`.)
+
+| Поле | Тип | Описание |
+|------|-----|----------|
+| `resolver` | `account_name_type` | Аккаунт, наложивший бан (должен равняться `banned_by` цели) |
+| `target` | `account_name_type` | Забаненный оракул / создатель |
+| `unban_oracle` | `bool` | Снять бан оракула (`pm_oracle_object.banned_until`) |
+| `unban_creator` | `bool` | Снять бан создателя (`pm_creator_ban_object.banned_until`) |
### `pm_transfer_position_operation` (ID 81)
**Auth:** `active` аккаунта `from`
@@ -213,6 +254,12 @@ Commit-reveal фаза 2: раскрывает ставку и ставит её
| 95 | `pm_leverage_resolve_operation` | Расчёт — плечевая позиция принудительно закрыта: `outcome_index`, `won`, `pool_received`/`bettor_received`, `leverage` |
| 96 | `pm_market_accepted_operation` | Эвалуатор — рынок запущен: оракул принял, self-oracle или авто-приём; замороженные условия + флаг `self_oracle` |
| 97 | `pm_payout_operation` | Расчёт — на каждую активную ставку: `amount` (стейк), `side`/`outcome_index`, `payout` (**0 при проигрыше**) |
+| 100 | `pm_ban_expired_operation` | Временный бан оракула/создателя истёк на `banned_until`: крон снял его (поля `account`, `oracle`, `creator`). Досрочное ручное снятие — через подписанный `pm_unban` |
+| 101 | `pm_market_expired_operation` | Дедлайн `accept_deadline` ожидающего рынка прошёл: оракул не принял/отклонил в течение `pm_oracle_accept_window_sec` — рынок аннулирован, сид возвращён (комиссия за создание удержана). Поля `oracle`, `creator`, `market_id`, `refunded_liquidity` |
+
+> ID 91–93 — это *обычные* операции `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert` (см.
+> спецификацию); ID 98–99 — *обычные* операции `pm_dispute_oracle_respond`/`pm_unban` (выше). Результаты
+> по каждому беттору — `pm_payout`; помарочный `pm_auto_payout` остаётся маркером расчёта.
---
diff --git a/@l10n/ru/docs/protocol/operations/validators.md b/@l10n/ru/docs/protocol/operations/validators.md
index 845b5363c4..bdeca93009 100644
--- a/@l10n/ru/docs/protocol/operations/validators.md
+++ b/@l10n/ru/docs/protocol/operations/validators.md
@@ -163,4 +163,39 @@
---
+## `set_reward_sharing_operation` (ID 64)
+
+**Авторизация:** `active` `owner`
+
+**Разделение награды валидатора (HF13).** Валидатор выбирает пересылать часть своей блочной награды своим **стейкхолдерам** — аккаунтам, проголосовавшим за него, — пропорционально взвешенному по времени весу голоса. `sharing_rate` — эта доля в базисных пунктах; общий пул накапливается и распределяется в конце каждой эпохи через виртуальную операцию `stakeholder_reward`.
+
+| Поле | Тип | Описание |
+|------|-----|---------|
+| `owner` | `account_name_type` | Валидатор, устанавливающий свою долю разделения |
+| `sharing_rate` | `uint16_t` | Доля блочной награды, пересылаемая стейкхолдерам, в базисных пунктах (0 = ничего, 10000 = 100%) |
+
+```json
+[64, {
+ "owner": "alice",
+ "sharing_rate": 2500
+}]
+```
+
+- `sharing_rate` ограничен сверху значением 10000 (100%).
+- Распределение идёт по **взвешенному по времени** весу голоса, поэтому недавно добавленные голоса получают меньшую долю, пока не «созреют».
+
+---
+
+## `stakeholder_reward_operation` (ID 65) — виртуальная
+
+Эмитируется в каждую эпоху распределения, когда валидатор с ненулевым `sharing_rate` выплачивает стейкхолдеру его долю общей блочной награды. Виртуальная (никогда не подписывается); появляется в `account_history`.
+
+| Поле | Тип | Описание |
+|------|-----|---------|
+| `validator` | `account_name_type` | Валидатор, разделивший награду |
+| `stakeholder` | `account_name_type` | Голосующий, получающий долю |
+| `shares` | `asset` (SHARES) | Сумма, зачисленная стейкхолдеру |
+
+---
+
См. также: [Типы данных](../data-types.md), [Обзор операций](./overview.md), [Свойства цепочки](../../governance/chain-properties.md).
diff --git a/@l10n/ru/docs/protocol/virtual-operations.md b/@l10n/ru/docs/protocol/virtual-operations.md
index a0d2b158da..909bbf4a06 100644
--- a/@l10n/ru/docs/protocol/virtual-operations.md
+++ b/@l10n/ru/docs/protocol/virtual-operations.md
@@ -351,4 +351,32 @@
---
+## Прогнозные рынки (HF14)
+
+Эмитируются логикой консенсуса PM — **не** кроном по часам. Два источника:
+- **Эвалуатор подписанной операции**, в момент её применения — `pm_market_accepted` (при accept / self-oracle / авто-приёме) и `pm_leverage_liquidate` (при встречной или отменяющей ставке, толкающей плечевую позицию за порог).
+- **Обработчик дедлайнов `process_pm_markets()`**, запускаемый каждый блок: рассчитывает рынки, достигшие **экспирации / дедлайна / окончания окна спора / границы эпохи** (кап `pm_processing_cap_per_block`, старейший дедлайн первым).
+
+См. [Операции прогнозных рынков](./operations/prediction-markets.md). (ID 91–93 — это *обычные* операции `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert`, а ID 98–99 — *обычные* операции `pm_dispute_oracle_respond`/`pm_unban` — см. ту страницу.)
+
+| ID | Операция | Триггер |
+|----|----------|---------|
+| 84 | `pm_batch_settle_operation` | Достигнута граница эпохи: очередные ставки по снимку начала эпохи |
+| 85 | `pm_commit_forfeit_operation` | `reveal_deadline` без раскрытия: штраф → `forfeit_pool`, остаток возвращён |
+| 86 | `pm_auto_payout_operation` | Истекло окно спора (по рынку): паримутюэль-расчёт + возврат принципала LP |
+| 87 | `pm_dispute_finalize_operation` | Достигнут `voting_end_time`: подсчёт решает; штраф оракулу; пере-разрешение/поддержка |
+| 88 | `pm_dispute_auto_close_operation` | Достигнут `auto_close_time`, оракул не ответил: анти-фриз возврат, слэш страховки → DAO |
+| 89 | `pm_oracle_missed_penalty_operation` | Истёк `result_expiration` без разрешения: слэш → DAO, возврат всех ставок |
+| 90 | `pm_lazy_recall_operation` | Простаивающая аллокация lazy-пула достигла шага отзыва: один поэтапный шаг возвращён в пул |
+| 94 | `pm_leverage_liquidate_operation` | Эвалуатор — ликвидация плеча в ходе рынка (встречная `0` / отменяющая `1` ставка, каскад) |
+| 95 | `pm_leverage_resolve_operation` | Расчёт — плечевая позиция принудительно закрыта по `cancel_value`: `outcome_index`, `won`, `pool_received`/`bettor_received`, `leverage` |
+| 96 | `pm_market_accepted_operation` | Эвалуатор — рынок запущен: оракул принял, self-oracle или авто-приём; замороженные условия + флаг `self_oracle` |
+| 97 | `pm_payout_operation` | Расчёт — на каждую активную ставку: `amount` (стейк), `side`/`outcome_index`, `payout` (**0 при проигрыше**); рядом с порыночной `pm_auto_payout` |
+| 100 | `pm_ban_expired_operation` | Временный бан оракула/создателя истёк на `banned_until`: крон очистил его (`account`, `oracle`, `creator`). Досрочные ручные снятия используют подписанную `pm_unban` |
+| 101 | `pm_market_expired_operation` | Дедлайн `accept_deadline` пендинг-рынка прошёл: оракул не принял/отклонил в течение `pm_oracle_accept_window_sec` — рынок аннулирован (`status -1`), сид-ликвидность возвращена (`refunded_liquidity`), комиссия за создание **не** возвращена (`oracle`, `creator`, `market_id`, `refunded_liquidity`) |
+
+Всё движение средств PM строго zero-sum (без эмиссии); расчёт сохраняет `Σ out == Σ ставок + принципал LP + forfeit_pool`.
+
+---
+
См. также: [Обзор операций](./operations/overview.md), [Награды](./operations/awards.md), [Комитет](./operations/committee.md).
diff --git a/@l10n/zh-CN/docs/advanced/hardfork-management.md b/@l10n/zh-CN/docs/advanced/hardfork-management.md
index a2dbd920a3..47c5017731 100644
--- a/@l10n/zh-CN/docs/advanced/hardfork-management.md
+++ b/@l10n/zh-CN/docs/advanced/hardfork-management.md
@@ -48,6 +48,8 @@ VIZ Ledger 通过确定性硬分叉系统协调协议升级。硬分叉在编译
| 10 | 通胀模型 |
| 11 | 发行模型变更 |
| 12 | 紧急共识恢复(见下文) |
+| 13 | 分配纪元长度(`chain_properties_hf13`) |
+| 14 | 预测市场 (Onix):18 个操作 + 7 个虚拟操作,CPMM/LMSR、同注分彩结算、预言机、争议、承诺-揭示、懒惰池;链属性 v5 |
---
diff --git a/@l10n/zh-CN/docs/governance/chain-properties.md b/@l10n/zh-CN/docs/governance/chain-properties.md
index 4ca4e23efa..41d7487dfa 100644
--- a/@l10n/zh-CN/docs/governance/chain-properties.md
+++ b/@l10n/zh-CN/docs/governance/chain-properties.md
@@ -122,8 +122,28 @@
| `chain_properties_hf4` | 1 | HF4 | inflation_validator_percent、inflation_ratio_committee_vs_reward_fund、inflation_recalc_period |
| `chain_properties_hf6` | 2 | HF6 | data_operations_cost_additional_bandwidth、validator_miss_penalty_percent、validator_miss_penalty_duration |
| `chain_properties_hf9` | 3 | HF9 | create_invite_min_balance、committee_create_request_fee、create_paid_subscription_fee、account_on_sale_fee、subaccount_on_sale_fee、validator_declaration_fee、withdraw_intervals |
+| `chain_properties_hf13` | 4 | HF13 | distribution_epoch_length |
+| `chain_properties_pm` | 5 | HF14 | ~30 个预测市场参数 + 终止开关 `pm_commit_reveal_enabled`、`pm_lazy_pool_enabled` |
-所有新的验证者属性提交请使用版本索引 3(`chain_properties_hf9`)。
+所有新的验证者属性提交请使用版本索引 **5**(`chain_properties_pm`)。索引 4 为 `chain_properties_hf13`(`distribution_epoch_length`)。
+
+### 预测市场参数 (v5, HF14) {#pm-parameters}
+
+均为中位数投票;参见 [预测市场操作](../protocol/operations/prediction-markets.md)。
+
+所有 PM 百分比均以 bp 计(10000 = 100.00%),与其他 `*_percent` 一致;不再使用千分比(‰)。
+
+- **预言机:** `pm_min_oracle_insurance`、`pm_max_oracle_fee_percent`(**唯一**的费率治理上限——针对预言机 %)、`pm_oracle_registration_fee`、`pm_oracle_penalty_percent`、`pm_oracle_dispute_response_sec`、`pm_oracle_accept_window_sec`(默认 3600 = 1 小时——指定的预言机须在此窗口内接受或拒绝待定市场;超时后 cron 向创建者退还种子流动性,但**不**退还创建费,并作废市场 → `pm_market_expired`)。
+- **风险 / 覆盖率** *(市场下注量的百分比,100 = 1.0×):* `pm_listing_min_coverage_percent`(250 = 2.5×)——预言机保险覆盖低于其下注量此比例的市场,会从默认 `list_markets` 目录中隐藏(经 `show_risky` 显示);`pm_betting_min_coverage_percent`(150 = 1.5×)——建议性阈值,发布供客户端在下注前要求显式风险确认(不在链上强制;须 `≤ pm_listing_min_coverage_percent`)。
+- **市场:** `pm_min_liquidity`、`pm_market_creation_fee`、`pm_max_outcomes`、`pm_max_market_duration`。*(无聚合费率上限;creator/liquidity 费率无上限、自我约束;静态 `总和 ≤ 100%` 偿付不变式。)*
+- **批次 / 承诺-揭示:** `pm_batch_epoch_blocks`、`pm_reveal_window_blocks`、`pm_min_batch_bet`、`pm_commit_no_reveal_penalty_percent`、`pm_commit_reveal_enabled`。
+- **争议:** `pm_dispute_fee`、`pm_dispute_grace_sec`、`pm_dispute_vote_period_sec`、`pm_dispute_auto_close_sec`、`pm_dispute_approve_min_percent`、`pm_no_contest_penalty_percent`、`pm_dispute_reward_multiplier`(bp 乘数,10000 = 1×)。
+- **时间惩罚:** `pm_default_time_penalty_percent`、`pm_max_time_penalty`。
+- **懒惰池:** `pm_lazy_pool_enabled`、`pm_lazy_alloc_percent`、`pm_lazy_max_total_alloc_percent`、`pm_lazy_recall_step_percent`、`pm_lazy_lock_sec`、`pm_lazy_emergency_penalty_percent`、`pm_lazy_min_liquidity_fee_percent`(默认 200 = 2%——池拒绝为 `liquidity_fee_percent` 低于此奖励下限的市场共同提供流动性)。
+- **杠杆(可选):** `pm_leverage_enabled`、`pm_leverage_fund_percent`、`pm_leverage_max_per_position_bp`、`pm_leverage_max_position_ratio_percent`、`pm_leverage_min_market_liquidity`、`pm_leverage_safety_margin_percent`、`pm_leverage_max_slippage_percent`、`pm_leverage_m_factor_percent`、`pm_leverage_pool_profit_percent`、`pm_leverage_expiration_buffer_sec`、`pm_conversion_profit_cost_percent`。
+- **公平性:** `pm_processing_cap_per_block`。
+
+三个 `*_enabled` 标志(`pm_commit_reveal_enabled`、`pm_lazy_pool_enabled`、`pm_leverage_enabled`)为实时终止开关:验证者中位数可在无需新硬分叉的情况下停用承诺-揭示、懒惰池或杠杆。
---
diff --git a/@l10n/zh-CN/docs/node/configuration.md b/@l10n/zh-CN/docs/node/configuration.md
index 417e8d778e..510c9a48c6 100644
--- a/@l10n/zh-CN/docs/node/configuration.md
+++ b/@l10n/zh-CN/docs/node/configuration.md
@@ -158,6 +158,11 @@ skip-virtual-ops = false
# 允许在链过期时生产(仅用于开发/测试网)
enable-stale-production = false
+# 禁用少数派 fork 检测(仅用于单运营者测试网/分叉)。
+# 与 enable-stale-production 不同,它不会在健康参与度下被自动清除。
+# 切勿在真实的公共网络上启用。
+disable-minority-fork-detection = false
+
# 生产区块所需的最低参与度 % (0–99)
required-participation = 33
@@ -203,4 +208,4 @@ logger.p2p.appenders = p2p
| `plugins/chain/plugin.hpp` | `shared-file-size`, `min-free-shared-file-size`, `inc-shared-file-size`, `block-num-check-free-size`, `single-write-thread`, `enable-plugins-on-push-transaction`, `read-wait-micro`, `max-read-wait-retries`, `write-wait-micro`, `max-write-wait-retries`, `skip-virtual-ops`, `clear-votes-before-block`, `track-account-range`, `history-whitelist-ops`, `history-blacklist-ops`, `history-start-block` |
| `plugins/p2p/p2p_plugin.hpp` | `p2p-endpoint`, `p2p-max-connections`, `p2p-seed-node`, `checkpoint` |
| `plugins/webserver/webserver_plugin.hpp` | `webserver-http-endpoint`, `webserver-ws-endpoint`, `webserver-thread-pool-size` |
-| `plugins/validator/validator.hpp` | `enable-stale-production`, `required-participation`, `validator`, `private-key`, `emergency-private-key`, `fork-collision-timeout-blocks`, `ntp-server`, `ntp-request-interval`, `debug-block-production` |
+| `plugins/validator/validator.hpp` | `enable-stale-production`, `disable-minority-fork-detection`, `required-participation`, `validator`, `private-key`, `emergency-private-key`, `fork-collision-timeout-blocks`, `ntp-server`, `ntp-request-interval`, `debug-block-production` |
diff --git a/@l10n/zh-CN/docs/node/validator-node.md b/@l10n/zh-CN/docs/node/validator-node.md
index 26ae1c8257..d3b9f838e1 100644
--- a/@l10n/zh-CN/docs/node/validator-node.md
+++ b/@l10n/zh-CN/docs/node/validator-node.md
@@ -164,6 +164,9 @@ docker run -d \
### 少数派 Fork 检测
如果节点的 fork 数据库显示 21+ 个连续区块全部来自此节点自己的验证者,它会自动回滚到 LIB 并重新同步。这可以捕获网络隔离情况。
+> [!WARNING] 单运营者分叉
+> 在**一个运营者控制所有验证者**的测试网或主网分叉上,「连续 21 个区块都是我们的」是正常的健康状态,因此检测器会无限循环地回滚到 LIB。此时 `enable-stale-production = true` **无效**:在参与率 ≥33% 时,该覆盖会在每个区块被自动清除。请改用 `disable-minority-fork-detection = true`——它会完全绕过标准检测路径和 DLT 检测路径,并且永远不会被自动清除。**切勿在真实的公共网络上启用**——它会移除隔离保护。
+
### 生产 Watchdog
如果在 `should_be_producing` 为 true 的情况下 180 秒内(紧急主节点为 60 秒)没有生产区块,watchdog 会自动清除卡住的标志(`minority_fork_recovering`、P2P 追赶、链同步)并尝试恢复生产。
diff --git a/@l10n/zh-CN/docs/plugins/prediction-market-api.md b/@l10n/zh-CN/docs/plugins/prediction-market-api.md
new file mode 100644
index 0000000000..6a350cfcd4
--- /dev/null
+++ b/@l10n/zh-CN/docs/plugins/prediction-market-api.md
@@ -0,0 +1,186 @@
+# `prediction_market_api` 插件
+
+对 HF14 预测市场状态的只读 JSON-RPC 访问(市场、下注、预言机、流动性、争议、懒惰池、链属性 v5)。插件直接返回原始共识 `pm_*` 对象,外加少量计算型 DTO。
+
+**启用:** 将 `prediction_market_api` 加入节点插件列表(`vizd` 默认注册)。依赖 `chain` + `json_rpc`。所有列表方法通过 `from`(跳过)与 `limit`(`≤ 1000`)分页。
+
+## 方法
+
+### 市场
+
+| 方法 | 参数 | 返回 |
+|------|------|------|
+| `get_market` | `market_id` | `pm_market_object` |
+| `list_markets` | `status, from, limit, [show_risky]` | `pm_market_object[]` |
+| `list_markets_by_oracle` | `oracle, from, limit` | `pm_market_object[]` |
+| `list_markets_by_creator` | `creator, from, limit` | `pm_market_object[]` |
+| `get_market_outcomes` | `market_id` | `pm_outcome_object[]` |
+| `get_market_weight_sums` | `market_id` | `pm_market_weight_sums`(计算型) |
+| `get_market_bets` | `market_id, from, limit` | `pm_bet_object[]` |
+| `get_market_liquidity` | `market_id, from, limit` | `pm_liquidity_object[]` |
+| `get_market_full` | `market_id, [account]` | `pm_market_full`(计算型) |
+
+`list_markets` 的 `status`:`-1` 已删除、`0` 等待、`1` 活跃、`2` 关闭、`3` 已裁定。默认情况下
+`list_markets` 会隐藏保证金不足的市场(预言机保险 < 下注量的 **2.5×**);`show_risky = true` 可显示它们
+(仅隐藏,链上始终允许下注)。
+
+`get_market_full` 是市场详情页的**单次调用富集视图**:返回市场 + 结果 + 权重合计 + 预言机(含可靠度)+ 已解析元数据,且——当提供可选 `account` 时——返回该账户**在本市场上**的下注、杠杆头寸与 LP。为瘦客户端省去多次往返。
+
+### 市场元数据(链下解析)
+
+每个市场携带一个自由格式、共识不透明的 `metadata` JSON 字符串。插件将其索引的键(类别 / 子类别 / 标签 /
+受禁司法辖区)解析为 `pm_market_meta_object`——**仅用于展示/索引,非共识**。
+
+| 方法 | 参数 | 返回 |
+|------|------|------|
+| `get_market_meta` | `market_id` | `pm_market_meta_object`(无则报错) |
+| `list_markets_by_category` | `category, from, limit, [jurisdiction], [subcategory], [tag], [sort]` | `pm_market_meta_object[]` |
+| `get_market_categories` | — | `pm_market_categories`(计算型) |
+
+`list_markets_by_category` 会排除其 `banned_jurisdictions` 含可选 ISO 代码 `jurisdiction` 的市场(受监管
+客户端传入自身辖区即可只获取可列出的市场)。可选的 `subcategory`(精确)与 `tag`(CSV 成员)进一步收窄集合;
+`sort` ∈ `newest`(市场 id 降序,默认)· `oldest` · `volume`(`bets_sum` 降序)· `expiration`(`betting_expiration`
+升序)。`get_market_categories` 返回实时分类法——每类别 / 每子类别计数,外加前 20 个热门标签(排除 jurisdiction-*)——
+在当前已索引市场上聚合,使浏览 UI 无需硬编码分类法即可构建其筛选标签。对象:`market`、`category`、`subcategory`、`tags`(逗号分隔)、
+`banned_jurisdictions`(逗号分隔 ISO;为空 = 全球允许)、`expiry`(争议窗口关闭 + TTL 后清理)。
+
+### 持仓与预言机
+
+| 方法 | 参数 | 返回 |
+|------|------|------|
+| `get_account_positions` | `account, from, limit` | `pm_position[]`(下注 + `expected_payout`) |
+| `get_account_leverage_positions` | `account, from, limit` | `pm_leverage_position_object[]` |
+| `get_market_leverage_positions` | `market_id, from, limit` | `pm_leverage_position_object[]` |
+| `get_creator_ban` | `account` | `pm_creator_ban_object`(无则报错) |
+| `get_oracle` | `owner` | `pm_oracle`(对象 + `reliability_score`) |
+| `list_oracles` | `from, limit` | `pm_oracle_object[]` |
+
+> 每位下注者的结算以 `pm_payout` 虚拟操作发出(本金、side/outcome、结果;输则为 `0`);杠杆头寸的结算为
+> `pm_leverage_resolve`(`outcome_index`、`won`、`leverage`)。两者均见于 `account_history`;头寸对象本身可经上述方法查询。
+
+### 杠杆预览(Boost)
+
+只读报价,调用评估器所用的**同一套节点内保证金数学**,因此预览与对应 `pm_leverage_*` 操作在头区块的计算结果一致。它们是非共识估算(读取与广播之间储备会变动——务必发送链上滑点保护)。
+
+| 方法 | 参数 | 返回 |
+|------|------|------|
+| `get_leverage_quote` | `market_id, outcome_index, collateral` | `pm_leverage_quote`(计算型) |
+| `get_leverage_close_preview` | `position_id` | `pm_leverage_close_preview`(计算型) |
+| `get_leverage_convert_preview` | `position_id` | `pm_leverage_convert_preview`(计算型) |
+
+`get_leverage_quote` 镜像 `pm_leverage_open`:返回最大偿付贷款与由此得到的最大杠杆、池/头寸上限、至多 12 个滑块档位(每档含代币、阈值、当前及最坏情形取消价值),且——当无法杠杆时——返回 `available = false` 并附 `failed_constraints[]` 列表。`get_leverage_close_preview` / `get_leverage_convert_preview` 在当前储备下镜像 `pm_leverage_close` / `pm_leverage_convert`(取消价值、池义务、下注者所得、是否可平仓/可转换,以及按当前中位数 `pm_conversion_profit_cost_percent` 的转换费)。
+
+### 争议、懒惰池、治理
+
+| 方法 | 参数 | 返回 |
+|------|------|------|
+| `get_dispute` | `market_id` | `pm_dispute_object` |
+| `get_dispute_votes` | `market_id` | `pm_dispute_votes`(投票 + 实时计票) |
+| `get_lazy_pool` | — | `pm_lazy_pool_object` |
+| `get_lazy_deposit` | `account` | `pm_lazy_deposit_object` |
+| `get_lazy_allocations` | `from, limit` | `pm_lazy_allocation_object[]` |
+| `get_market_lazy_allocation` | `market_id` | `pm_lazy_allocation_object`(无则报错) |
+| `get_pm_chain_properties` | — | `chain_properties_pm`(中位数, v5) |
+
+`get_lazy_allocations` 列出懒惰池的每市场分配记录(用于池仪表盘);`get_market_lazy_allocation` 获取给定市场的那一条。预言机罚分戳记无需单独方法——它们随 `pm_oracle_object`(`penalty_stamps`、`last_penalty_stamp_time`)经 `get_oracle` 一并返回。
+
+### 图表 —— kline / 权重历史
+
+用于绘制每个结果权重随时间变化的时间序列。**每当市场的各结果权重发生变化时**(下注、取消、清算、批次结算、杠杆开仓或杠杆结算),插件追加一个点 —— 即各结果同注分彩权重(下注额)的带时间戳快照。这是**非共识**插件状态(存于 chainbase,undo/redo 安全,不计入状态哈希);历史从插件在节点上首次启用时开始累积。
+
+**保留:** kline 历史与市场元数据**一同清理**,遵循同一时间表 —— `result_expiration` + 争议宽限 + `pmm-ttl-days`(默认 7)。市场的完整图表在其存续期间及结算后的保留窗口内均可用,随后两个索引被清理(极长历史会分多个区块逐步清空),以使节点存储保持有界。
+
+| 方法 | 参数 | 返回 |
+|------|------|------|
+| `get_market_kline` | `market_id, [from], [limit]` | `pm_kline[]`(按 `seq` 升序) |
+
+分页为**从最新偏移**(为瘦客户端刻意保持简单):`from` 为跳过的**最新**点数量,`limit ≤ 1000` 为页大小。
+- `(market_id, 0, 1000)` → 最新 ≤ 1000 个变化。
+- `(market_id, 1000, 1000)` → 再往前一页的 1000 个 —— 以 `from += 1000` 重复以惰性加载更早历史。
+
+绘图:x = `timestamp`(unix 秒),每个结果 `i` 一条线,y = `weights[i]`(或归一化 `weights[i] / Σweights`,即隐含概率)。
+
+## 计算型 DTO
+
+- **`pm_position`** —— 下注 + `expected_payout`(若该方获胜的赔付,或结算后已实现值;与 `settle_market` 逐字节一致)、`market_status`、`resolved_outcome`。
+- **`pm_oracle`** —— 预言机对象 + `reliability_score`(bp `[0..10000]`,非共识启发式:裁定成功率与争议胜率的混合,再减去封禁罚分)。
+- **`pm_market_weight_sums`** —— 各方/各结果的 `bets_sum`/`weight_sum`(权重通过扫描下注计算,因其不存储)。
+- **`pm_kline`** —— 一个图表点:`seq`(uint32,0 起、按市场单调递增的变化索引)、`timestamp`(unix 秒,x)、`reason`(uint8:0 下注、1 取消、2 清算、3 批次结算、4 杠杆开仓、5 杠杆结算)、`bets_sum`(总下注额)、`weights[]`(每个结果的权重,y;索引 = outcome_index)。
+- **`pm_dispute_votes`** —— 投票 + finalize 计票。旧字段(权重 = `|vote_percent|`,非质押):`uphold_weight`/`challenge_weight`/`total_weight`、`challenger_leads`(≥ `pm_dispute_approve_min_percent`)、`proposed_outcome`。**精确的按质押加权投影(镜像 `pm_dispute_finalize`;所有 `*_shares` 为 vesting-shares = `effective_vesting_shares` + 懒惰池质押→shares):** `participation_shares`(已投票者权重之和)、`electorate_shares`(`total_vesting_shares` + 池 NAV→shares)、`quorum_required_shares`、`quorum_percent_bp`(法定人数,bp,10000 = 100.00%)、`quorum_reached`(bool)、`oracle_defense_shares`/`change_shares`、`outcome_change_shares[]`(按结果)、`expected_uphold`(预言机裁决是否维持)、`expected_outcome`(当前若裁决将设定的结果)、`expected_consensus_strength_bp`。该投影与定时任务在 `voting_end_time` 按当前投票应用的结果一致(在此之前投票可更改)。
+
+**`pm_market_full`** —— 单次调用的富集市场视图(`oracle`/`meta` 缺失时为 `null`;除非提供 `account` 参数,否则 `my_*` 数组为空):
+```
+{ market: pm_market_object,
+ outcomes: pm_outcome_object[], // 二元市场为空
+ weight_sums: pm_market_weight_sums,
+ oracle: pm_oracle | null,
+ meta: pm_market_meta_object | null,
+ my_positions: pm_position[], // 账户在本市场的下注
+ my_leverage_positions: pm_leverage_position_object[],
+ my_liquidity: pm_liquidity_object[] }
+```
+
+**`pm_leverage_quote`** —— 杠杆开仓预览(来自 `pm::leverage::*`,即评估器运行的同一套数学):
+```
+{ available: bool, outcome_index, collateral,
+ max_loan, max_leverage_x100, // 100 = 1.00×
+ pool_free_amount, fund_available, per_position_cap, market_position_cap,
+ pool_profit_percent, safety_margin_percent, max_slippage_percent, m_factor_percent,
+ expiration_buffer_sec, auto_close_time, // betting_expiration − buffer
+ stops: [ { leverage_x100, loan, total_bet, expected_tokens, pool_profit,
+ liquidation_threshold, current_cancel_value, worst_case_cancel_value } ],
+ failed_constraints: [ { constraint, reason } ] } // !available 时填充
+```
+**`pm_leverage_close_preview`** —— `{ position_id, outcome_index, cancel_value, pool_obligation, bettor_receives, collateral, loan, pool_profit_charge, closeable: bool, loss_vs_collateral, loss_percent_bp }`。
+**`pm_leverage_convert_preview`** —— `{ position_id, outcome_index, cancel_value, pool_obligation, current_profit, conversion_profit_cost_percent, conversion_fee, total_user_payment, convertible: bool }`。
+
+**`pm_market_categories`** —— 带实时计数的浏览分类法:
+```
+{ categories: [ { category, count, subcategories: [ { subcategory, count } ] } ], // 按 count 降序
+ hot_tags: [ { tag, count } ] } // 前 20(排除 jurisdiction-*)
+```
+
+## 示例
+
+市场 `42` 最新 1000 个图表点,再取前 1000 个:
+```bash
+# 最新一页
+curl -s --data '{"jsonrpc":"2.0","id":1,"method":"call",
+ "params":["prediction_market_api","get_market_kline",[42,0,1000]]}' http://127.0.0.1:8090
+# 往前一页
+curl -s --data '{"jsonrpc":"2.0","id":1,"method":"call",
+ "params":["prediction_market_api","get_market_kline",[42,1000,1000]]}' http://127.0.0.1:8090
+```
+
+瘦客户端(向后滚动时惰性加载更早历史)—— 将每个点按结果转成 `{ x: unixtime, y: weight }` 序列:
+```js
+async function call(method, params) {
+ const r = await fetch('http://127.0.0.1:8090', { method: 'POST',
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'call',
+ params: ['prediction_market_api', method, params] }) });
+ return (await r.json()).result;
+}
+
+// 从最新向后按每页 1000 拉取,直到凑够 `want` 个点(或历史耗尽)。
+async function loadKline(marketId, want = 3000) {
+ const points = [];
+ for (let from = 0; points.length < want; from += 1000) {
+ const page = await call('get_market_kline', [marketId, from, 1000]);
+ if (!page.length) break; // 到达历史起点
+ points.unshift(...page); // 页内升序;更早的页前置
+ if (page.length < 1000) break;
+ }
+ return points;
+}
+
+// 每个结果一条 {x,y} 序列 —— 直接喂给任意图表库。
+function toSeries(points, outcomeCount) {
+ const series = Array.from({ length: outcomeCount }, () => []);
+ for (const p of points)
+ for (let i = 0; i < outcomeCount; i++)
+ series[i].push({ x: p.timestamp, y: Number(p.weights[i]) });
+ return series;
+}
+```
+
+参见 [预测市场操作](../protocol/operations/prediction-markets.md) 与 [链属性](../governance/chain-properties.md#pm-parameters)。
diff --git a/@l10n/zh-CN/docs/plugins/validator.md b/@l10n/zh-CN/docs/plugins/validator.md
index 1b8ee701de..d6215fe8a2 100644
--- a/@l10n/zh-CN/docs/plugins/validator.md
+++ b/@l10n/zh-CN/docs/plugins/validator.md
@@ -24,6 +24,7 @@ chain::plugin, p2p::p2p_plugin, snapshot::snapshot_plugin
| `private-key` | — | 用于签名的 WIF 私钥;可重复 |
| `emergency-private-key` | — | 紧急共识的 WIF 密钥;自动将 `CHAIN_EMERGENCY_VALIDATOR_ACCOUNT` 添加到验证者集合 |
| `enable-stale-production` | `false` | 绕过参与度和同步检查(仅用于测试网/网络恢复) |
+| `disable-minority-fork-detection` | `false` | 完全跳过少数派 fork 检测(仅用于单运营者测试网/分叉)。在健康参与度下永远不会被自动清除——参见[少数派 Fork 检测](#少数派-fork-检测) |
| `required-participation` | `3300` | 最低验证者参与度(**基点**,3300 = 33%) |
| `fork-collision-timeout-blocks` | `21` | 强制生产前的连续 fork 冲突延迟次数(一个完整的验证者轮次) |
@@ -116,7 +117,8 @@ T=6.000s 的槽位:
在每次生产尝试之前(在 HF12 安全检查之后),插件遍历 `fork_db` 中最后 21 个区块。如果所有 21 个都由节点自己配置的验证者生产,则节点被隔离在少数派 fork 上。
- **默认操作:** 调用 `p2p().resync_from_lib()` — 回滚到 LIB,重置 fork DB,重新启动 P2P 同步,重新连接种子节点。返回 `minority_fork`。
-- **使用 `enable-stale-production=true`:** 记录警告,继续生产。
+- **使用 `enable-stale-production=true`:** 记录警告,继续生产。**注意:** 在参与度 ≥33% 时,该覆盖会在每个区块被自动清除,因此在单运营者分叉上它*不会*停止检测器——请改用 `disable-minority-fork-detection`。
+- **使用 `disable-minority-fork-detection=true`:** 完全跳过标准检测路径和 DLT 检测路径,且该标志永远不会被自动清除。适用于「连续 21 个区块都是我们的」为健康稳态的单运营者测试网/分叉。**切勿在真实的公共网络上启用**——它会移除隔离保护。
- **跳过时机:** 紧急共识激活时(committee 区块总会匹配我们配置的集合)。在紧急模式下,DLT 特定的从节点隔离检查取代它。
---
@@ -217,7 +219,7 @@ validator[skip_flags=0x0 catching_up=0 head=#79881136 last_prod=45s_ago minority
| `no_private_key` | 配置中缺少链上注册的签名密钥对应的 `private-key` |
| `low_participation` | 网络参与度 < 33%;检查节点连接或设置 `enable-stale-production=true` |
| `fork_collision` | 下一高度有竞争区块;等待投票权重解决或 21 次延迟超时 |
-| `minority_fork` | 已隔离;插件自动重新同步到 LIB |
+| `minority_fork` | 已隔离;插件自动重新同步到 LIB。在单运营者分叉上会无限循环——请设置 `disable-minority-fork-detection=true` |
| Watchdog 重复触发 | 同步或追赶标志卡住;头部推进时 watchdog 会自动清除 |
| `SLOT-HIJACK` 日志 | 紧急主节点清空了我们的密钥;通过 `validator_update_operation` 恢复 |
diff --git a/@l10n/zh-CN/docs/prediction-markets/concepts-analysis.md b/@l10n/zh-CN/docs/prediction-markets/concepts-analysis.md
new file mode 100644
index 0000000000..c2b271258d
--- /dev/null
+++ b/@l10n/zh-CN/docs/prediction-markets/concepts-analysis.md
@@ -0,0 +1,204 @@
+---
+title: 概念分析 —— Onix 对照 90 个预测市场概念
+description: VIZ 链上实现(Onix 协议、Forecaster 客户端)如何映射到 90 个预测市场理论概念——哪些已解决、固有、不需要或在路线图中。
+---
+
+# 概念分析 —— Onix 对照 90 个理论概念
+
+> **Forecaster** 是 VIZ 链上预测市场的瘦客户端——让全球用户通过签署 `pm_*` 操作参与的接入层
+> (见[板块总览](./))。**Onix** 是它所对接的协议。本页将 **90 个 PM-Atlas 预测市场概念**逐一映射到
+> **VIZ 链上实现如何处理它**,以及该概念对本架构**是否必要**。
+>
+> 依据文档:[白皮书](./whitepaper)、[规范](./specification)、[工作流与争议](./workflows)。
+
+## 图例
+
+| 标记 | 含义 |
+|------|---------|
+| ✅ **已解决** | Onix 设计直接解决/处理 |
+| ⚪ **固有** | 由构造自然具备(无需额外工作) |
+| ➖ **不需要** | 在 Onix 下架构上不必要 |
+| 🟡 **部分 / 路线图** | 今日部分解决;其余在 VIZ 路线图 |
+| 🏛 **客户端层** | 由司法辖区客户端处理,而非协议 |
+| 🔴 **开放 / 风险** | 仍是现实关切;未完全解决 |
+
+与其他平台最大的结构性差异:**LP 本金结构性保障(赢家只从输家被没收的本金中获得赔付),二元用 CPMM、多元用
+LMSR-softmax + 同注分彩结算,且无订单簿。** 大量「流动性与交易」概念存在的目的就是管理做市商库存风险,因此对
+Onix **不适用**。
+
+> **链上实化(HF14 / 上线)。** 本映射最初针对白皮书/规范撰写。若干当时标为*路线图*的项现已**实现为共识操作**
+> 并在 `consensus_sim` 验证:批量拍卖 + 提交-揭示下注(`pm_commit_bet`/`pm_reveal_bet`/`pm_batch_settle`,
+> 二元,中位数 kill-switch `pm_commit_reveal_enabled`);可选杠杆子系统(`pm_leverage_open/close/convert`,
+> 懒惰池提供资金,kill-switch `pm_leverage_enabled`);懒惰池本身;`endogeneity_tier` 字段;链上创建者封禁;
+> 更丰富的结算虚拟操作(`pm_payout`、`pm_leverage_resolve`、`pm_market_accepted`)+ 配套插件 API;以及懒惰
+> 池质押在 PM 争议与 DAO 提案投票中计为治理权重。**刻意未做:** 争议的提交-揭示投票——委员会争议按设计为公开
+> 听证。**仍是路线图:** 自动外生数据预言机。
+
+## 1. 信息论
+
+| # | 概念 | 裁定 | VIZ 上如何处理 / 是否需要 |
+|---|---------|---------|--------------------------------------------------|
+| 1 | **Brier Score** | ⚪ 固有 | 非协议机制,而是评判 Onix 市场的*指标*。每笔下注/裁定都是共识事件,价格史与结果全在链上 → 任何人可对平台(及预言机)做 Brier 评分。仅作分析/声誉输入,非核心逻辑。 |
+| 2 | **Calibration** | ⚪ 固有 | Onix 价格是真实概率(CPMM、LMSR softmax 之和为 1)。校准是可*测量*的涌现属性,由时间惩罚与深度 LP 间接改善;协议不强制。 |
+| 3 | **Credibility Markets** | 🟡 部分 | 保证金预言机 + 14 项声誉 + 综合信任分本质上是*裁决者*的信誉市场。「以信誉质押于结果」是原生的。通用「为主张质押信誉」产品可在客户端层构建,非核心。 |
+| 4 | **Distribution Markets** | 🟡 路线图 | Onix Multi(3–10 离散结果)以桶近似分布。真正的连续分布市场(CDF/标量)当前**不**在范围;需标量结果操作。 |
+| 5 | **Endogeneity** | 🟡 部分(已缓解) | 市场改变其所预测之物的风险。由**已上线的 `endogeneity_tier` 字段**与**可选提交-揭示/批量下注(已上线)**缓解,使公开价格在窗口内不「泄露」;外生裁定仍是路线图。见[缓解](#fanshenxing-jiazu-de-huanjie)。 |
+| 6 | **Forecasting Accuracy** | ⚪ 固有 | 整体价值主张。Onix 间接改善:无风险 LP → 更深订单 → 更少滑点 → 更多知情参与 → 更好价格。 |
+| 7 | **Info Finance** | ⚪ 固有 | Onix *就是*信息金融工具:共识操作把信息变为可定价、可结算的头寸。迁移到 VIZ 使信息层抗审查、可组合。 |
+| 8 | **Information Aggregation** | ✅ 已解决 | 核心功能。CPMM/LMSR 把分散下注聚合为单一概率;无风险 LP 深度正是使聚合奏效的杠杆。 |
+| 9 | **Information Asymmetry** | 🟡 部分 | 同注分彩/CPMM 意味着知情者从*其他输家*而非 LP 获利——故不会拖垮流动性。**可选提交-揭示 + 批量下注现已上线(二元):** 承诺下注按统一批价结算,消除 mempool 方向泄露;per-market `allow_batch` + 中位数 kill-switch 保持可选。 |
+| 10 | **Legibility** | ✅ 已解决 | 每个金融动作都是共识 VIZ 操作(前/后储备审计)。任何节点可读可审——远比中心化后端或不透明 CLOB 更清晰。新结算虚拟操作(`pm_payout`、`pm_leverage_resolve`、`pm_market_accepted`)+ 插件方法使逐下注者结果与杠杆结算可直接查询。 |
+| 11 | **Longshot Bias** | 🟡 部分 | CPMM/LMSR 仍可能因下注行为出现冷门偏差;Onix 不直接纠正。时间惩罚与深度抑制扭曲,但偏差是行为输出,未消除。 |
+| 12 | **Noise Decomposition** | ⚪ 固有 | 分析视角,非协议特性。链上价格/成交量序列使「信号 vs 噪声」分解对分析者可行。 |
+| 13 | **Nowcasting** | ⚪ 固有 | Onix 价格随每笔下注更新(~3 秒块),给出实时即时预测。对任何活跃 AMM 市场固有。 |
+| 14 | **Price Discovery** | ✅ 已解决 | CPMM 与 LMSR-softmax 是连续价格发现引擎;价格一致性(`Σ price = 1`)按构造成立,无需套利/拆合层。 |
+| 15 | **Probability Infrastructure** | ✅ 已解决 | 本质上即 Onix 在 VIZ 的论点:预测市场作为**一等公民共识操作**(`pm_*`),而非智能合约——一种底层概率原语。 |
+| 16 | **Superforecasting** | ⚪ 固有 | 个体技能概念;Onix 通过输家→赢家赔付奖励准确下注者。是参与者特征,非协议逻辑。 |
+| 17 | **Wisdom of Crowds** | ✅ 已解决 | Onix 货币化的机制。无风险 LP 降低门槛,让更多群体参与,锐化聚合。 |
+| 18 | **Yes Bias** | 🟡 部分 | 行为上偏向「Yes」。对称 CPMM 与仅作用于利润的时间惩罚结构上不偏 Yes,但也不纠正人类偏差。 |
+
+## 2. 机制设计
+
+| # | 概念 | 裁定 | VIZ 上如何处理 / 是否需要 |
+|---|---------|---------|--------------------------------------------------|
+| 19 | **Binary Contracts** | ✅ 已解决 | Onix Binary = 两结果上的 CPMM(`x·y=k`)。AM-GM 证明保证 `reserve_a+reserve_b ≥ L`,覆盖 LP 本金。主市场类型。 |
+| 20 | **Combinatorial Prediction Markets** | ➖ 不需要(今日) | LMSR 可在组合空间自然分解,但 Onix Multi 限于 3–10 个*独立*结果并刻意省略 CTF 拆/合。组合/条件捆绑明确不在范围。 |
+| 21 | **Incentive Compatibility** | ✅ 已解决 | LMSR 从对数评分规则继承 IC(说真话占优)。Onix 再加保证金预言机(保险 > 操纵利润)、输家资助赢家、时间加权 LP 奖励对齐激励。 |
+| 22 | **Keynesian Beauty Contest** | ✅ 已解决(市场)/ ⚪(争议层——按设计接受) | KBC 是*相对/对等评分*的病态。Onix **市场**层让下注者对外部真相(同注分彩)获酬,故结构上反 KBC——价格错时你因*偏离*而获利。唯一暴露是**按质押加权的委员会争议投票**。**决定:争议提交-揭示投票不会实现**——委员会争议是**公开听证**,DAO 信誉依赖透明裁决。残余 KBC 风险被接受且很小:投票者不因与多数一致而获酬,投票可改至关闭。池内 DAO 成员亦有投票权(懒惰池质押 → vesting-shares,HF14)。见[缓解](#fanshenxing-jiazu-de-huanjie)。 |
+| 23 | **LMSR** | ✅ 已解决(关键创新) | LMSR 在二元上*失败*(0/1 边界永久损失)。Onix 之解:**二元用 CPMM,LMSR 仅用于多元且做市商非对手方**——同注分彩由输家付赢家,故 LMSR 补贴从不受险(`LP 最大损失 = 0` vs `b·ln(N)`)。核心设计动作。 |
+| 24 | **LOX(Log-Odds Excess Lateness)** | ➖ 不需要 | 一种专门的评分/迟到指标。Onix 改用**对利润的二次时间惩罚**处理迟下注激励——更简单的结算期机制。 |
+| 25 | **Market Manipulation** | 🟡 部分 | 保证金预言机(保证金须超操纵利润)、DPoS 校验操作、委员会争议提高操纵成本。靠深度限制大额下注操纵;在**批量/提交-揭示市场(已上线)**上由统一价中和速度狙击(「狙击税」);主动监控仍是路线图。 |
+| 26 | **Market Scoring Rules** | ✅ 已解决 | Onix Multi 是市场评分规则(LMSR)实现,配以同注分彩结算。直接采用。 |
+| 27 | **Multi-Outcome Markets** | ✅ 已解决 | Onix Multi 以 LMSR softmax + 同注分彩处理 N=3–10,`b = S/ln(N)`。一等公民市场类型。 |
+| 28 | **Parimutuel Markets** | ✅ 已解决(基础) | *两种*市场类型的结算均为同注分彩:输家被没收的本金形成 `winners_pool`,按代币份额分配。这使 LP 保障是结构性而非保险性的。 |
+| 29 | **Peer Prediction** | ➖ 不需要 | 无真相的说真话方案。Onix 依赖保证金预言机 + 委员会争议,不采用对等预测评分。 |
+| 30 | **Position Collateralization** | ✅ 已解决(+ 可选杠杆,上线) | 默认每笔下注全额预付(无下注时扣费)——构造上完全抵押。Onix 现**亦**提供该概念的「下一步」:**可选杠杆子系统**(`pm_leverage_open/close/convert`,kill-switch 默认关)。保证金为**来自懒惰池的贷款**(不增发——保持零和),故系统视角持仓足额抵押。会击垮 CLOB 清算引擎的二元「跳空风险」由**按下注前储备清算**处理:对向下注/结算强平回收 `min(cancel_value, obligation) ≥ loan`;唯一有界坏账路径是同侧 `pm_cancel_bet`。清算级联刻意**不**受 kill-switch 门控。 |
+| 31 | **Proper Scoring Rules** | ✅ 已解决 | LMSR 是对数恰当评分规则的成本函数对偶;Onix Multi 继承其真实诱导属性。 |
+| 32 | **Reflexivity** | 🟡 部分(已缓解) | endogeneity 的母概念。由同一套件缓解——**提交-揭示/批量 + `endogeneity_tier` 已上线**,外生裁定仍路线图——**外加链上创建者封禁**(`pm_creator_ban_object`)应对有害反身性(暗杀/「悬赏」、为结果制造「拥趸」的宣传市场);类别*清单*本身在客户端层。深度无风险 LP 亦抬高制造头条操纵的成本。见[缓解](#fanshenxing-jiazu-de-huanjie)。 |
+
+## 3. 流动性与交易
+
+> **要点:** Onix **无订单簿、无承担库存的做市商**。这一大类概念专为管理 CLOB/做市商库存风险而存在,因此对 Onix **不适用**。
+
+| # | 概念 | 裁定 | VIZ 上如何处理 / 是否需要 |
+|---|---------|---------|--------------------------------------------------|
+| 33 | **Adverse Selection** | ✅ 已解决(重构) | 经典问题(知情流拖垮做市商)**无法拖垮 Onix LP**:赢家由输家付,从不动 LP 本金。知情者从其他*下注者*获利,非 LP。消除核心 LP 失效模式。 |
+| 34 | **Arbitrage** | ⚪ 固有 | 市场内套利不必要:CPMM 与 LMSR-softmax 中价格一致性按构造成立。无需拆/合套利层。 |
+| 35 | **Batched Auctions** | ✅ 已解决(可选,上线) | 实现为 **per-market 统一价批量**(`mode=1` + 提交-揭示 → 每 `pm_batch_epoch_blocks` 纪元 `pm_batch_settle`);仅净残量推动 AMM,同侧成交统一定价,中和速度狙击。per-market `allow_batch` + 中位数 kill-switch;今日**仅二元**。LP `Σreserve ≥ L` 不变量不变。 |
+| 36 | **Bid-Ask Spread** | ➖ 不需要 | 无订单簿 → 无报价价差。「交易成本」表现为由深度决定的 CPMM/LMSR 滑点。概念不映射。 |
+| 37 | **Bonding Trades** | ⚪ 固有 | 下注*就是*绑定交易:资金投入储备,仅在裁定(或撤注/转让)时释放。原生行为。 |
+| 38 | **Continuous Double Auction** | ➖ 不需要 | CDA 是 Onix 明确放弃、改用 AMM 定价的 CLOB 模型。 |
+| 39 | **Covariance Markets** | ➖ 不需要 | 交易事件间相关性需 Onix 刻意省略的组合/条件结构。范围外。 |
+| 40 | **Cross-Platform Arbitrage** | 🟡 部分 | Onix 价格可与 Polymarket/Kalshi 背离;跨平台套利可行但在协议外。VIZ 开放 API + 无头客户端使数据可得;无原生跨平台桥。 |
+| 41 | **Execution Quality** | ✅ 已解决(重构) | 无部分成交/排队。执行质量 = 确定性滑点 + 可选 `min_tokens`/`min_return` 保护,共识校验。构造上可预测。 |
+| 42 | **Gap Risk** | ✅ 已解决(对 LP) | 跳空风险(突跳 0/1 抹掉做市商)正是 Onix 结构性 LP 保障消除的失效模式——LP 从不持有输家侧的终端风险。下注者仍承担自身结果风险(如设计)。 |
+| 43 | **Hedging** | 🟡 部分 | 下注者可通过对冲头寸、转让(`pm_transfer_position`)、撤注(若允许)经反向 CPMM,及现在的**可选杠杆**(`pm_leverage_open/convert`)做资本高效对冲。无原生多腿衍生品;基础 + 杠杆对冲可行。 |
+| 44 | **Implied Correlation** | ➖ 不需要 | 需 Onix 省略的多事件/组合市场。范围外。 |
+| 45 | **Insider Trading** | 🟡 部分 / 🏛 客户端 | 协议无法侦测内幕;由时间惩罚(迟到的知情下注利润更少)与保证金裁定缓解。KYC/监控由**客户端层**负责(受监管客户端)。 |
+| 46 | **Kelly Criterion** | ⚪ 固有 | 下注者下注规模策略,非协议特性。Onix 暴露干净概率与完全抵押,便于参与者计算 Kelly;新的**可选杠杆**让下注者以保证金践行分数 Kelly 优势(池资助、受清算约束)。 |
+| 47 | **Liquidity Fragmentation** | 🟡 路线图 | 今日 per-market 池碎片化流动性。白皮书首要路线图项——**共享/类别级 AMM 池**——是架构性修复。懒惰池已跨市场共担*存款*。 |
+| 48 | **Liquidity Provision** | ✅ 已解决(核心差异化) | 无风险 LP 是头条:本金结构性保障、时间加权费用奖励、懒惰池自动分配 + MasterChef 记账。解决了「LP 亏钱」这一驱动整个协议的问题。 |
+| 49 | **Market Making** | ✅ 已解决(重构) | 无需主动做市商——AMM + LP 池*即*做市商,且不承担库存风险。「做市」坍缩为被动、无风险的流动性提供。 |
+| 50 | **Minimum Viable Liquidity** | ✅ 已解决 | 强制下限:最低初始流动性 100 VIZ;懒惰池以 `free_balance × 分配%` 自动为每个新市场播种。MVL 结构性引导而非听天由命。 |
+| 51 | **Order Book** | ➖ 不需要 | Onix 基于 AMM;按设计无订单簿。 |
+| 52 | **Orderflow Arbitrage** | ➖ 不需要 | 无订单簿 / 无 PFOF 式订单流路由 → 不适用。 |
+| 53 | **Relative Value Trading** | ➖ 不需要 | 跨工具相对价值需 Onix 省略的相关/组合市场。范围外。 |
+| 54 | **Retail Flow** | ✅ 已解决(重构) | CLOB 模型中零售流补贴做市商对毒性流的亏损。Onix 无做市商需保护——零售与知情下注者同入一个同注分彩池;LP 无所谓。「零售 vs 毒性」张力在 LP 层消解。 |
+| 55 | **Semantic Tick Size** | ➖ 不需要 | 最小变动价位是 CLOB 概念。Onix 价格是连续 AMM 函数;精度为固定 mVIZ 单位(1/1000)。无需 tick 设计。 |
+| 56 | **Temporal Arbitrage** | 🟡 部分 | 早/晚下注风险不同;Onix 对利润的**时间惩罚**正是把迟到计入价格、抑制「等确定」套利的机制。未消除,但明确不鼓励。 |
+| 57 | **Time Arbitrage** | 🟡 部分 | 与 #56 同族——利用信息时机。二次时间惩罚 + ~3 秒块降低但不消除优势。 |
+| 58 | **Toxic Flow** | ✅ 已解决(对 LP) | 定义性 CLOB/LMSR 问题(狙击手在 99¢ 结果上以 10¢ 扫单,做市商吃 80¢)**不打到 Onix LP**——赔付来自输家本金,LP 补贴无条件返还。毒性流仅意味着知情下注者赢得同注分彩池,如设计。重大结构性胜利。 |
+| 59 | **Wash Trading** | 🟡 部分 / 🏛 客户端 | 下注无即时费用消除了一个 wash 激励,但刷量仍可能;转让纯属重新分配(无费用农场)。侦测/监控是客户端 + 路线图关切。 |
+
+## 4. 预言机与裁定
+
+| # | 概念 | 裁定 | VIZ 上如何处理 / 是否需要 |
+|---|---------|---------|--------------------------------------------------|
+| 60 | **Corruption Value Multiple(CVM)** | ✅ 已解决(设计原则) | 协议显式安全不变式:预言机**保险保证金须超潜在操纵利润**。风险因子(保险/下注比)馈入综合信任分。CVM 直接是保证金理据。 |
+| 61 | **Dispute Resolution** | ✅ 已解决 | 完整系统:12h 宽限、`dispute_fee`、强制预言机响应、per-market 裁决(`dispute_mode==0` 委员会加权投票 / `==1` 指定裁决者)、保险罚没、3 结果 no-contest、14 天自动关闭防冻结、链上创建者/预言机封禁。**HF14 新增:** 懒惰池存款人保留争议投票权重(池 NAV → vesting-shares)。 |
+| 62 | **Oracle Design** | ✅ 已解决 | 保证金预言机模型:注册费、≥5000 VIZ 保险、显式接受、带证据裁定、14 项声誉、新鲜度衰减、封禁机制。核心子系统。 |
+| 63 | **Resolution Criteria** | 🟡 部分 / 🏛 客户端 | 市场问题/标准存于 `url`/描述(仅展示)。协议强制*流程*(谁裁、争议)而非标准*质量*——含糊标准是创建者/客户端责任,事后经争议 + **链上创建者封禁**(`pm_creator_ban_object`,已上线)治理。 |
+| 64 | **Self-Resolving Markets** | ➖ 不需要(今日) | Onix 裁定由预言机驱动,非算法自裁。自动数据预言机是客观市场的高优路线图,可近似自裁。 |
+| 65 | **UMA Protocol** | ➖ 不需要(被替代) | UMA 乐观预言机(Polymarket 使用)由 Onix 保证金预言机 + VIZ 委员会争议模型功能性替代。同一问题,原生 VIZ 之解——无外部预言机依赖。 |
+
+## 5. 治理与决策
+
+| # | 概念 | 裁定 | VIZ 上如何处理 / 是否需要 |
+|---|---------|---------|--------------------------------------------------|
+| 66 | **Attention Markets** | ➖ 不需要 | 交易注意力/病毒性是独立产品;Onix 聚焦事件裁定。可为客户端层市场类别,非核心。 |
+| 67 | **Conditional Tokens** | ➖ 不需要(明确) | 白皮书 §7.2 论证 CTF 拆/合**架构上不必要**——价格一致性是数学性的,非由代币强制。CTF *唯一*有用特性(可转让头寸)原生重实现为带加密 memo 的 `pm_transfer_position`。刻意省略。 |
+| 68 | **Decision Markets** | 🟡 可能 | Onix Multi 可表达决策市场,但条件「若政策则指标」结构非原生(无条件代币)。可在客户端层构建。 |
+| 69 | **Futarchy** | 🟡 可能(客户端) | futarchy = 条件期货上的决策市场。Onix 无原生条件市场,故完整 futarchy 核心不支持。VIZ 加权委员会已治理*参数*;按市场治理为客户端/路线图构建。 |
+| 70 | **Hyperstition Markets** | ➖ 不需要(刻意) | 反身性即特性(协调而非预测)。这是*设计选择,非待修缺陷*:Onix 要求结果**可由保证金预言机外部核实**,按默认结构性排除 hyperstition 市场。可作客户端层「协调市场」产品存在,但非核心目标。见[缓解](#fanshenxing-jiazu-de-huanjie)。 |
+| 71 | **Impact Markets** | ➖ 不需要 | 追溯资助/影响力证书市场是独立领域。可为客户端层类别;非核心。 |
+| 72 | **No-Loss Prediction Markets** | 🟡 相邻 | Onix 对*下注者*非无损(输家没收本金以资助赢家)。但对 *LP* **是**「无损」(本金保障)。收益资助的无损变体是另一模型;LP 侧无损已交付。 |
+| 73 | **Opportunity Markets** | ⚪ 固有(相邻) | 懒惰池的**机会成本保护**(渐进召回、活跃市场惩罚、故障印记)直接应对资本机会成本——尽管「机会市场」作为产品类别在范围外。 |
+
+## 6. 商业与平台
+
+| # | 概念 | 裁定 | VIZ 上如何处理 / 是否需要 |
+|---|---------|---------|--------------------------------------------------|
+| 74 | **AI agents** | 🟡 路线图 | 无头客户端 + 开放 VIZ 操作使程序化代理(下注者、LP、自动预言机)易于实现。AI 驱动的流动性/裁定是自然延伸,尚未规范。 |
+| 75 | **Cross-subsidization** | ⚪ 固有 | 懒惰池以一次存款跨多市场交叉补贴流动性;MasterChef `reward_per_share` 共享费用收益。交叉补贴内建于池经济。 |
+| 76 | **Demand markets** | ➖ 不需要 | 衡量/聚合需求的市场是产品类别;非核心 Onix 原语。客户端层。 |
+| 77 | **Distribution moat** | 🟡 战略 | Onix 护城河是无风险 LP 收益 + VIZ 原生基础设施(飞轮)。分发是 go-to-market 关切,迁移后由平台无关性部分解决。 |
+| 78 | **Election markets** | 🏛 客户端 | 作为普通二元/多元市场支持;其*合法性*是司法辖区客户端事务(白名单预言机、类别过滤)。协议中立。 |
+| 79 | **Event contracts** | ⚪ 固有 | 每个 Onix 市场*就是*事件合约。其监管分类是客户端/法律问题,非协议逻辑。 |
+| 80 | **Federal preemption** | 🏛 客户端(对协议不适用) | 取决于美国事件合约是否「swaps」。VIZ DLT 是**基础设施而非运营方**——如比特币是账本。法律义务附着于客户端,非共识。 |
+| 81 | **Long-tail markets** | ✅ 已解决 | 正是 LMSR 有界损失所启用的利基——Onix 经懒惰池自动分配 + 最低流动性下限使其播种*无风险*。长尾可行性是核心卖点。 |
+| 82 | **Market structure** | ✅ 已解决(已定义) | Onix 定义清晰结构:AMM 定价、同注分彩结算、保证金预言机、DPoS 治理参数、共识级操作。相对 CLOB 平台的连贯、新颖市场结构。 |
+| 83 | **Market surveillance** | 🟡 路线图 / 🏛 客户端 | 完整链上审计轨迹(每操作共识校验)使任何人*可*监控。主动监控/执法是客户端 + 路线图关切。 |
+| 84 | **Network effects** | 🟡 战略 | 飞轮(无风险 LP → 深度 → 下注者 → 费用 → 更多 LP)是预期网络效应。共享流动性池(路线图)加强它。 |
+| 85 | **Parlays** | ➖ 不需要 | 多腿组合下注需 Onix 省略的条件/组合结构。范围外(可在独立市场上由客户端构建)。 |
+| 86 | **Platform competition** | 🟡 战略 | 以「无无常损失的被动收益」独特角度对抗 Polymarket/Kalshi。战略定位,非协议逻辑。 |
+| 87 | **Polymarket** | ⚪ 参照 | 主要基准。Onix 在每个维度都不同:CPMM/LMSR vs CLOB、零 LP 风险 vs 库存风险、原生操作 vs Polygon 合约、保证金预言机 vs UMA、无 CTF。 |
+| 88 | **Regulatory arbitrage** | 🏛 客户端 | 司法辖区客户端模型意味着每个地区在中立的 VIZ 之上构建其合规(或无许可)客户端。监管定位完全在客户端层。 |
+| 89 | **Regulatory classification** | 🏛 客户端 | 市场是 swaps/博彩/证券由各辖区在客户端层决定;协议分类中立(无许可与受监管客户端用同样的 `pm_*` 操作)。 |
+
+## 摘要 —— Onix-on-VIZ 设计究竟改变了什么
+
+**结构性解决(核心优势):** LP 侧的逆向选择、毒性流、跳空风险、无常损失、做市商库存风险 → 全部消除,因为赢家
+只由输家被没收的本金资助、LP 本金无条件返还(CPMM 用 AM-GM 证明;LMSR 用同注分彩)。**LMSR 的二元失败** →
+通过二元用 CPMM、将 LMSR 限于做市商非对手方的多元市场而规避。**流动性提供、最小可行流动性、长尾可行性** →
+无风险 LP + 懒惰池自动分配。**预言机设计、争议解决、CVM** → 保证金预言机 + 14 项声誉 + 加权委员会争议。
+**价格发现、套利、条件代币** → 价格一致性是数学性的(`Σ price = 1`),故无需订单簿、拆/合或内部套利层。
+
+**不需要 / 刻意省略:** 订单簿、CDA、买卖价差、语义最小变动价位、订单流套利、CTF 拆/合、组合/协方差/相关/相对
+价值/连串市场、UMA、对等预测、LOX。
+
+**下放到司法辖区客户端层:** 联邦优先、监管分类/套利、选举市场合法性、KYC/内幕交易执法、市场监控。
+
+**自规范以来新上线(HF14):** 可选批量拍卖 + 提交-揭示下注(二元)、可选杠杆子系统(头寸抵押)、懒惰池、
+`endogeneity_tier`、链上创建者封禁、逐下注者/杠杆结算虚拟操作 + 插件 API,以及 PM 争议与 DAO 提案中的懒惰池治
+理权重。
+
+**在 VIZ 路线图(今日部分):** 共享/类别流动性池(解决碎片化)、自动数据预言机(→ 自裁定客观市场)、分布市场、
+AI 代理。*(注:争议的提交-揭示投票**不在**此列——它被刻意拒绝;争议听证保持公开。下注的提交-揭示已上线。)*
+
+**仍开放 / 行为性(已缓解,非消除):** 冷门偏差/Yes 偏差、靠深度的价格操纵、跨平台套利。反身性家族有具体缓解
+方案——见下。
+
+## 反身性家族的缓解 {#fanshenxing-jiazu-de-huanjie}
+
+endogeneity、reflexivity、凯恩斯选美(KBC)与 hyperstition 是**同一根因在不同层**:市场/价格影响其所衡量的结
+果。一小套原语应对全部四者。
+
+### 缓解原语
+
+| 原语 | 状态 | 修复 | 备注 |
+|-----------|--------|-------|---------|
+| **提交-揭示争议投票** | **已拒绝(不会构建)** | KBC | 委员会争议是**公开听证**:`pm_dispute_vote` 是公开选票,**按设计保持公开**——DAO 信誉依赖透明裁决。投票可改至关闭;KBC 残余被接受(投票不因与多数一致而获酬)。 |
+| **提交-揭示下注(批量)** | **上线(可选,二元)** | endogeneity、reflexivity、信息不对称 | `pm_commit_bet`/`pm_reveal_bet`/`pm_batch_settle` 隐藏在途订单方向/大小,使公开价格在下注窗口内不「泄露」。按统一价**批量**结算。 |
+| **`endogeneity_tier` 字段** | **上线字段** | endogeneity | 预言机标 1 经济数据、2 体育/排定、3 政治/社会;UI 展示反身性风险;客户端可限制 tier-3。 |
+| **外生裁定(自动数据预言机)** | 路线图(高) | endogeneity、reflexivity | 裁定绑定到市场无法影响的外部数据源 → 干净温度计。 |
+| **禁止类别清单 + 创建者封禁** | **创建者封禁链上上线**;清单在客户端层 | 有害反身性、hyperstition | 封禁 YES 会制造「拥趸」的市场(暗杀/「悬赏」/恐怖、宣传市场)。经链上创建者封禁 + 客户端类别过滤。 |
+| **深度无风险 LP 流动性** | 今日核心 | 操纵驱动的反身性 | 飞轮使订单深,故为「头条」操纵抬价代价高。 |
+
+### 净裁定变化
+
+| 概念 | 之前 | 之后 |
+|---------|--------|-------|
+| Keynesian Beauty Contest | 🔴 开放 | ✅ 市场 / ⚪ 争议——投票**按设计**公开(无提交-揭示;可改选票、投票者不获酬、池内投票者有权) |
+| Endogeneity | 🔴 开放 | 🟡 已缓解——`endogeneity_tier` + 提交-揭示/批量**上线**;外生预言机路线图 |
+| Reflexivity | 🔴 开放 | 🟡 已缓解——创建者封禁**链上上线**;类别清单客户端层 |
+| Hyperstition | 🔴 开放 | ➖ 按设计排除(可选客户端产品) |
diff --git a/@l10n/zh-CN/docs/prediction-markets/index.md b/@l10n/zh-CN/docs/prediction-markets/index.md
new file mode 100644
index 0000000000..2261354e16
--- /dev/null
+++ b/@l10n/zh-CN/docs/prediction-markets/index.md
@@ -0,0 +1,49 @@
+---
+title: 预测市场(Onix)—— 总览与地图
+description: VIZ 链上预测市场技术栈——Onix 协议与 Forecaster 客户端——从白皮书到规范、对象、操作、工作流与概念分析的完整文档树。
+---
+
+# VIZ 上的预测市场
+
+VIZ Ledger 将预测市场作为**一等公民共识操作**(`pm_*`)运行,自 HF14 上线。全文会出现两个名称:
+
+- **Onix** —— **协议**:链上市场引擎(CPMM 二元 + LMSR 多元、同注分彩零和结算、保证金预言机、懒惰池、可选
+ 杠杆、批量 / 提交-揭示下注)。
+- **Forecaster** —— 对接该协议的 VIZ Ledger **瘦客户端**。它是一个无头、与平台无关的前端,让**全球用户参与链上
+ 预测市场**——创建市场、下注、提供流动性、做预言机、发起争议——通过直接对公共 VIZ 节点签署 `pm_*` 操作。协议
+ 中立;Forecaster(以及任何按其方式构建的司法辖区客户端)是接入层。
+
+## 文档地图
+
+```mermaid
+flowchart TD
+ ROOT["预测市场(Onix)"]
+ ROOT --> OV["总览 — 一页式定位(整体一览)"]
+ ROOT --> WP["白皮书 — 论点:为何 LP 无风险、两种市场类型、飞轮"]
+ ROOT --> SP["规范 — 形式化机制 + §17 链上对象模型"]
+ ROOT --> OPS["操作 — 已签名的 pm_* 共识操作"]
+ ROOT --> VOPS["虚拟操作 — 结算时/到期时发出的确定性 vop"]
+ ROOT --> API["插件 API — prediction_market_api 只读方法"]
+ ROOT --> WF["工作流与图 — 一个典型市场贯穿每个角色"]
+ ROOT --> CA["概念分析 — 90 个 PM 理论概念对照 VIZ 实现"]
+```
+
+## 从这里开始
+
+| 页面 | 是什么 |
+|------|-----------|
+| [总览](./onix) | 一页式定位:AMM 定价的同注分彩,具备结构性无风险流动性。 |
+| [白皮书](./whitepaper) | 产业论点——LP 保障、Onix Binary(CPMM)+ Onix Multi(LMSR)、预言机、懒惰池、杠杆、治理。 |
+| [规范](./specification) | 形式化规范:参数、状态机、定价、结算、争议、懒惰池、杠杆,以及 **[链上对象模型](./specification)**(每个 `pm_*_object` 及其查找索引)。 |
+| [操作](../protocol/operations/prediction-markets) | 21 个已签名共识操作(`pm_create_market`、`pm_place_bet`、…)。 |
+| [虚拟操作](../protocol/virtual-operations) | 确定性 vop(`pm_payout`、`pm_market_accepted`、`pm_leverage_resolve`、`pm_batch_settle`、…)。 |
+| [插件 API](../plugins/prediction-market-api) | `prediction_market_api` —— 对市场、下注、预言机、争议、懒惰池及中位数投票参数的只读访问。 |
+| [工作流与交互图](./workflows) | 一个典型二元市场贯穿所有参与者,附正常与争议裁定的零和主账本。 |
+| [概念分析(Onix 对照 90)](./concepts-analysis) | 链上实现如何映射到预测市场理论图谱——哪些已解决、固有、不需要或在路线图。 |
+
+## 治理
+
+所有经济参数均由代表**中位数投票**,位于 `chain_properties_pm` 结构体——见
+[链参数 → 预测市场参数](../governance/chain-properties#pm-parameters)。调整费用、罚则、懒惰池、杠杆或
+批量/提交-揭示时序无需硬分叉;三个实时终止开关(`pm_commit_reveal_enabled`、`pm_lazy_pool_enabled`、
+`pm_leverage_enabled`)让验证者中位数无需分叉即可停用整个子系统。
diff --git a/@l10n/zh-CN/docs/prediction-markets/onix.md b/@l10n/zh-CN/docs/prediction-markets/onix.md
new file mode 100644
index 0000000000..0035492b83
--- /dev/null
+++ b/@l10n/zh-CN/docs/prediction-markets/onix.md
@@ -0,0 +1,71 @@
+---
+title: Onix —— AMM 定价的同注分彩预测市场
+description: Onix 在同注分彩(parimutuel)结算之上叠加连续的 AMM 价格发现——价格像 AMM 一样波动,而流动性具备彩池的风险特征:做市商永远不会破产。
+---
+
+# Onix —— AMM 定价的同注分彩预测市场
+
+> 在 **同注分彩(parimutuel)结算** 之上叠加连续的 **AMM 价格发现**——价格像 AMM 一样波动,而流动性具备彩池的风险特征:**做市商永远不会破产。**
+
+::: info Onix 与 Forecaster
+**Onix** 是链上协议。**Forecaster** 是 VIZ Ledger 上对接它的瘦客户端——无头、与平台无关的接入层,让全球用户
+通过直接对公共 VIZ 节点签署 `pm_*` 操作来参与链上预测市场。完整文档树见[板块总览与地图](./)。
+:::
+
+## 一个核心思想
+
+Onix 将**价格与赔付解耦**:
+
+- **价格(发现)**——CPMM 曲线(二元)或 LMSR-softmax(多元)在每次下注时更新实时概率,并为每笔下注分配一个**权重**(其份额凭证)。
+- **赔付(结算)**——赢家**只**从输家被没收的本金中获得赔付,按权重分配:纯**同注分彩**,严格零和(协议从不增发代币)。
+
+Onix 所有的独特之处都源于这一解耦。
+
+## 为什么重要 —— 三点
+
+::: tip 1 · 无法被抽干的流动性
+由于赢家从输家而非从 LP 本金中获得赔付,流动性提供者**无法破产**——没有无常损失、没有库存风险、不会被狙击致死。这由*构造保证*(CPMM 用 AM–GM,LMSR 用守恒),而非靠保险。
+:::
+
+::: tip 2 · 无 IL 的被动收益 —— 懒惰池
+一次存入即自动作为隐形流动性铺展到多个市场,并为可选杠杆提供资金,采用 MasterChef 式的奖励记账。**无需**挑选市场、**不**承担无常损失即可赚取预测市场的流动性收益。
+:::
+
+::: tip 3 · 链原生、零和
+市场是一等公民的共识操作(`pm_*`),而非智能合约:抗审查、可组合、约 3 秒出块、无需预言机桥。协议从不增发代币——只做再分配。
+:::
+
+## 一笔下注如何运作
+
+1. **你下注** `X` 于某结果。`X` 进入曲线;曲线返回你的**权重**——越早下注(在价格移动之前),权重越多。
+2. **赔率板更新。** 某一方的实时系数 = `1 + 对方池 × (1 − 佣金) / 本方池`,佣金(预言机 + 创建者 + LP)已内含其中。
+3. **结算时**,输家本金(扣除佣金)按权重在赢家间分配。你的赔付 = 退回本金 **+** 你在输家池中的份额。LP 本金原封不动地返还。
+
+## 对比
+
+| | CLOB / AMM(Polymarket、Kalshi) | 纯同注分彩(彩池) | **Onix** |
+|---|---|---|---|
+| 实时价格 | 是 | 否(仅池比) | **是(CPMM / LMSR)** |
+| 下注时锁定赔率 | 是 | 否 | 否(诚实的同注分彩) |
+| LP / 做市商可能破产 | **是**(IL、狙击、跳空风险) | 不适用 | **否(结构性)** |
+| 带收益的流动性层 | 脆弱 | 无 | **懒惰池,无 IL** |
+| 运行于 | 合约 / 后端 | 后端 | **共识(`pm_*`)** |
+| 代币增发 | 有时 | 否 | **否(零和)** |
+
+## 诚实的取舍
+
+::: warning 赔率是同注分彩——会漂移到收盘
+Onix **不**在下注时锁定你的系数。赔率板随资金流动而移动,最终系数只有在收盘时才确定——正如彩池。这不是需要打补丁的缺陷:锁定赔率的*唯一*方式是让对手方承担风险(庄家,或可能亏损的 AMM LP)。Onix 的漂移正是其 LP 保障的直接代价——风险存在于**下注者之间**,所以任何人的流动性都不会被烧毁。
+:::
+
+## 新颖之处
+
+- **AMM 加权 + 同注分彩结算** 集成于同一引擎——连续价格发现,*不带*做市商库存风险。
+- **结构性、可证明的 LP 安全**,而非依赖保险或补贴的流动性。
+- **共担的、带收益的流动性层**(懒惰池),同时为可选杠杆提供资金——清算按下注前的储备执行,因此池始终被补足。
+- **可选的抗 MEV**(批量 / 提交-揭示下注)与**透明治理**(保证金预言机、可修改投票的公开听证争议)——全部叠加在安全基座之上,从不触碰 LP 保障。
+
+## 了解更多
+
+- 协议操作 —— [预测市场](../protocol/operations/prediction-markets)
+- 插件 API —— [预测市场 API](../plugins/prediction-market-api)
diff --git a/@l10n/zh-CN/docs/prediction-markets/specification.md b/@l10n/zh-CN/docs/prediction-markets/specification.md
new file mode 100644
index 0000000000..2e0c9642af
--- /dev/null
+++ b/@l10n/zh-CN/docs/prediction-markets/specification.md
@@ -0,0 +1,974 @@
+---
+title: Onix 协议 —— 规范
+description: Onix 协议的正式技术规范,已实现为 VIZ DLT 上的共识操作(HF14)。
+---
+
+# Onix 协议规范
+
+**版本:** 2.0(链上 / HF14)
+**状态:** 正式技术规范 —— 现已实现为 VIZ DLT 上的共识操作
+
+---
+
+> **链上(HF14)。** 实现为 VIZ DLT 上的一等公民共识操作(`pm_*`),并在 `consensus_sim` 中验证。**所有
+> 百分比均为基点(bp):10000 = 100.00%**;所有时长为以**秒 / 区块**计的治理参数。中位数投票的参数位于
+> `chain_properties_pm` 结构体(§3);按市场字段为 `pm_create_market` 操作;所有状态位于 §17 的 chainbase
+> 对象中。预言机费用采用 offer→quote(创建者上限 → 预言机在接受时冻结其报价,发出 `pm_market_accepted`)。
+> 争议有两种模式 —— 委员会(`dispute_mode = 0`,默认:按质押加权的**公开** `pm_dispute_vote`,关闭前可改,
+> 懒惰池质押计入)与账户(`dispute_mode = 1`:指定 `dispute_resolver`)。
+
+## 目录
+
+1. 定义与角色
+2. 货币与精度
+3. 系统参数
+4. 市场状态机
+5. Onix Binary:恒定乘积做市商
+6. Onix Multi:LMSR 与同注分彩结算
+7. 费用结构
+8. 迟下注的时间惩罚
+9. 流动性提供
+10. 裁定与赔付
+11. 撤注
+12. 争议系统
+13. 预言机错过裁定的惩罚
+14. 预言机声誉评分
+15. 持仓转让
+16. 懒惰流动性池
+16a. 可选杠杆
+16b. 批量 / 提交-揭示下注
+17. 链上对象模型
+
+---
+
+## 1. 定义与角色
+
+| 角色 | 定义 |
+|------|-----------|
+| **市场创建者** | 支付 `pm_market_creation_fee`(`pm_create_market`);设定问题、结果、流动性、费用上限与时序参数 |
+| **预言机** | 注册(费用:`pm_oracle_registration_fee`),存入保险(最低:`pm_min_oracle_insurance`),在接受时以**基点**报出费用条款(≤ 创建者上限)+ 固定费,接受/拒绝市场,提供结果裁定 |
+| **下注者** | 对结果下注;按本金与当前储备成比例获得代币 |
+| **流动性提供者(LP)** | 向市场池提供资金;赚取流动性费的时间加权份额 + 惩罚池 |
+| **懒惰池提供者** | 以锁仓期向懒惰流动性池存入 VIZ;池自动向市场分配并通过 `reward_per_share` 累加器分发奖励 |
+| **争议裁决者** | 仅账户模式(`dispute_mode = 1`):按市场的 `dispute_resolver` 账户仲裁。委员会模式(`dispute_mode = 0`)不使用裁决者——由 SHARES 选民投票 |
+| **DAO / 委员会基金** | 链上既有的委员会基金。接收 `pm_market_creation_fee` 与额外的预言机罚款 |
+
+---
+
+## 2. 货币与精度
+
+所有金额以整数存储,精度 = 1/1000(毫 VIZ)。`1000` 内部单位 = 1.000 VIZ。
+
+时间惩罚值使用精度 = 1/1,000,000(微单位)。
+
+---
+
+## 3. 系统参数
+
+### 中位数投票参数(`chain_properties_pm`)
+
+所有经济参数由代表中位数投票(无需硬分叉即可调参),位于链上 `chain_properties_pm` 结构体。每个代表通过标准
+的 **`versioned_chain_properties_update_operation`**(op ID 46)发布其偏好值 —— `chain_properties_pm`
+是该版本化结构体的当前版本(v5,HF14)—— 网络对活跃代表应用**逐字段中位数**。两个风险覆盖率旋钮
+(`pm_listing_min_coverage_percent`、`pm_betting_min_coverage_percent`)属于同一个 v5 结构体,调参方式完全相同。
+**所有百分比为基点(bp,10000 = 100.00%);时长以秒或区块计** —— 但这两个覆盖率旋钮例外,它们为成交量百分比
+(100 = 1.0×)。确切默认值与范围见
+[链参数](../governance/chain-properties#pm-parameters);权威来源是结构体本身。
+
+| 分组 | 参数 |
+|---|---|
+| 注册与下限 | `pm_oracle_registration_fee`、`pm_min_oracle_insurance`、`pm_market_creation_fee`、`pm_min_liquidity`、`pm_max_outcomes`、`pm_max_market_duration` |
+| 费用与罚则(bp) | `pm_max_oracle_fee_percent`、`pm_oracle_penalty_percent`、`pm_no_contest_penalty_percent`、`pm_default_time_penalty_percent`、`pm_max_time_penalty` |
+| 接受窗口 | `pm_oracle_accept_window_sec`(默认 3600 = 1 小时;未在此期限内被接受/拒绝的待定市场由 cron 作废——种子退还,创建费保留) |
+| 风险 / 覆盖率(成交量 %) | `pm_listing_min_coverage_percent`(250 = 2.5×;覆盖率低于此值的市场从默认目录中隐藏,经 `show_risky` 展示)、`pm_betting_min_coverage_percent`(150 = 1.5×;建议性客户端风险确认阈值,`≤` 挂牌阈值,不在链上强制) |
+| 争议 | `pm_dispute_fee`、`pm_dispute_grace_sec`、`pm_oracle_dispute_response_sec`、`pm_dispute_vote_period_sec`、`pm_dispute_auto_close_sec`、`pm_dispute_approve_min_percent`(bp)、`pm_dispute_reward_multiplier`(bp) |
+| 懒惰池 | `pm_lazy_pool_enabled`、`pm_lazy_alloc_percent`、`pm_lazy_max_total_alloc_percent`、`pm_lazy_recall_step_percent`、`pm_lazy_lock_sec`、`pm_lazy_emergency_penalty_percent`、`pm_lazy_min_liquidity_fee_percent`(默认 200 = 2%;池跳过 `liquidity_fee_percent` 低于此奖励下限的市场) |
+| 杠杆 | `pm_leverage_enabled`、`pm_leverage_fund_percent`、`pm_leverage_max_per_position_bp`、`pm_leverage_max_position_ratio_percent`、`pm_leverage_min_market_liquidity`、`pm_leverage_safety_margin_percent`、`pm_leverage_max_slippage_percent`、`pm_leverage_m_factor_percent`、`pm_leverage_pool_profit_percent`、`pm_leverage_expiration_buffer_sec`、`pm_conversion_profit_cost_percent` |
+| 批量 / 提交-揭示 | `pm_commit_reveal_enabled`、`pm_batch_epoch_blocks`、`pm_reveal_window_blocks`、`pm_commit_no_reveal_penalty_percent`(bp)、`pm_min_batch_bet` |
+| 处理 | `pm_processing_cap_per_block` |
+
+`pm_market_creation_fee` 与额外预言机罚款的接收方是链上既有的委员会/DAO 基金,而非 PM 专用账户。
+
+### 按市场参数(`pm_create_market` 操作)
+
+由创建者在创建时设定;预言机费用字段为预言机在接受时据以报价的**上限**(offer→quote)。完整字段参考:
+[预测市场操作](../protocol/operations/prediction-markets)。
+
+| 字段 | 描述 |
+|---|---|
+| `oracle`、`market_type`(0 二元 / 1 多元)、`outcomes`、`url` | 市场定义 |
+| `oracle_fee_percent`、`oracle_fixed_fee` | 预言机费用**上限**(bp + 固定);预言机在接受时冻结其报价 ≤ 此(且 ≤ 中位 `pm_max_oracle_fee_percent`) |
+| `creator_fee_percent`、`liquidity_fee_percent` | 创建者与 LP 费用(输家池的 bp) |
+| `liquidity`、`lmsr_b` | 种子流动性;`lmsr_b` 用于多元市场 |
+| `betting_expiration`、`result_expiration` | 计时器 |
+| `time_penalty_type`、`time_penalty_value`、`penalty_curve_type` | 迟下注惩罚形状 |
+| `allow_early_resolution`、`allow_cancellation` | 开关 |
+| `allow_batch`、`allow_instant_bet` | 下注模式(二元) |
+| `endogeneity_tier` | 1 经济数据 / 2 体育 / 3 政治(展示/风险提示) |
+| `dispute_mode`(0 委员会 / 1 账户)、`dispute_resolver` | 争议路由 |
+| `dispute_penalty_percent` | 争议成立时的预言机罚则策略(bp,带符号) |
+| `metadata` | 自由格式客户端 JSON(共识不透明;链下解析) |
+
+---
+
+## 4. 市场状态机
+
+### 状态
+
+| Status | 名称 | 描述 |
+|--------|------|-------------|
+| -1 | Deleted | 预言机拒绝,**或**接受窗口(`pm_oracle_accept_window_sec`)到期;种子流动性返还创建者(创建费保留) |
+| 0 | Waiting | 等待预言机审阅 |
+| 1 | Active | 接受下注至 `betting_expiration` |
+| 2 | Closed | 下注结束,等待预言机裁定 |
+| 3 | Resolved | 结果确定,赔付已计算 |
+
+### 赔付状态
+
+| payout_status | 名称 | 描述 |
+|---------------|------|-------------|
+| 0 | Not calculated | 裁定前 |
+| 1 | Calculated | 赔付待处理(宽限期活跃) |
+| 2 | Paid | 全部赔付已处理 |
+| 3 | Disputed | 已提交争议,赔付冻结 |
+
+### 转移
+
+```mermaid
+stateDiagram-v2
+ direction LR
+ state "Waiting (0)" as Waiting
+ state "Active (1)" as Active
+ state "Closed (2)" as Closed
+ state "Resolved (3)" as Resolved
+ state "Deleted (-1)" as Deleted
+ state "Paid out" as Paid
+ [*] --> Waiting
+ Waiting --> Active: 预言机接受
+ Waiting --> Deleted: 预言机拒绝
+ Waiting --> Deleted: 接受窗口到期 (pm_market_expired)
+ Active --> Closed: betting_expiration
+ Active --> Resolved: 提前裁定(若允许)
+ Closed --> Resolved: 预言机裁定
+ Resolved --> Paid: 宽限窗口(12 小时),无争议
+ Deleted --> [*]
+ Paid --> [*]
+```
+
+**前置条件:**
+
+| 转移 | 前置条件 |
+|-----------|---------------|
+| 0 → 1 | 预言机保险 ≥ `min_oracle_insurance`;预言机接受 |
+| 0 → 1(自预言机) | 创建者 = 预言机;保险检查;创建时自动批准 |
+| 0 → -1 | 预言机拒绝;种子流动性返还创建者 |
+| 0 → -1(到期) | `now ≥ created_time + pm_oracle_accept_window_sec` 且预言机无动作;cron 作废市场、退还种子(创建费保留)、发出 `pm_market_expired` |
+| 1 → 3 | 预言机提交带结果的裁定(0、1,或 -1 表示 no-contest);`allow_early_resolution=1` 或 `time ≥ betting_expiration` |
+| 2 → 3 | 预言机提交裁定;`time ≤ result_expiration` |
+| 3 → paid | 宽限期内无争议;定时任务处理赔付 |
+
+### 市场创建流程
+
+1. 从创建者扣除 `market_creation_fee` → DAO 基金(不可退)
+2. 在市场上记录来自预言机资料的 `oracle_fixed_fee`
+3. 从创建者余额锁定 `liquidity`
+4. 初始化储备:`reserve_a = floor(liquidity/2)`,`reserve_b = liquidity − reserve_a`
+5. 计算 `k = reserve_a × reserve_b`
+6. 若自预言机:经保险检查自动批准为 status=1
+7. 若外部预言机:进入 status=0 并设置 `accept_deadline = created_time + pm_oracle_accept_window_sec`
+
+### 预言机接受流程
+
+待定市场须由其预言机在接受窗口(`pm_oracle_accept_window_sec`,默认 1 小时)内处置。三种结局:
+
+- **接受**(status 0 → 1):(1) 从创建者向预言机转移 `oracle_fixed_fee`(自预言机则跳过);
+ (2) 递增 `markets_accepted`;(3) 更新 `last_active_time`;(4) 触发懒惰池自动分配(若池有自由余额
+ **且**市场 `liquidity_fee_percent ≥ pm_lazy_min_liquidity_fee_percent`)。
+- **拒绝**(status 0 → -1):种子流动性返还创建者;无 vop。
+- **到期**(status 0 → -1):若到 `accept_deadline` 仍无动作,逐块 cron 作废市场、退还种子流动性
+ (**不**含创建费)、发出 `pm_market_expired`。
+
+### 审计轨迹
+
+每个改变状态的动作都是一个共识操作或虚拟操作,永久记录在区块日志中并可通过 `account_history` 查询。下注、
+撤注、流动性添加/提取、接受/拒绝、裁定、争议、争议裁决、赔付与罚款都作为 `pm_*` 操作/虚拟操作出现,并附带它
+们所触及的市场储备。
+
+---
+
+## 5. Onix Binary:恒定乘积做市商
+
+### 不变量
+
+```
+k = reserve_a × reserve_b
+```
+
+`k` 仅在流动性添加/提取操作时改变。
+
+### 下注(A 侧)
+
+```
+new_reserve_b = reserve_b + amount
+new_reserve_a = floor(k / new_reserve_b)
+tokens_received = reserve_a − new_reserve_a
+price = amount × 1,000,000 / tokens_received
+```
+
+B 侧对称(交换 a/b)。
+
+### 滑点保护
+
+`place-bet` 上的可选 `min_tokens` 参数。若 `tokens_received < min_tokens`,交易被拒。
+
+### 市场初始化
+
+```
+reserve_a = floor(liquidity / 2)
+reserve_b = liquidity − reserve_a
+k = reserve_a × reserve_b
+```
+
+最低初始流动性:100,000 mVIZ(100 VIZ)。
+
+### 权重(代币)语义
+
+- `weight` = 下注者获得的结果代币数量(由 CPMM 在下注时设定)
+- `weight` 是**相对索取权**,而非以 VIZ 计价的赔付。结算为**同注分彩**(与 Onix Multi 相同):赢家取回本金
+ 外加按权重的输家池比例份额。
+- 若下注 A 侧且结果 A 获胜:`payout = bet_amount + (weight / total_winning_weight) × winners_pool − time_penalty_on_profit`
+- 若结果 A 落败:payout = 0(本金没入赢家池)
+
+CPMM 是**定价引擎**(概率 + 权重分配);它不再门控赔付。这使两种市场类型共享一个结算模型:*AMM 分配权重
+(二元用 CPMM,多元用 LMSR);输家按权重比例资助赢家。*
+
+### 价格显示
+
+```
+implied_probability_A = reserve_b / (reserve_a + reserve_b) × 100%
+implied_probability_B = reserve_a / (reserve_a + reserve_b) × 100%
+```
+
+### LP 本金保障(证明)
+
+在同注分彩结算下,保障精确且不依赖曲线几何:
+
+```
+Money OUT = L (LP principal) + Σ(winning bet_amount) + winners_pool + fees
+ = L + winning_bets + (losers_sum − fees) + fees
+ = L + winning_bets + losing_bets = L + all_bets = Money IN
+```
+
+无论权重如何,总赔付以 `losers_sum` 为上限,故 LP 本金 `L` 无条件返还,赢家完全由输家资助。(旧的 AM-GM 界
+`reserve_a + reserve_b ≥ L` 不再用于偿付能力;它仍是定价曲线的属性。)
+
+---
+
+## 6. Onix Multi:LMSR 与同注分彩结算
+
+### 价格函数(softmax)
+
+对 N 个结果,数量参数 q_1, ..., q_N,流动性参数 b:
+
+```
+price(i) = exp(q_i / b) / Σ_j exp(q_j / b)
+```
+
+**不变量:** `Σ_i price(i) = 1`(按 softmax 定义)。
+
+### 成本函数
+
+```
+C(q) = b × ln(Σ_j exp(q_j / b))
+```
+
+在结果 i 上买入 Δ 代币的成本:
+
+```
+cost = C(q + Δ·e_i) − C(q)
+ = b × [ln(Σ_j exp(q'_j / b)) − ln(Σ_j exp(q_j / b))]
+where q'_i = q_i + Δ, all other q'_j = q_j
+```
+
+数值稳定性(log-sum-exp 技巧):
+
+```
+ln(Σ exp(x_j)) = max(x) + ln(Σ exp(x_j − max(x)))
+```
+
+### 流动性参数
+
+```
+b = S / ln(N)
+```
+
+其中 S = LP 补贴存款,N = 结果数。
+
+### 结算(裁定时)
+
+```
+1. Oracle declares winning outcome
+2. losers_sum = Σ bet_amount for all non-winning bets
+3. oracle_fee = floor(losers_sum × oracle_fee_percent / 10000)
+4. creator_fee = floor(losers_sum × creator_fee_percent / 10000)
+5. liq_fee = floor(losers_sum × liquidity_fee_percent / 10000)
+6. winners_pool = losers_sum − oracle_fee − creator_fee − liq_fee
+7. For each winning bettor:
+ payout = bet_amount + (their_tokens / total_winning_tokens × winners_pool) − time_penalty
+8. LP subsidy returned unconditionally
+9. LP earns time-weighted share of liq_fee
+```
+
+### LP 本金保障(证明)
+
+1. LP 存入 S VIZ 作补贴。这设定 b = S / ln(N)。
+2. 下注期间,用户支付 VIZ → 取得代币。VIZ 累积为下注池。
+3. 裁定时:输家没收 100% → `losers_sum`。赢家由 `losers_sum` 支付(非补贴)。
+4. LP 补贴 S **无条件**返还 —— 它在架构上与赔付流分离。
+
+### 边界情形
+
+| 情形 | 结果 |
+|----------|---------|
+| 全部下注于获胜结果 | `losers_sum=0`、`winners_pool=0`。每个下注者取回 `bet_amount`。LP 补贴返还。 |
+| 无人下注于获胜结果 | `losers_sum=total_bets`。未分配的 `winners_pool` → LP 奖励。 |
+| 零成交量市场 | LP 补贴返还。无手续费、无赔付。 |
+| 单一下注者获胜 | 下注者获得 `bet_amount + winners_pool`。LP 补贴返还。 |
+
+### 操作
+
+| 操作 | 描述 |
+|-----------|-------------|
+| `pm_create_market_multi { oracle, outcomes, liquidity, fees, ... }` | 创建 N 结果市场 |
+| `pm_place_bet_multi { market, outcome_index, amount, min_tokens }` | 为某结果买入代币 |
+| `pm_cancel_bet_multi { bet_id, min_return }` | 通过反向 LMSR 卖回代币 |
+| `pm_add_liquidity_multi { market, amount }` | 增加 LP 补贴(提升 b) |
+| `pm_withdraw_liquidity_multi { liquidity_id }` | 提取 LP 补贴(强制最低下限) |
+| `pm_resolve_multi { market, winning_outcome }` | 预言机宣布赢家,触发结算 |
+
+二元市场(N=2)使用 Onix Binary(CPMM)。LMSR 仅用于 N > 2。
+
+---
+
+## 7. 费用结构
+
+### 裁定时费用计算
+
+所有百分比费用在裁定时从**输家侧总成交量**计算:
+
+```
+losers_sum = Σ bet_amount for all losing bets
+
+oracle_fee = floor(losers_sum × oracle_fee_percent / 10000)
+creator_fee = floor(losers_sum × creator_fee_percent / 10000)
+liquidity_fee = floor(losers_sum × liquidity_fee_percent / 10000)
+winners_pool = losers_sum − oracle_fee − creator_fee − liquidity_fee
+```
+
+下注时不从下注扣费。完整下注额进入 CPMM/LMSR 储备。
+
+### 预言机固定费
+
+每个市场一次性费用。由预言机在资料中设定。市场接受时由创建者付给预言机。自预言机市场完全跳过(不发生余额操
+作)。
+
+### 费用追踪字段
+
+- `oracle_fee_earned` —— 裁定时不使用;费用从 losers_sum 计算
+- `liquidity_fee_earned` —— 已付给早退 LP 的累计 LP 费;裁定时:`LP fee pool = max(0, floor(losers_sum × liquidity_fee_percent / 10000) − liquidity_fee_earned) + penalty_pool`
+- 每笔下注的 `oracle_fee` 与 `liquidity_fee` 记录用于审计;不在市场上累加
+
+### 取整
+
+所有计算使用 `floor()`。未分配的尘额(< 1 mVIZ)在最终赔付时送入 DAO 基金。
+
+---
+
+## 8. 迟下注的时间惩罚
+
+### 惩罚窗口
+
+| 类型 | 窗口计算 |
+|------|-------------------|
+| Fixed(type=0) | 到期前 `penalty_window = time_penalty_value` 秒 |
+| Percentage(type=1) | `penalty_window = time_penalty_value / 100 × (betting_expiration − market_creation_time)` |
+
+### 惩罚计算
+
+```
+time_to_expiration = betting_expiration − current_time
+
+if time_to_expiration < penalty_window:
+ ratio = 1 − (time_to_expiration / penalty_window)
+
+ if penalty_curve_type == 1: // quadratic
+ penalty_ratio = ratio × ratio
+ else: // linear
+ penalty_ratio = ratio
+
+ time_penalty = floor(penalty_ratio × max_time_penalty)
+else:
+ time_penalty = 0
+```
+
+### 赔付时应用(仅作用于利润)
+
+```
+profit = floor(winners_pool × weight / total_winning_weight) // parimutuel share of losers' pool
+penalty_deduction = floor(profit × time_penalty / 1,000,000)
+net_payout = bet_amount + profit − penalty_deduction
+```
+
+**不变量:** `net_payout ≥ bet_amount` —— 惩罚仅作用于利润份额,故赢家始终至少取回本金。(Onix Binary 与
+Onix Multi 相同。)
+
+---
+
+## 9. 流动性提供
+
+### 添加流动性
+
+```
+add_a = amount × reserve_a / (reserve_a + reserve_b)
+add_b = amount − add_a
+new_reserve_a = reserve_a + add_a
+new_reserve_b = reserve_b + add_b
+new_k = new_reserve_a × new_reserve_b
+```
+
+存款时记录 `sec_to_expiration = betting_expiration − current_time`。
+
+### 时间加权费用分配(裁定时)
+
+```
+fee_pool = remaining_liquidity_fee + total_penalty_pool
+
+weight_i = amount_i × max(1, sec_to_expiration_i)
+total_weight = Σ weight_i
+fee_share_i = floor(fee_pool × weight_i / total_weight)
+lp_payout_i = principal_i + fee_share_i
+```
+
+每笔存款为独立持仓。同一用户的多笔存款分别追踪。
+
+### 提前提取
+
+**前置条件:** market status=1,`time < betting_expiration`,`resulting liquidity_sum ≥ 100,000 mVIZ`。
+
+```
+// Fractional withdrawal
+fraction = withdraw_amount / lp_amount
+withdraw_weight_a = floor(weight_a × fraction)
+withdraw_weight_b = floor(weight_b × fraction)
+
+// Reverse reserves
+new_reserve_a = reserve_a − withdraw_weight_a
+new_reserve_b = reserve_b − withdraw_weight_b
+new_k = new_reserve_a × new_reserve_b
+
+// Time-ratio discount
+time_served = current_time − lp_deposit_time
+market_duration = betting_expiration − market_creation_time
+time_ratio = min(1, time_served / market_duration)
+
+// Fee share (conservative: min of both sides)
+fee_from_a = floor(a_bets_sum × liquidity_fee_percent / 10000)
+fee_from_b = floor(b_bets_sum × liquidity_fee_percent / 10000)
+estimated_pool = min(fee_from_a, fee_from_b) − already_paid_to_early_lps
+lp_tw = withdraw_amount × max(1, sec_to_expiration)
+total_tw = Σ (active LP time-weights)
+raw_fee_share = floor(estimated_pool × lp_tw / total_tw)
+fee_share = floor(raw_fee_share × time_ratio)
+
+returned = withdraw_amount + fee_share
+```
+
+**到期后锁定:** 当 `time ≥ betting_expiration` 时禁止 LP 提取。所有 LP 持仓锁定至裁定。
+
+### 提前提取的本金安全
+
+提取减去原始 `weight_a` 与 `weight_b`(而非当前储备的比例份额)。若 `reserve_a < weight_a` 或
+`reserve_b < weight_b`,提取被**阻止**。
+
+### 创建者作为首位 LP
+
+市场创建者自动成为首位 LP。其 `sec_to_expiration` 等于完整市场时长,给予最大时间权重。
+
+---
+
+## 10. 裁定与赔付
+
+### 赔付优先顺序
+
+| 优先级 | 类型 | 接收方 | 金额 |
+|----------|------|-----------|--------|
+| 1 | Oracle fee (2) | 预言机 | `floor(losers_sum × oracle_fee_percent / 10000)` |
+| 1.5 | Creator fee (7) | 创建者 | `floor(losers_sum × creator_fee_percent / 10000)` |
+| 2 | Creator LP (1) | 创建者 | `principal + time-weighted fee share` |
+| 3 | LP return (1) | LP | `principal + time-weighted fee share` |
+| 4 | Winner bets (0) | 赢家 | `bet_amount + floor(winners_pool × weight / total_winning_weight) − penalty_deduction`(同注分彩) |
+| 5 | Dispute refund (5) | 争议参与者 | (若适用) |
+| 6 | Oracle penalty bonus (6) | 全部参与者 | (若预言机被罚) |
+
+### 输家侧
+
+赔付 = 0。本金并入储备池。
+
+### 零成交量市场
+
+LP 取回全部本金。预言机取得固定费(若有)。所有费用累加器保持为 0。
+
+---
+
+## 11. 撤注
+
+### 前置条件
+
+| 条件 | 检查 |
+|-----------|-------|
+| 下注活跃 | `bet.status == 0` |
+| 用户拥有该下注 | `bet.user == current_user.id` |
+| 市场活跃 | `market.status == 1` |
+| 下注开放 | `current_time < market.betting_expiration` |
+| 允许撤注 | `market.allow_cancellation == 1` |
+
+### 反向 CPMM 机制
+
+对 A 侧下注(side=0):
+
+```
+new_reserve_a = reserve_a + tokens
+new_reserve_b = floor(k / new_reserve_a)
+amount_returned = reserve_b − new_reserve_b
+if amount_returned <= 0: amount_returned = 0
+```
+
+B 侧对称。
+
+### 滑点保护
+
+可选 `min_return` 参数。若 `amount_returned < min_return`,交易被拒。
+
+### 状态变更(原子)
+
+1. 下注状态 → 1(已撤),记录 `returned_amount`
+2. 更新市场储备
+3. 市场下注合计按原下注额递减
+4. 用户余额增加 `amount_returned`,`bets_balance` 减少
+5. 历史记录(type=4)
+6. 带前/后储备的 market log 记录
+
+---
+
+## 12. 争议系统
+
+### 提交前置条件
+
+- 提交者在该市场下过注
+- 在裁定后 `pm_dispute_grace_sec` 之内
+- 按模式路由:**委员会**(`dispute_mode = 0`)—— 无需裁决者账户,SHARES 选民通过 `pm_dispute_vote` 投票(公开、可改至 `voting_end_time`、权重 = `effective_vesting_shares` + 懒惰池质押→shares),由 `pm_dispute_finalize` 定时任务计票;**账户**(`dispute_mode = 1`)—— 市场指定的 `dispute_resolver` 作出 `pm_dispute_resolve`
+- 市场无未结争议
+- 提交者支付 `pm_dispute_fee`
+
+### 预言机响应
+
+须在 `pm_oracle_dispute_response_sec` 内。错过则从保险自动罚没 `pm_dispute_fee`,并记录在预言机对象上。
+
+预言机以 **`pm_dispute_oracle_respond`**(op ID 98)提交其反驳。由于争议是一场公开听证,该文本存储**在争议对象上**
+(`oracle_response` + `oracle_response_time`,可经 `get_dispute` 读取),以便每位委员会投票者或账户裁决者在裁决
+前予以权衡。仅市场的预言机可响应,且仅在争议未结且 `now ≤ oracle_response_deadline` 时;再次提交将覆盖先前的
+响应。
+
+### 争议生命周期
+
+```
+Resolution (T=0) → Grace period (T to T+12h) → Dispute filed (T≤12h)
+ → Oracle response (12h window) → Resolver decision (up to 14 days)
+ → After verdict: recalculate or unfreeze → Auto-payout after new grace period
+ → Auto-close fallback (T+14 days): full refund + oracle penalty
+```
+
+### 争议成立(预言机有误 —— 翻转)
+
+对提出者的奖励是罚没额中切出的一份;**罚没的其余部分资助获胜下注者**(经 `forfeit_pool`),而非裁决者、也非
+DAO。**委员会投票者与账户模式裁决者均不获报酬。**
+
+```
+reward_target = floor(dispute_fee × pm_dispute_reward_multiplier / 10000) // bp; 30000 = ×3
+bonus = max(0, reward_target − dispute_fee), 以罚没额为上限
+
+1. Disputer ← dispute_fee(托管退回)+ bonus // bonus 取自被罚没的保险
+2. forfeit_pool += (slash − bonus) // → 赢家,结算时经 winners_pool
+```
+
+罚没大小:委员会模式将预言机的 `dispute_penalty_percent` 按 `consensus_strength` 缩放;账户模式取裁决者的
+`penalty_amount`(均以剩余保险为上限)。
+
+### 争议被驳回(预言机正确 —— 维持)
+
+```
+Disputer 将全部 dispute_fee → 预言机(100%,补偿)。 // 无 50/50 裁决者/DAO 拆分
+```
+
+### 重算流程(预言机有误)
+
+1. 校验罚款(以剩余保险为上限)
+2. 支付提出争议者奖励(fee + bonus);罚没余额 → `forfeit_pool`(赢家)
+3. 罚没预言机保险
+5. 应用封禁(若请求)
+6. 删除所有现存未支付的 payouts
+7. 翻转获胜结果(A↔B)
+8. 以修正后的结果从头重生成赔付
+9. 记录审计轨迹
+
+### 裁决者权力 —— 封禁是合规/监管功能(仅账户模式)
+
+以下制裁是**账户模式** `pm_dispute_resolve`(op ID 80)的字段,由市场指定的 `dispute_resolver` 发出。当该裁决者
+是**监管方或持牌仲裁人**时,这正是其执行链下规则的方式:在单次裁决中即可罚没保险,**并将预言机与市场创建者双方**
+从平台上封禁,可临时或永久。**委员会/DAO 模式(`dispute_mode = 0`)在设计上无封禁权**——公开听证只罚没保险
+(按共识强度缩放)并经 `pm_dispute_finalize` 调整声誉;它从不封禁。
+
+| 参数 | 类型 | 描述 |
+|-----------|------|-------------|
+| `penalty_amount` | mVIZ | 额外保险罚没(0 至剩余)→ DAO 基金 |
+| `ban_oracle` | 0/1 | 封禁预言机 |
+| `ban_oracle_until` | unix ts / 0 | 0=永久,>0=到期 |
+| `ban_creator` | 0/1 | 封禁创建者 |
+| `ban_creator_until` | unix ts / 0 | 0=永久,>0=到期 |
+
+封禁会将发出封禁的 `resolver` 记录到目标的 `banned_by`。**解除封禁:** 同一裁决者可以 **`pm_unban`**(op ID 99,
+`unban_oracle` / `unban_creator`)**提前**解除;否则封禁在 `banned_until` 到期时自然失效,此时逐块 cron 将其清除
+并发出 **`pm_ban_expired`** 虚拟操作(ID 100),以便历史/索引器观察到该解除。
+
+### 自动关闭(14 天回退)
+
+| 动作 | 描述 |
+|--------|-------------|
+| 原告 | 退回争议费 |
+| 预言机 | 从保险罚没 `dispute_fee` |
+| 下注 | 全部退款(原始金额) |
+| LP | 全部退款(仅本金) |
+| 罚款分配 | 罚没额按比例分配给所有参与者 |
+| 争议状态 | 置为 3(自动关闭) |
+
+### No-Contest 声明
+
+预言机以 `market_id` 与 `reason` 调用 `oracle-no-contest`。
+
+1. 所有下注 → 待退款赔付(全额原始金额)
+2. 所有 LP 持仓 → 待退款赔付(仅本金)
+3. 罚款:从保险扣 `dispute_fee` 的 `oracle_no_contest_penalty_percent`%
+4. 罚款按比例分配给参与者
+5. 市场:`resolved_outcome = -1`,`payout_status = 1`
+6. 宽限期开始(可争议)
+
+### 3 结果裁定(No-Contest 争议)
+
+裁决者从以下选其一:
+- `correct_outcome = 0` —— A 胜(重算赔付)
+- `correct_outcome = 1` —— B 胜(重算赔付)
+- `correct_outcome = -1` —— 确认 no-contest(保留退款赔付)
+
+若预言机有误:待退款赔付被删除,替换为正确的赢家赔付。适用标准争议罚则。
+
+---
+
+## 13. 预言机错过裁定的惩罚
+
+若预言机未在 `result_expiration` 前裁定:
+
+```
+penalty_amount = floor(oracle_insurance × oracle_penalty_percent / 100)
+```
+
+### 分配
+
+```
+stakes[user_id] += bet_amount (for each active bet)
+stakes[user_id] += liquidity_amount (for each active LP position)
+total_stakes = Σ stakes[user_id]
+
+bonus_i = floor(penalty_amount × stakes[user_id] / total_stakes)
+```
+
+每个参与者获得:全额退款(本金)+ 比例奖励。
+
+市场终结:status=3,payout_status=2。
+
+---
+
+## 14. 预言机声誉评分
+
+### 原始指标(每个预言机 14 个计数器)
+
+| 指标 | 类型 | 来源 |
+|--------|------|--------|
+| `markets_accepted` | counter | oracle-accept-market |
+| `markets_resolved` | counter | resolve-market |
+| `markets_no_contest` | counter | oracle-no-contest |
+| `markets_missed` | counter | cron(错过截止) |
+| `disputes_received` | counter | create-dispute |
+| `disputes_lost` | counter | resolve-dispute (status=1) |
+| `disputes_won` | counter | resolve-dispute (status=2) |
+| `disputes_auto_closed` | counter | cron(14 天自动关闭) |
+| `dispute_responses_missed` | counter | cron(12h 响应截止) |
+| `total_volume_resolved` | mVIZ | resolve-market(bets_sum 之和) |
+| `total_insurance_slashed` | mVIZ | 所有罚款事件 |
+| `avg_resolution_time` | seconds | resolve-market |
+| `bans_received` | counter | resolve-dispute |
+| `active_since` | timestamp | register-oracle |
+| `last_active_time` | timestamp | accept/resolve/no-contest |
+
+### 派生比率
+
+分母:`total_outcomes = markets_resolved + markets_no_contest + markets_missed`
+
+| 比率 | 公式 |
+|------|---------|
+| `resolution_rate` | `markets_resolved / total_outcomes` |
+| `dispute_loss_rate` | `disputes_lost / disputes_received` |
+| `no_contest_rate` | `markets_no_contest / total_outcomes` |
+| `deadline_miss_rate` | `markets_missed / total_outcomes` |
+| `dispute_response_rate` | `1 − (dispute_responses_missed / disputes_received)` |
+
+### 可靠性分数(0–100)
+
+```
+reliability_score = clamp(0, 100,
+ BASE_SCORE
+ − W_DISPUTE_LOSS × dispute_loss_rate × 100
+ − W_NO_CONTEST × excess_no_contest × 100
+ − W_DEADLINE_MISS × deadline_miss_rate × 100
+ − W_NO_RESPONSE × (1 − dispute_response_rate) × 100
+ + W_VOLUME_BONUS × volume_tier
+ + W_EXPERIENCE × experience_tier × freshness_multiplier
+ − W_BAN_PENALTY × bans_received
+)
+```
+
+其中 `excess_no_contest = max(0, no_contest_rate − 0.10)`。
+
+### 默认权重
+
+| 权重 | 值 |
+|--------|-------|
+| BASE_SCORE | 50 |
+| W_DISPUTE_LOSS | 0.40 |
+| W_NO_CONTEST | 0.10 |
+| W_DEADLINE_MISS | 0.20 |
+| W_NO_RESPONSE | 0.15 |
+| W_VOLUME_BONUS | 0–25(分层:≥10K→+5、≥100K→+10、≥500K→+15、≥1M→+20、≥5M→+25) |
+| W_EXPERIENCE | 0–25(分层:≥7d→+5、≥30d→+10、≥90d→+15、≥180d→+20、≥365d→+25) |
+| W_BAN_PENALTY | 每次封禁 15 |
+
+### 新鲜度衰减
+
+| 距上次活跃天数 | 乘数 |
+|----------------------|-----------|
+| ≤ 30 | 1.00 |
+| 31–90 | 0.75 |
+| 91–180 | 0.50 |
+| > 180 | 0.25 |
+
+### 综合信任分数
+
+```
+trust_score = reliability_score × risk_factor
+```
+
+| 风险分(保险/下注) | risk_factor |
+|---------------------------|-------------|
+| ≥ 3.0× | 1.00 |
+| ≥ 2.0× | 0.95 |
+| ≥ 1.0× | 0.85 |
+| < 1.0× | 0.70 |
+
+### 新预言机检测
+
+`total_outcomes < 5` → `is_new = true`。UI 中独立徽章。
+
+分数在读取时经 `compute_oracle_reliability_score()` 计算,不存储。
+
+---
+
+## 15. 持仓转让
+
+### 操作
+
+```
+pm_transfer_position { bet_id, to_user, amount, memo }
+```
+
+- 将一笔下注的全部或部分代币转给另一账户
+- 转出的代币保留原市场与结果
+- 赔付在裁定时归当前持有者
+- 无滑点、无市场冲击 —— 纯粹的记录重新分配
+- 对 Onix Binary 与 Onix Multi 持仓均适用
+
+### Memo 隐私模型
+
+| 模式 | 格式 | 可见性 |
+|------|--------|------------|
+| 明文 | 不以 `#` 开头的字符串 | 链上公开 |
+| 加密 | 以 `#` 开头的字符串 | 私密 —— 仅发送方与接收方可解密 |
+
+加密:使用 VIZ 账户 memo 密钥的 ECIES 共享密钥 `ECDH(sender_memo_private, recipient_memo_public)`(标准
+Graphene 模型)。客户端加解密。
+
+---
+
+## 16. 懒惰流动性池
+
+### 参数
+
+| 中位数投票参数 | 作用 |
+|---------|-------------|
+| `pm_lazy_pool_enabled` | 池 kill-switch |
+| `pm_lazy_alloc_percent` | 每个市场分配的自由余额份额(bp) |
+| `pm_lazy_max_total_alloc_percent` | 活跃市场上池占比的上限(bp) |
+| `pm_lazy_recall_step_percent` | 闲置市场的渐进式召回步长(bp) |
+| `pm_lazy_lock_sec` | 存款锁定期(秒) |
+| `pm_lazy_emergency_penalty_percent` | 紧急提取对锁定利润的罚则(bp) |
+| `pm_lazy_min_liquidity_fee_percent` | 池进行分配所需的市场最低 `liquidity_fee_percent`(bp);低于它市场得不到池流动性(奖励下限) |
+| `pm_min_liquidity` | 每个市场的最低分配(亦为市场种子下限) |
+
+### 存款
+
+- 首位存款者:`shares = amount`
+- 后续:`new_shares = amount × total_shares / free_balance`
+- 锁定计时器:`unlock_time = now + pm_lazy_lock_sec`
+- 计算份额前先结算奖励:`pending += shares × (pool.rps − user.snapshot) / PRECISION`
+
+### 自动分配
+
+在市场激活(status → 1)时:
+
+```
+alloc_amount = free_balance × allocation_percent / 100
+× (1 − active_market_penalty_pct / 100) ^ oracle_active_market_count
+× (1 − fault_penalty_pct / 100) ^ oracle_active_fault_stamps
+```
+
+奖励下限闸门(最先检查):若市场 `liquidity_fee_percent < pm_lazy_min_liquidity_fee_percent`,池**不**分配任何资金——它只补贴 LP 费用付得起的市场。此时创建者自己的种子是唯一流动性。
+
+检查:`alloc_amount ≥ min_market_allocation`,`allocated + alloc_amount ≤ total × max_total_allocation / 100`。
+
+Pool LP 以 `user=0` 插入。在时间加权费用分配中等同参与。
+
+### 奖励分配(懒惰记账)
+
+在带池 LP 利润的市场裁定时:
+
+```
+profit = lp_return − allocation_amount
+if profit > 0 AND total_shares > 0:
+ pool.reward_per_share += profit × PRECISION / total_shares
+```
+
+用户奖励(读取时计算):
+
+```
+live_reward = pending_rewards + shares × (pool.rps − user.snapshot) / PRECISION
+```
+
+### 计划提取
+
+从合并的已解锁记录(全部或部分):
+
+```
+1. Run unlock consolidation
+2. Settle rewards: pending += shares × (rps − snapshot) / PRECISION
+3. Share value = shares_to_burn × free_balance / total_shares
+4. Reward portion = pending_rewards × withdraw_percent / 100
+5. Total payout = share value + reward portion
+```
+
+### 紧急提取
+
+全部存款(锁定 + 未锁定):
+
+```
+1. Settle rewards
+2. total_value = shares × free_balance / total_shares + pending_rewards
+3. profit = total_value − principal_deposited
+4. if profit > 0: penalty = profit × (locked_shares / total_shares) × emergency_penalty / 100
+5. Penalty → pool reward_per_share
+6. User receives: total_value − penalty
+```
+
+### 机会成本保护
+
+> **治理 vs 硬编码。** 仅召回**步长**由中位数投票——`pm_lazy_recall_step_percent`。其余为**硬编码**,更改需
+> 硬分叉:**10 步**划分(`window/10`、`check_step ≥ 10`)、闲置判定(自上次检查起*无新下注*即为闲置——
+> `bets_sum ≤ bets_sum_at_check`),以及**每活跃市场 5% 惩罚**(`alloc × 95/100`)。故障印记的罚则与到期窗口
+> 同为硬编码。
+
+**A. 渐进式召回:** 市场时长分为 **10 个固定步**。每步若自上次检查起**无新下注**,则向池召回当前分配的
+`pm_lazy_recall_step_percent`(bp,治理)。
+
+**B. 活跃市场惩罚:** `factor = (1 − 5%) ^ active_market_count`——硬编码,同一预言机每个并发活跃市场递归减 5%。
+
+**C. 故障印记:** 在不良市场结果(no-contest、错过截止、零成交量、败诉、无响应、自动关闭)时,预言机获得一个
+在固定洁净运营窗口后自动到期的故障印记;每个活跃印记进一步降低其分配。(罚则大小与到期窗口为硬编码。)
+
+---
+
+## 16a. 可选杠杆(懒惰池提供资金)
+
+自 HF14 上线;可选,由中位数 kill-switch `pm_leverage_enabled`(默认关)治理。
+
+- **开仓**(`pm_leverage_open`)—— 下注者提交抵押;懒惰池从 `free_balance` **借出**保证金(受
+ `leverage_fund_used` 限制;开仓时对照 `pm_leverage_fund_percent`、`…_max_per_position_bp`、
+ `…_max_position_ratio_percent`、`…_min_market_liquidity` 检查)。不增发代币 —— 从系统视角看持仓足额
+ 抵押。杠杆开仓**不**创建 `pm_bet`;曲线权重持有在 `pm_leverage_position_object` 上。
+- **清算** —— 按**下注前储备**执行,故池回收 `min(cancel_value, obligation) ≥ loan`:对向下注级联
+ (`pm_place_bet`)与结算强平始终全额回收(贷款 + 利息 → 池);**唯一**有界的坏账路径是同侧
+ `pm_cancel_bet`(情形 B)。级联**不**受 `pm_leverage_enabled` 门控(该标志仅阻止新开仓),故关闭杠杆永
+ 不剥夺已开持仓的保护。
+- **虚拟操作** —— `pm_leverage_resolve`(结算时强平,带结果 + 杠杆)、`pm_leverage_liquidate`(盘中,
+ `reason` 0 对向 / 1 撤注)。
+- **治理权重** —— 懒惰池存款人保留其在 PM 争议与 DAO 委员会的投票权重(池 NAV → vesting-shares,经
+ `get_vesting_share_price`,HF14 门控)。
+
+API:`get_account_leverage_positions`、`get_market_leverage_positions`、`get_lazy_pool`。
+
+## 16b. 批量 / 提交-揭示下注(抗 MEV)
+
+自 HF14 起对**二元**市场上线(多元在 LMSR 批量落地前强制 `allow_instant_bet`);按市场可选(`allow_batch`
+/ `allow_instant_bet`),中位数 kill-switch `pm_commit_reveal_enabled`。
+
+- `pm_place_bet` 带 `mode = 1` 将一笔**批量**下注入队;`pm_commit_bet`(承诺哈希 + 托管)→
+ `pm_reveal_bet` 运行**提交-揭示**流程。未揭示的承诺通过 `pm_commit_forfeit` 没收
+ `pm_commit_no_reveal_penalty_percent`(bp)。
+- 在每个纪元边界(`pm_batch_epoch_blocks`,揭示窗口 `pm_reveal_window_blocks`)入队下注由 `pm_batch_settle`
+ 定时任务以**统一价格**结算 —— 只有净残量推动 AMM,故批内排序无优势,且 `Σ reserve ≥ L` 不变量得以保持。
+
+## 17. 链上对象模型
+
+所有状态都存于在 HF14 注册为核心索引的 **chainbase 对象**中 —— 不存在 SQL 数据库。字段级定义位于操作/对象头文件中,并可通过 [`prediction_market_api` 插件](../plugins/prediction-market-api)
+只读查询。原型保存在 `users` 表上的声誉计数器现为 `pm_oracle_object` 的字段。
+
+| 对象(索引) | 保存 | 按何查找 |
+|---|---|---|
+| `pm_oracle_object` | 预言机注册、保险、14 个声誉计数器、故障印记、封禁(`banned_until` + `banned_by`) | owner |
+| `pm_market_object` | 市场配置、CPMM 储备(`reserve_a/b`、`k`)、`*_fee_percent`(bp)、`status` / `payout_status`、计时器、`dispute_mode`、`a_bets_sum` / `b_bets_sum`、预言机裁定声明(`decision_url` / `decision_reason`) | id / creator / oracle / result_expiration |
+| `pm_outcome_object` | 每结果的 LMSR `q`、`bets_sum`、`bets_count`(多元市场) | market + outcome |
+| `pm_bet_object` | 一笔下注 —— account、`side` / `outcome_index`、`amount`、曲线 `weight`、`time_penalty`、`status`、`mode` | market / account |
+| `pm_liquidity_object` | 一个 LP 持仓 —— 本金、存款时间、时间权重;`provider` 为空 ⇒ 懒惰池 LP | market |
+| `pm_commit_object` | 提交-揭示的承诺哈希 + 托管(批量 / 提交-揭示) | market / account |
+| `pm_dispute_object` | 一个争议 —— disputer、`proposed_outcome`、费用托管、计时器、`status`、`dispute_mode`、预言机反驳(`oracle_response` / `oracle_response_time`) | market |
+| `pm_dispute_vote_object` | 一张委员会选票 —— voter、`vote_outcome`、`vote_percent`(关闭前可改) | market + voter |
+| `pm_lazy_pool_object` | 单例池 —— `free_balance` / `allocated_balance` / `earned_balance`、`reward_per_share`、`leverage_fund_used`、`total_shares` | 单例(id 0) |
+| `pm_lazy_deposit_object` | 一位存款人 —— shares、奖励快照、解锁时间 | account |
+| `pm_lazy_allocation_object` | 池对某市场的静默 LP 分配 + 渐进式召回状态(`bets_sum_at_check`、`check_step`、`recalled_amount`) | market |
+| `pm_leverage_position_object` | 一个已开杠杆持仓 —— collateral、loan、obligation、曲线权重、`status` | account / market + status |
+| `pm_creator_ban_object` | 被封禁的创建者 —— `banned_until`、`ban_count`、`banned_by` | ban account |
+
+声誉指标在读取时计算(`compute_oracle_reliability_score()` —— §14),不存储。所有百分比字段均为基点
+(`*_percent`,bp)。懒惰池的按市场分配与渐进式召回状态位于 `pm_lazy_allocation_object`;预言机故障印记与声誉计数器位于 `pm_oracle_object`。
+
+**仅插件(非共识):** `pm_market_meta_object` —— 链下解析的市场元数据(类别 / 标签 / 受禁司法辖区),用于
+发现与辖区过滤;由 [`prediction_market_api`](../plugins/prediction-market-api) 插件从每个市场不透明的
+`metadata` 字符串构建,从不参与共识。
+
+这些对象的完整字段定义见[预测市场操作](../protocol/operations/prediction-markets)。
diff --git a/@l10n/zh-CN/docs/prediction-markets/whitepaper.md b/@l10n/zh-CN/docs/prediction-markets/whitepaper.md
new file mode 100644
index 0000000000..c17ead3f3a
--- /dev/null
+++ b/@l10n/zh-CN/docs/prediction-markets/whitepaper.md
@@ -0,0 +1,725 @@
+---
+title: Onix 协议 —— 白皮书
+description: Onix 协议产业白皮书:VIZ DLT 上具备流动性保障的预测市场。
+---
+
+# Onix 协议:VIZ DLT 上具备 LP 保障的预测市场
+
+**产业白皮书**
+
+*Anatoly Piskunov (On1x)*
+*版本 2.0 —— 2026 年 6 月(链上 / HF14)*
+
+---
+
+> **链上状态(HF14)。** 本文最初针对中心化原型撰写。协议现已作为 VIZ DLT 上的**一等公民共识操作
+> (`pm_*`)**运行,并在 `consensus_sim` 中验证。自 HF14 起上线:两种市场类型(CPMM 二元 + LMSR 多元)、
+> 同注分彩零和结算、**懒惰池**、可选**杠杆**子系统、可选**批量 / 提交-揭示下注**(二元)、保证金预言机,
+> 以及双模式争议系统(委员会 / 账户)。所有百分比参数均为**基点(bp):10000 = 100.00%**(原型使用千分比
+> permille)。下文在链上设计与原型文本不同之处均有标注。
+
+## 摘要
+
+预测市场将分散的信息聚合为价格,其概率估计持续优于民调、专家组和统计模型。然而采用仍受一个结构性问题制约:
+**流动性提供者会亏钱。**
+
+Uniswap v3 的 LP 承受无常损失。LMSR 做市商面临全部补贴的风险。CLOB 做市商遭遇逆向选择。每一种现有模型都要求
+资本提供者以不确定的收益换取下行风险——数据显示大多数人亏损。
+
+**Onix 协议**彻底消除 LP 风险。这是一种预测市场架构,其中 LP 本金由**结算机制本身结构性地保障**——不是靠
+保险,不是靠对冲。赢家只从输家被没收的本金中获得赔付。LP 资金提供市场深度,但从不用于结算下注。
+
+本文描述 Onix 协议的两种市场类型——**Onix Binary**(恒定乘积做市商)与 **Onix Multi**(LMSR 定价 +
+同注分彩结算)——以及市场架构、预言机与争议解决系统、懒惰流动性池、可选杠杆、治理模型,及其作为共识级操作在
+VIZ DLT 上的实现。
+
+---
+
+## 1. 问题:预测市场中的 LP 风险
+
+每个预测市场都需要流动性。没有它,价格便毫无意义——一笔使市场移动 20% 的下注暴露的是下注者的资金,而非群体
+的智慧。根本问题是:**谁提供这份流动性,他们承担什么风险?**
+
+### 1.1 当前格局
+
+| 平台 | LP 模型 | LP 风险 | 收益来源 |
+|----------|----------|---------|-------------|
+| **Uniswap v3** | 集中流动性 AMM | 无常损失(常 >5% 年化;>50% 的 v3 LP 跑不赢买入持有) | 交易手续费 |
+| **Aave / Compound** | 借贷池 | 智能合约风险、清算级联 | 借款人利息 |
+| **Curve** | Stableswap AMM | 锚定资产低 IL、智能合约风险 | 手续费 + CRV 增发 |
+| **Standard LMSR** | 做市商补贴 | 损失高达 `b × ln(N)`——全部补贴 | 买卖价差 |
+| **Polymarket (CLOB)** | 主动做市 | 库存风险、逆向选择 | 买卖价差 |
+| **Kalshi** | 无 LP 概念 | 不适用(交易所模型) | 不适用 |
+
+格局清晰:为预测市场提供流动性要么需要主动管理技能(CLOB),要么容忍资本损失(LMSR),要么接受无常损失
+(AMM)。这些都不适合零售参与者。
+
+### 1.2 为何重要
+
+预测市场在深而具流动性时表现最佳。深度市场产生准确价格、吸引知情交易者,并产生使预测市场成为公共品的信息价
+值。但深度需要资本,资本需要风险补偿。
+
+结果是先有鸡还是先有蛋的问题:
+- 浅市场 → 高滑点 → 差体验 → 少下注者 → 低手续费 → 无 LP 激励 → 浅市场
+
+打破此循环需从等式的 LP 一侧移除风险。若提供流动性是无风险的,进入门槛降为零,飞轮便可开始转动。
+
+---
+
+## 2. Onix 协议
+
+### 2.1 设计原则
+
+Onix 协议建立在三条架构不变量之上:
+
+1. **LP 本金结构性安全。** 这不是风险缓释策略——而是赔付架构的属性。LP 资金与下注结算取自物理上分离的池。
+
+2. **输家资助赢家。** 所有赔付(赢家利润、预言机费、创建者费、LP 费)只取自输家被没收的本金。手续费在裁定时
+ 计算为 `floor(losers_sum × fee_bp / 10000)`(bp:10000 = 100.00%),从不在下注时扣除。
+
+3. **双市场类型,单一保障。** 二元市场(Onix Binary)与多元市场(Onix Multi)使用不同定价公式,但共享同一
+ 结算模型与同一 LP 保障。
+
+### 2.2 Onix Binary(CPMM + 同注分彩结算)
+
+Onix Binary 使用恒定乘积做市商公式——与 Uniswap 相同的 `x * y = k` 不变量——作为二元结果的**定价引擎**,
+配合**同注分彩结算**(输家按权重比例资助赢家),与 Onix Multi 相同的结算模型。
+
+**机制:**
+
+市场维护两个储备 `reserve_a` 与 `reserve_b`,乘积恒为 `k`:
+
+```
+k = reserve_a × reserve_b
+```
+
+当用户对结果 A 下注 `amount`(实现中 side 0 → `reserve_a`)时,本金进入该侧储备,代币从**对侧**储备取出:
+
+```
+new_reserve_a = reserve_a + amount
+new_reserve_b = floor(k / new_reserve_a)
+tokens_received = reserve_b − new_reserve_b
+```
+
+`tokens_received`(称为 `weight`)是用户对赢家池的**相对索取权**(若结果 A 获胜;结算为同注分彩——见下,
+与 Onix Multi 相同)。被下注的一侧其隐含概率上升(A 上的钱越多 → `reserve_a` 增长 → `P(A)` 增长):
+
+```
+P(A) = reserve_a / (reserve_a + reserve_b)
+P(B) = reserve_b / (reserve_a + reserve_b)
+```
+
+**结算与 LP 安全性证明(同注分彩):**
+
+裁定时,赢家取回本金,外加按权重的输家池比例份额(与 Onix Multi 相同):
+
+```
+winners_pool = losers_sum − fees
+payout = bet_amount + (weight / total_winning_weight) × winners_pool − time_penalty_on_profit
+```
+
+LP 本金 `L` 无条件返还,保障精确:
+
+```
+Money OUT = L + winning_bets + winners_pool + fees = L + winning_bets + losing_bets = L + all_bets = Money IN
+```
+
+无论权重如何,总赔付以 `losers_sum` 为上限,故 LP 资金从不用于结算下注。CPMM 是**定价引擎**(概率 +
+权重);它不门控赔付。(AM-GM 关系 `reserve_a + reserve_b ≥ 2√k = L` 对定价曲线仍成立,但不再用于偿付能力。)
+
+**示例:**
+
+```
+Setup: 200 VIZ liquidity → reserve_a = 100, reserve_b = 100, k = 10,000
+Fees (bp): oracle 50 (0.5%), creator 50 (0.5%), liquidity 100 (1%)
+
+Alice bets 50 VIZ on A → receives weight 33.33 (price moves from 50% to 69%)
+Bob bets 80 VIZ on B → receives weight 81.82
+
+Resolution: A wins
+ Losers (Bob): 80 VIZ forfeited → losers_sum = 80
+ oracle_fee = floor(80 × 50/10000) = 0.4 VIZ
+ creator_fee = floor(80 × 50/10000) = 0.4 VIZ
+ liq_fee = floor(80 × 100/10000) = 0.8 VIZ
+ winners_pool = 80 − 1.6 = 78.4 VIZ
+
+ Alice (only winner, weight 33.33 of 33.33):
+ payout = 50 (stake) + 78.4 × (33.33/33.33) = 128.4 VIZ (minus any time penalty on profit)
+ LP return: 200 VIZ principal + share of 0.8 VIZ fee pool
+```
+
+### 2.3 Onix Multi(LMSR + 同注分彩结算)
+
+Onix Multi 是协议针对 3–10 个结果市场的创新。它结合 Hanson 的对数市场评分规则(LMSR,2003)做实时定价,
+与同注分彩结算来保障 LP 安全。
+
+**定价(LMSR softmax):**
+
+对结果为 {1, 2, ..., N} 的市场,每个结果有数量参数 `q_i`:
+
+```
+price(i) = exp(q_i / b) / Σ_j exp(q_j / b)
+```
+
+这是 softmax 函数——价格按构造总是恰好相加为 1.0。无需任何套利机制或 split/merge 操作。
+
+在结果 i 上买入 Δ 代币的成本:
+
+```
+C(q) = b × ln(Σ_j exp(q_j / b))
+
+cost = C(q + Δ·e_i) − C(q)
+```
+
+参数 `b` 控制价格敏感度(b 越高,每笔下注的价格冲击越小)。它由 LP 补贴提供资金:`b = S / ln(N)`,其中
+S 为总补贴。
+
+**创新——同注分彩结算:**
+
+在**标准 LMSR** 中,做市商是所有下注的对手方。若群体正确预测结果,做市商损失高达 `b × ln(N)`——可能是全部
+补贴。这正是 LMSR 在企业预测市场(Microsoft、Inkling,由运营方吸收损失)之外采用有限的原因。
+
+**Onix Multi 改变了赔付来源。** 裁定时:
+
+```
+1. Oracle declares the winning outcome
+2. Losers forfeit 100% → losers_sum
+3. Fees deducted from losers_sum (bp; 10000 = 100.00%):
+ oracle_fee = floor(losers_sum × oracle_fee_bp / 10000)
+ creator_fee = floor(losers_sum × creator_fee_bp / 10000)
+ liq_fee = floor(losers_sum × liquidity_fee_bp / 10000)
+ winners_pool = losers_sum − fees
+4. Winners receive:
+ payout = bet_amount + (tokens / total_winning_tokens × winners_pool) − time_penalty
+5. LP subsidy returned unconditionally
+```
+
+赢家由输家支付,而非 LP。补贴在架构上与结算流分离。
+
+**LP 本金保障证明:**
+
+1. LP 存入 `S` VIZ 作为补贴,用于提供市场深度。
+2. 下注期间,用户支付 VIZ → 取得结果代币。VIZ 累积为下注池。
+3. 裁定时,输家被没收的本金资助赢家赔付与手续费。补贴 `S` 从未进入赔付池。
+4. 无论结果如何,补贴无条件返还给 LP。
+
+**对比:**
+
+| 维度 | Standard LMSR | Onix Multi |
+|-----------|---------------|------------|
+| LP 角色 | 所有下注的对手方 | 深度存款(非对手方) |
+| LP 最大损失 | `b × ln(N)`(全部补贴) | **零** |
+| 赢家赔付 | 1 代币 = 1 单位货币 | 代币 = 对输家池的比例索取权 |
+| 需要 CTF split/merge? | 是(强制价格之和 = 1) | 否(softmax 保证) |
+
+**示例(3 结果选举):**
+
+```
+Setup: b = 1000, outcomes = [A, B, C], subsidy = 1000 VIZ
+Initial: price(A) = price(B) = price(C) = 33.3%
+
+Alice bets 50 VIZ on A → ~47 tokens (price: 33% → ~38%)
+Bob bets 100 VIZ on B → ~88 tokens
+Carol bets 30 VIZ on C → ~29 tokens
+
+Resolution: A wins
+ Losers: Bob (100) + Carol (30) = 130 VIZ
+ Fees (200 bp = 2% total): 2.6 VIZ
+ winners_pool = 127.4 VIZ
+
+ Alice: 50 + (47/47 × 127.4) = 177.4 VIZ
+ LP: 1000 VIZ returned in full + share of liquidity fees
+```
+
+### 2.4 边界情形
+
+| 情形 | 结果 |
+|----------|---------|
+| 全部下注于赢家 | `losers_sum = 0` → 每个下注者恰好取回其下注额。LP 补贴返还。零和。 |
+| 无人下注于赢家 | 整个输家池未分配 → LP 奖励。LP 利润最大化。 |
+| 零成交量市场 | LP 补贴全额返还。无手续费、无赔付。 |
+| 单一下注者获胜 | 该下注者获得 `bet_amount + winners_pool`。LP 补贴返还。 |
+
+---
+
+## 3. 市场架构
+
+### 3.1 市场生命周期
+
+```mermaid
+stateDiagram-v2
+ direction LR
+ state "Waiting (0)" as Waiting
+ state "Active (1)" as Active
+ state "Closed (2)" as Closed
+ state "Resolved (3)" as Resolved
+ state "Deleted (-1)" as Deleted
+ state "Paid out" as Paid
+ [*] --> Waiting
+ Waiting --> Active: 预言机接受
+ Waiting --> Deleted: 预言机拒绝
+ Active --> Closed: 下注到期
+ Active --> Resolved: 提前裁定(若允许)
+ Closed --> Resolved: 预言机裁定
+ Resolved --> Paid: 宽限窗口(12 小时)
+ Deleted --> [*]
+ Paid --> [*]
+```
+
+市场由创建者创建,经预言机(质押保险)审阅并接受,开放下注,以某一结果裁定,并在争议宽限期后赔付。
+
+### 3.2 费用模型(仅从输家提取)
+
+Onix 协议的一个鲜明特征是**下注时不扣任何手续费**。完整下注额进入市场储备。手续费仅在裁定时计算,且只取自输
+家被没收的本金:
+
+```mermaid
+flowchart TD
+ LS["losers_sum(输家下注的 100%)"]
+ LS --> OF["oracle_fee = floor(losers_sum × oracle_fee_bp / 10000)"]
+ LS --> CF["creator_fee = floor(losers_sum × creator_fee_bp / 10000)"]
+ LS --> LF["liquidity_fee = floor(losers_sum × liquidity_fee_bp / 10000)"]
+ LS --> WP["winners_pool = losers_sum − 所有费用"]
+```
+
+这提供了结构性保障:手续费与赢家赔付取自完全分离的资金来源。费用提取永不与对赢家的义务竞争。
+
+**预言机费用条款在接受时冻结(offer→quote)。** 创建者公布预言机可收取的**上限**(bp 计的
+`oracle_fee_percent` 上限 + `oracle_fixed_fee` 上限);预言机接受时报出其实际条款(≤ 创建者上限且 ≤ 中位
+治理上限 `pm_max_oracle_fee_percent`),冻结到市场上并发出 `pm_market_accepted` 虚拟操作。自预言机在创建时
+冻结其条款。**预言机固定费**(每个市场)从输家池余额支付(从不增发)。**市场创建费**进入 DAO 基金作为反垃圾
+保护。
+
+### 3.3 时间加权 LP 分配
+
+LP 手续费份额按 `amount × max(1, seconds_to_expiration)` 比例分配:
+
+```
+weight_i = amount_i × max(1, sec_to_expiration_i)
+fee_share_i = floor(total_fee_pool × weight_i / Σ weight_j)
+```
+
+早期 LP 每单位资本的收益远高于后期 LP。在 48 小时市场中,第 1 小时存入的 LP 每 VIZ 收益约为第 47 小时存入者
+的 ~2400 倍。
+
+每笔存款作为独立持仓追踪——同一用户的多笔存款分别加权与支付。无论市场结果如何,LP 本金始终全额返还。
+
+### 3.4 迟下注的时间惩罚
+
+为抑制临近到期的下注(其不确定性风险更小),对临近到期下注施加可配置的时间惩罚:
+
+```
+if time_to_expiration < penalty_window:
+ ratio = 1 − (time_to_expiration / penalty_window)
+ penalty_ratio = ratio² // quadratic (default)
+ time_penalty = floor(penalty_ratio × max_penalty)
+```
+
+惩罚**仅作用于利润**,永不作用于本金。获胜下注者始终至少取回其原始本金。二次曲线在惩罚窗口早期平缓、后期陡
+峭,奖励「稍迟」而非「极迟」。
+
+### 3.5 持仓转让
+
+持仓可通过原生协议操作在账户间转让:
+
+```
+pm_transfer_position { bet_id, to_user, amount, memo }
+```
+
+无滑点、无市场冲击——纯粹的记录重新分配。`memo` 字段支持明文与加密两种模式(通过 VIZ 账户 memo 密钥的
+ECIES),可实现 P2P 交易、OTC 交易与私密备注。
+
+这是 Conditional Tokens Framework(Polymarket/Gnosis)中唯一带来真实用户价值的可组合性特性。CTF
+split/merge 在架构上不必要——Onix 定价公式按构造保证价格一致性。
+
+---
+
+## 4. 预言机与争议解决
+
+### 4.1 保证金预言机模型
+
+Onix 协议中的预言机默认不被信任——它们是**有保证金的**。每个预言机必须:
+
+- 以一次性费用注册(默认 10 VIZ)
+- 存入保险(最低 5,000 VIZ)
+- 显式接受市场(每次接受都质押其保险)
+- 以结果及支撑证据(decision URL)裁定市场
+
+保险保证金创造问责:误裁、错过截止或败诉的预言机其保险会被罚没。为使经济安全模型成立,保证金必须超过预言机潜
+在的操纵利润。
+
+预言机收入有两个来源:
+1. **固定费**(每个市场)——补偿质押保险与提供裁定
+2. **百分比费**(裁定时取自输家池)——随市场成交量扩展
+
+### 4.2 争议仲裁
+
+任何下注者可在宽限期内支付争议费以质疑裁定。争议期间所有赔付冻结。
+
+裁决以两种**按市场选择**的模式之一进行,于创建时选定:
+
+- **委员会模式(`dispute_mode = 0`,默认)**——*全体 SHARES 选民*通过**按质押加权投票**
+ (`pm_dispute_vote`)决定,由 `pm_dispute_finalize` 定时任务在 `voting_end_time` 确定性地计票。这是
+ **公开听证**:实时计票可查询,投票**不**隐藏于 commit-reveal 之后(一项刻意且永久的选择——DAO 尽可能
+ 透明地裁决争议)。由于听证期间会浮现新证据,**投票可修改**至关闭(重复投票覆盖先前的)。投票者权重为其
+ `effective_vesting_shares` **加上其转换为 vesting-shares 的懒惰池质押**,因此把 VIZ 存入池中的 DAO
+ 成员保留其治理权重。
+- **账户模式(`dispute_mode = 1`)**——单一指定的 `dispute_resolver`(建议多签)作出裁决
+ (`pm_dispute_resolve`)。
+
+任一模式下的裁决逻辑:
+
+**若预言机有误(翻转):**
+- 应用正确结果并重算赔付。
+- 提出争议者取回其费用,**外加从被罚没保险中切出的奖励**,大小为
+ `dispute_fee × pm_dispute_reward_multiplier`(bp;如 30000 = ×3),以罚没额为上限。
+- **罚没的余额加入赢家池**(经 `forfeit_pool`)——归获胜下注者,**而非**裁决者或 DAO。委员会投票者与账户
+ 模式裁决者**均不获奖励**(委员会投票是无偿的治理职责)。
+- 预言机保险被罚没(委员会模式按共识强度缩放,账户模式按裁决者的 `penalty_amount`),并可附加封禁。
+
+**若预言机正确(维持):**
+- 提出争议者**将全部争议费没收给预言机**(对恶意挑战的补偿)。
+- 原赔付不变继续。
+
+**防拒裁:** 若裁决者 14 天内不作为,争议自动关闭:所有下注与 LP 退款,预言机受罚,提出争议者的费用退回。这
+保证资金永不被无限期冻结。
+
+### 4.3 预言机声誉评分
+
+协议为每个预言机追踪 14 项链上指标并计算可靠性分数(0–100):
+
+```
+reliability_score = clamp(0, 100,
+ 50 (base)
+ − 0.40 × dispute_loss_rate × 100
+ − 0.10 × excess_no_contest × 100
+ − 0.20 × deadline_miss_rate × 100
+ − 0.15 × (1 − dispute_response_rate) × 100
+ + volume_bonus (0–25)
+ + experience_bonus × freshness_multiplier (0–25)
+ − 15 × bans_received
+)
+```
+
+关键设计选择:
+- **比率而非计数**——100 次中败 1 次(1%)优于 2 次中败 1 次(50%)
+- **从 50 中性起步**——新预言机须赢得声誉,而非从 100 起步
+- **新鲜度衰减**——不活跃的预言机随时间失去经验加分
+- **成交量分层**——高成交量预言机因经过验证的记录获得加分
+
+可靠性分数与风险因子(保险与下注量之比)结合,产生**综合信任分数**——展示给用户的主要指标。
+
+### 4.4 No-Contest 与 3 结果裁定
+
+无法核实结果的预言机可自愿宣布 **no-contest**,以较低罚则触发退款(从保险扣除 50% 争议费——远比败诉便宜)。
+这形成激励梯度:
+
+| 情形 | 预言机成本 | 封禁风险 |
+|----------|------------|----------|
+| 自愿 no-contest | 500 VIZ | 无 |
+| 败诉 | 1,000+ VIZ + 额外罚款 | 永久或临时 |
+| 错过截止 | 250 VIZ(自动罚款) | 无(但损害声誉) |
+
+若用户认为预言机滥用 no-contest,可对其提出争议。裁决者随后从**三个**可能的正确结果中选择:A 胜、B 胜,或
+确认 no-contest。这阻止预言机用 no-contest 来逃避向获胜下注者赔付。
+
+---
+
+## 5. 懒惰流动性池
+
+### 5.1 资本部署问题
+
+个体 LP 提供需要主动选择市场。多数用户不会手动评估并存入特定市场。结果:多数市场仅以创建者的初始流动性启动,
+产生浅订单簿与高滑点。
+
+### 5.2 池到市场的自动分配
+
+懒惰流动性池通过接受存款并在每个新市场激活时**自动分配**池自由余额的一个百分比来解决此问题:
+
+```
+alloc_amount = free_balance × allocation_percent / 100
+```
+
+分配按当前自由余额(非原始总额)计算,形成几何衰减——池永不被完全耗尽:
+
+```
+After 50 markets (2% allocation each): ~357 VIZ free from original 1,000
+After 100 markets: ~133 VIZ still free
+```
+
+最大总分配上限(默认 70%)提供额外安全。
+
+### 5.3 奖励分配(一个共享累加器)
+
+问题:当市场带池利润裁定时,该利润须按份额比例分给**所有**当前存款人——但在每个市场上遍历每位存款人将是
+O(N) 且无界。池用**一个全局累计值** `reward_per_share`(「rps」)避免这一点:
+
+```
+// When a market resolves with pool LP profit, the per-share value of the pool rises once:
+pool.reward_per_share += profit × PRECISION / total_shares
+
+// A depositor's earnings = their shares × how much rps has risen since they last touched the pool:
+live_reward = pending + shares × (pool.reward_per_share − user.snapshot) / PRECISION
+```
+
+通俗地说:每位存款人「拥有」每次 `reward_per_share` 上升中的一份,其奖励 =
+`份额 × (当前 rps − 上次存取时记录的 rps)`。存款人自己的记录**仅在其行动时**(存入/提取)才被触及;在此之前
+其权益在全局数字中静默累积。因此向成千上万存款人分配利润是 **O(1)**(一次加法),且在领取前不付出任何资金。
+这是出自
+[SushiSwap MasterChef 合约](https://github.com/sushiswap/masterchef/blob/master/contracts/MasterChef.sol)
+(及 Compound 的 cToken 指数)的著名累加器模式;`PRECISION`(1e9)保持整数除法精确。
+
+### 5.4 机会成本保护
+
+池向每个市场自动分配,形成一个攻击面:恶意预言机可创建长期零成交量市场以锁定池资金。三种机制应对:
+
+**渐进式提前召回:** 市场时长分为 10 步。每步若下注量低于阈值(分配的 1%),则将当前分配的 10% 召回池中。
+完全闲置的 30 天市场会失去约 60% 的分配。
+
+**活跃市场惩罚:** 同一预言机每多一个活跃市场,其分配减少 5%(递归)。拥有 10 个活跃市场的预言机每个市场仅获
+约 60% 的基础分配,激励质量而非数量。
+
+**故障惩罚印记:** 不良结果(错过截止、败诉、零成交量裁定)产生惩罚印记,进一步降低未来分配。印记在 10 天干
+净运营后自动到期。
+
+### 5.5 可选杠杆(由懒惰池提供资金)
+
+懒惰池**从一个 `free_balance` 承担两种角色**:静默的市场 LP 分配*以及*为**可选杠杆**子系统提供资金。下注者
+可开立杠杆持仓(`pm_leverage_open`),其保证金为**来自池的贷款**——不增发代币,从系统视角看持仓始终足额抵
+押。会击垮 CLOB 清算引擎的二元「跳空风险」通过**按下注前储备清算**处理:对向下注或结算强平回收
+`min(cancel_value, obligation) ≥ loan`,故池取回其贷款外加利息;唯一有界的坏账路径是同侧
+`pm_cancel_bet`。中位数 kill-switch(`pm_leverage_enabled`,默认关)阻止*新*开仓,但保护性清算级联刻意
+**不**受其门控——关闭杠杆永不剥夺已开持仓的保护。池在 LP 收益之外赚取杠杆利息,按相同的 MasterChef 方式记账。
+池贷款的结算发出 `pm_leverage_resolve` / `pm_leverage_liquidate`。
+
+---
+
+## 6. 治理
+
+### 6.1 代表投票的链参数
+
+VIZ 使用委托权益证明(DPoS)共识,由当选代表(验证人)通过中位数投票机制治理链参数:
+
+1. 每位代表公布所有参数的偏好值
+2. 网络计算所有活跃代表投票的**中位数**
+3. 中位数移动时参数自动改变——无需硬分叉、无需部署
+
+所有预测市场参数(费用、罚则、保险要求、争议窗口、懒惰池设置、杠杆旋钮、批量/提交-揭示时序)均由代表投票。所
+有百分比参数均以**基点(bp),10000 = 100.00%**:
+
+| 示例 | 治理 |
+|----------|-----------|
+| `pm_dispute_fee`、`pm_max_oracle_fee_percent`(bp) | 代表中位数投票 |
+| `pm_dispute_grace_sec`、`pm_dispute_vote_period_sec` | 代表中位数投票 |
+| `pm_dispute_approve_min_percent`、`pm_dispute_reward_multiplier`(bp) | 代表中位数投票 |
+| `pm_lazy_*` 分配/召回、`pm_leverage_*`(enabled、fund %、max position) | 代表中位数投票 |
+| `pm_commit_reveal_enabled`、`pm_batch_epoch_blocks`、`pm_reveal_window_blocks` | 代表中位数投票 |
+
+硬分叉仅用于结构性变更(新操作类型、公式改变),而非经济调参。Kill-switch(`pm_leverage_enabled`、
+`pm_commit_reveal_enabled`)让治理可通过中位数投票停用整个子系统而无需分叉。
+
+### 6.2 司法辖区客户端模型
+
+VIZ DLT 是基础设施,而非运营方——类似于比特币是账本而非货币传输方。协议中立且无需许可。法律义务附着于**客户
+端应用**,而非共识算法。
+
+任何司法辖区都可在 VIZ DLT 上构建合规客户端:
+
+| 客户端组件 | 实现 |
+|-----------------|---------------|
+| 预先批准的预言机 | 客户端持有许可、经 KYC 验证的预言机白名单 |
+| 预先批准的裁决者 | 政府批准的争议解决机构 |
+| KYC/AML | 客户端层身份验证 |
+| 费用路由为税收 | `dao_fund_account_id` → 国库账户 |
+| 市场限制 | 客户端按允许类别过滤 |
+| 下注限额 | 客户端强制的人均上限 |
+
+相同的协议操作(`pm_place_bet`、`pm_resolve`、`pm_dispute`)对无许可与受监管客户端运行一致。差异完全在客
+户端层。
+
+---
+
+## 7. 竞争格局
+
+### 7.1 平台对比
+
+| 维度 | Onix (Forecaster) | Polymarket | Kalshi | Standard LMSR |
+|-----------|-------------------|------------|--------|---------------|
+| **定价** | CPMM(二元)/ LMSR softmax(多元) | CLOB | CLOB | LMSR |
+| **LP 风险** | **零**(结构性保障) | 库存风险 | 不适用 | 高达 `b × ln(N)` |
+| **LP 知识门槛** | 低(存入即赚) | 高(管理订单) | 不适用 | 中 |
+| **费用模型** | 裁定时输家池的 % | 买卖价差 | 交易所费用(1-7%) | 价差 |
+| **预言机** | 每市场保证金 + 委员会争议 | UMA 乐观预言机 | Kalshi(CFTC 监管) | 运营方 |
+| **迟下注惩罚** | 二次、可配置 | 无 | 无 | 无 |
+| **持仓转让** | 原生协议操作 + 加密 memo | CTF(ERC-1155) | 无 | 无 |
+| **治理** | 代表投票参数 | 团队多签 | CFTC 流程 | 运营方 |
+| **基础设施** | VIZ DLT(共识级) | Polygon(智能合约) | 专有服务器 | 各异 |
+
+### 7.2 为何不需要 CTF Split/Merge
+
+Polymarket 使用 Gnosis Conditional Tokens Framework(CTF),其持仓为可拆分与合并的 ERC-1155 代币以强制价
+格一致性(价格之和 = $1)。
+
+在 Onix 协议下,此机制在架构上不必要:
+
+- **Onix Binary(CPMM):** `price(A) + price(B) = reserve_b/(reserve_a+reserve_b) + reserve_a/(reserve_a+reserve_b) = 1` —— 按定义
+- **Onix Multi(LMSR softmax):** `Σ price(i) = Σ exp(q_i/b) / Σ exp(q_j/b) = 1` —— 按 softmax 定义
+
+无需套利机制。价格一致性是公式的数学属性,而非外部强制层。
+
+### 7.3 飞轮
+
+```
+Risk-free LP → lower barrier for retail LPs
+ → more liquidity deposited
+ → deeper markets, less slippage
+ → better UX for bettors
+ → more volume
+ → more fees for LPs
+ → attracts even more LPs
+```
+
+「无无常损失的被动收益」是 Uniswap、Balancer 与 Curve 无法提供的价值主张。对加密原生受众而言,这是一个引人
+注目的叙事:通过为预测市场提供流动性赚取收益,本金零风险。
+
+---
+
+## 8. VIZ DLT:从原型到协议
+
+### 8.1 当前状态
+
+协议起步为带中心化后端的 Telegram WebApp(所有市场逻辑在服务端)——一个有已知限制的可用原型:除 Telegram
+账户外无女巫抵抗、无抗审查、无可组合性。**该迁移现已完成:** 完整市场逻辑作为 **VIZ DLT 上经共识验证的
+`pm_*` 操作**运行(HF14),在 `consensus_sim` 中端到端验证。本节其余部分描述这一现已实现的链上架构。
+
+### 8.2 迁移架构
+
+VIZ DLT 是一种分布式账本技术,约 3 秒出块、DPoS 共识、命名账户(Graphene 风格),且无通用智能合约。预测市
+场操作实现为**一等公民、经共识验证的操作**——非智能合约,非 `custom_json` 负载。
+
+| 层 | 示例 | 经共识验证? |
+|-------|---------|---------------------|
+| **协议操作** | `pm_create_market`、`pm_oracle_accept_market`、`pm_place_bet`、`pm_commit_bet`/`pm_reveal_bet`、`pm_resolve_market`、`pm_dispute_create`/`pm_dispute_vote`/`pm_dispute_resolve`、`pm_lazy_deposit`/`pm_lazy_withdraw`、`pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert` | 是——每个节点验证 |
+| **虚拟操作** | `pm_payout`(每下注者)、`pm_auto_payout`、`pm_market_accepted`、`pm_dispute_finalize`、`pm_dispute_auto_close`、`pm_oracle_missed_penalty`、`pm_lazy_recall`、`pm_batch_settle`、`pm_commit_forfeit`、`pm_leverage_resolve`/`pm_leverage_liquidate` | 是——确定性,在出块时生成 |
+| **metadata / custom_json** | 争议评论、市场描述、UI 元数据 | 否——仅展示/索引 |
+
+每个金融动作(下注、添加流动性、裁定市场、扣押保险)都由每个验证人验证。无效操作在入块前被拒。无 Solidity、
+无 gas 估算、无字节码部署。
+
+前端是**完全无头的 Web 客户端**——无后端服务器、无数据库、无会话。私钥存于浏览器(加密),交易本地签名并广播
+到公共 VIZ 节点。不依赖 Telegram;核心应用与平台无关。
+
+### 8.3 自原型以来已交付,及剩余路线图
+
+**已链上交付(HF14):**
+
+| 特性 | 状态 |
+|---------|--------|
+| 提交-揭示 + 批量下注(二元、可选、中位数 kill-switch) | ✅ 上线 |
+| 可选杠杆(懒惰池提供资金、下注前储备清算) | ✅ 上线 |
+| 懒惰池(自动分配、渐进式召回、MasterChef 记账) | ✅ 上线 |
+| 每下注者 / 杠杆结算虚拟操作 + 插件 API | ✅ 上线 |
+| 懒惰池质押作为治理权重(PM 争议 + DAO 提案) | ✅ 上线 |
+
+**剩余路线图:**
+
+| 优先级 | 特性 | 影响 |
+|----------|---------|--------|
+| 高 | 共享流动性池(类别级 AMM) | 在架构层面解决流动性碎片化 |
+| 高 | 自动数据预言机(外生数据源) | 消除客观市场的操纵 |
+| 中 | 分层争议窗口(小市场 vs 大市场) | 更好的 UX 校准 |
+| 中 | LMSR 批量结算(将批量/提交-揭示扩展到多元) | 多元市场目前强制即时下注 |
+| — | **争议**的提交-揭示投票 | **刻意拒绝**——争议保持公开听证(见 §4.2) |
+
+---
+
+## 9. Onix 不主张什么
+
+对取舍与局限的诚实披露:
+
+- **LP 利润不被保证。** 若某市场零输家下注,则无手续费可分配。LP 取回本金但无收益。
+
+- **存在平台风险。** 漏洞、攻击与治理攻击独立于做市商模型。LP 保障是结构性的(赔付架构),并非保险(无外部担保
+ 基金)。
+
+- **Onix Multi 代币并非固定面值工具。** 在标准 LMSR 中,1 个获胜代币 = 1 单位货币。在 Onix Multi 中,代币是
+ 对输家池的比例索取权。若所有下注者都选中赢家,则人人保本。
+
+- **LP 收益取决于成交量,而非深度。** 补贴 100,000 VIZ 的市场与补贴 1,000 VIZ 的市场,若成交量与费率相同,
+ 赚取相同的绝对手续费。补贴提供深度,而非收益。
+
+- **DPoS 治理有已知取舍。** 验证人少于 PoW/PoS、代表集中风险、按代币加权投票。这些是 DPoS 模型固有的(EOS、
+ Hive、Tron 共有),非 VIZ 特有。
+
+- **VIZ 代币流动性目前较低。** 经济保障(保险保证金、争议费)随代币价格扩展。协议假设效用随时间驱动需求——这
+ 是每个协议原生代币项目所下的同一注。
+
+---
+
+## 10. 结论
+
+Onix 协议针对预测市场采用的根本障碍:LP 风险。通过在二元(CPMM)与多元(LMSR + 同注分彩)市场中结构性地将
+LP 资金与下注结算分离,Onix 使流动性提供变为无风险且对零售参与者可及。
+
+关键创新:
+
+1. **LP 本金保障** 作为架构不变量,而非保险
+2. **输家资助赢家** 结算,消除手续费与赢家赔付的竞争
+3. **LMSR 定价 + 同注分彩结算**(Onix Multi)——将经验证的价格发现与 LP 安全结合
+4. **时间加权 LP 分配**,奖励早期资本投入
+5. **二次时间惩罚** 作用于利润(永不本金),用于迟下注
+6. **懒惰流动性池**,具自动分配、渐进式召回与 MasterChef 记账——同时为可选**杠杆**子系统提供资金(池提供保
+ 证金、下注前储备清算、除有界 cancel-bet 路径外无坏账)
+7. **保证金预言机模型**,具声誉评分、offer→quote 费用冻结与双模式争议系统——委员会争议为**公开听证**,投票
+ 可修改且按懒惰池加权
+8. **可选抗 MEV**——批量 / 提交-揭示下注(二元),带中位数 kill-switch
+9. **共识级实现** 于 VIZ DLT——无智能合约、无 gas、无外部 keeper;严格**零和**(协议从不增发代币)
+
+赌注很简单:若无风险 LP 吸引资本、资本创造深度、深度改善价格、价格吸引下注者,那么 Onix 协议便解决了预测市场
+的流动性问题。协议机制可数学验证。经济假设将由市场检验。
+
+---
+
+## 11. 作者与披露
+
+### 作者
+
+**Anatoly Piskunov**(On1x)—— 俄罗斯 IT 创新者、Web3/DLT 开发者、VIZ 区块链的创建者。其工作横跨分布式账
+本技术、去中心化社交协议与数字社区的经济模型。
+
+主要贡献包括:VIZ 区块链(Fair DPoS、社会资本原语)、Onix 协议(具 LP 保障的预测市场)、Voice 协议(抗审查
+消息)以及关于区块链经济学与 Web3 架构的大量出版物。
+
+完整出版物与项目列表:[https://on1x.com](https://on1x.com)
+
+### 披露
+
+Forecaster 与 Onix 协议的作者同时是 VIZ DLT 的创建者。迁移路线图提议将平台迁至作者设计并构建的区块链。
+
+这一点已预先披露。这也是常态:Polymarket 依赖 Polygon Labs 的基础设施,Kalshi 运行在自有服务器上,Augur
+设计了其运行所依赖的 REP 代币。每个平台都为自身基础设施辩护。问题不在于作者是否有利益——他们总有——而在于技
+术主张是否可证伪。本文中的每一条公式、证明与机制都可数学验证,且代码库开源。
+
+---
+
+## 12. 参考文献
+
+1. Hanson, R. (2003). *Combinatorial Information Market Design.* Information Systems Frontiers, 5(1), 107–119. —— 对数市场评分规则(LMSR)。
+
+2. Adams, H., Zinsmeister, N., Robinson, D. (2020). *Uniswap v2 Core.* —— 恒定乘积做市商(`x * y = k`)。
+
+3. Adams, H., et al. (2021). *Uniswap v3 Core.* —— 集中流动性与无常损失分析。
+
+4. Gnosis. *Conditional Tokens Framework (CTF) Documentation.* https://docs.gnosis.io/conditionaltokens/ —— ERC-1155 预测市场持仓。
+
+5. UMA Protocol. *Optimistic Oracle Documentation.* —— Polymarket 使用的争议升级机制。
+
+6. Leshner, R., Hayes, G. (2019). *Compound: The Money Market Protocol.* —— cToken 累加器模式(reward_per_share 的基础)。
+
+7. SushiSwap. *MasterChef Contract.* —— 奖励分配的懒惰记账模式。
+
+8. Piskunov, A. (2019). *VIZ blockchain system: technical description.* —— VIZ DLT 架构、DPoS 共识、命名账户。
+
+9. Piskunov, A. (2019). *What is Fair DPoS.* —— 委托权益证明中的治理创新。
+
+10. Piskunov, A. (2023). *VIZ as a Digital Representative Self-Governing State.* —— 区块链系统作为数字政体的框架。
diff --git a/@l10n/zh-CN/docs/prediction-markets/workflows.md b/@l10n/zh-CN/docs/prediction-markets/workflows.md
new file mode 100644
index 0000000000..25119631ce
--- /dev/null
+++ b/@l10n/zh-CN/docs/prediction-markets/workflows.md
@@ -0,0 +1,687 @@
+---
+title: 预测市场 —— 工作流与交互图
+description: 一个典型的 Onix 二元市场贯穿每个参与者,附正常与争议裁定的零和主账本。
+---
+
+# 工作流与交互图
+
+一个**典型场景**贯穿每个参与者。每个角色发送特定的**已签名操作**,被特定的**虚拟操作**触及,并以两种结果的
+**代币 发送 / 接收** 表收尾:
+
+- **正常裁定** —— 预言机裁定,宽限期过去,`pm_auto_payout` 结算。无争议。
+- **争议裁定** —— 预言机裁定 **A**,争议**翻转为 B**,随后进行结算。
+
+所有金额为抽象 **VIZ**。所有百分比为 **bp**(10000 = 100.00%)。结算严格**零和**——从不增发代币,
+`current_supply` 不变:
+
+```
+Σ winner_payout + oracle_take + creator_take + lp_bonus + LP_principal
+ == Σ all bet amounts + LP_principal + forfeit_pool (+ insurance slash, 争议中)
+```
+
+## 典型市场 **M**(binary CPMM,A vs B)
+
+| 项 | 值 |
+|------|-------|
+| 引擎 | binary CPMM(`x·y=k`),`weight = tokens_out` |
+| 种子流动性(marketmaker) | **2000** → 储备 A=1000 / B=1000 |
+| `oracle_fee_percent`(预言机报价) | **1000**(10%) |
+| `creator_fee_percent` | **500**(5%) |
+| `liquidity_fee_percent` | **500**(5%) |
+| `oracle_fixed_fee`(预言机报价) | **10** |
+| `dispute_penalty_percent` | **+10000**(罚没至多 100% 保险 ×consensus) |
+
+示例链参数:`pm_market_creation_fee` 5、`pm_oracle_registration_fee` 10、`pm_min_oracle_insurance` 5000、
+`pm_dispute_fee` 1000、`pm_dispute_reward_multiplier` 30000(**3×**)、`pm_no_contest_penalty_percent`
+5000、`pm_oracle_penalty_percent` 500、`pm_lazy_emergency_penalty_percent` 5000、
+`pm_leverage_pool_profit_percent` **R = 10%**、`pm_lazy_alloc_percent` 2000(20%)。
+
+### 角色表
+
+| 角色 | 职责 | 质押 / 动作 |
+|-------|------|----------------|
+| **maker** | 创建者 + 首位 LP | 种子 2000 流动性 |
+| **orac** | 外部预言机 | 保险 5000;报价 10% + 固定 10 |
+| **LP1** | 市场内流动性提供者 | 增加 1000 |
+| **A** | 下注者 —— 赢家 | 100 于 **A**,早期;权重 100 |
+| **C** | 下注者 —— 迟到赢家 | T+85% 时 100 于 **A**;权重 100;时间惩罚 **50%** |
+| **B** | 下注者 —— 输家 | 200 于 **B**;权重 200 |
+| **D** | 杠杆 **×10** 赢家 | collateral 10 + loan 90(市场 **L**) |
+| **E** | 杠杆 **×5** 被清算 | collateral 20 + loan 80(市场 **L**) |
+| **LZ1** | 懒惰池存款人 | 存入 1000 |
+| **disp** | 争议提出者 | 托管争议费 1000 |
+
+> 曲线权重(100 / 100 / 200)显式写出以便同注分彩的算术可读;真实 CPMM 随储备移动会给出略少的权重。
+
+## 交互图
+
+**市场生命周期。**
+
+```mermaid
+flowchart LR
+ W["Waiting (0)"] -->|预言机接受| A["Active (1)"]
+ W -->|预言机拒绝| X["Deleted (-1)"]
+ A -->|betting_expiration| C["Closed (2)"]
+ A -->|提前裁定| R["Resolved (3)"]
+ C -->|预言机裁定| R
+ R -->|宽限期,无争议| P["已赔付"]
+ R -->|提交争议| D["Disputed"]
+ D -->|finalize / 裁决者| P
+```
+
+**结算 —— 正常裁定(A 获胜)。** 输家资助赢家;LP 本金不变(零和)。
+
+```mermaid
+flowchart TD
+ B["B 输 200(输家池)"] --> POOL{"拆分 200"}
+ POOL -->|oracle_fee 20 + fixed 10| OR["预言机 +30"]
+ POOL -->|creator_fee 10| CR["创建者 +10"]
+ POOL -->|liq_fee 10 + 惩罚 37| LPS["LP +47"]
+ POOL -->|winners_pool 150 → 利润 75| A["A → 赔付 175"]
+ POOL -->|利润 75 − 时间惩罚 37| C["C → 赔付 138"]
+ MK["maker + LP1 本金 3000"] -.全额返还.-> MK
+```
+
+**争议 —— 预言机裁 A,翻转为 B。** 惩罚是保险罚没(独立资金)。
+
+```mermaid
+sequenceDiagram
+ participant O as 预言机
+ participant D as 提出者
+ participant V as 委员会 / 裁决者
+ O->>O: 裁定 A
+ D->>V: pm_dispute_create(托管 dispute_fee)
+ O-->>V: 强制响应(截止)
+ V->>V: pm_dispute_vote / pm_dispute_resolve → 翻转为 B
+ V-->>O: 保险罚没(5000)
+ V-->>D: 退费 + 奖励(从罚没中 2000)
+ V->>V: 重新结算 → B 获胜
+```
+
+## 主账本 —— 正常裁定(A 获胜)
+
+`losers_sum = 200`(B)。从输家池扣费:
+`oracle_fee = 200×10% = 20`、`creator_fee = 200×5% = 10`、`liq_fee = 200×5% = 10`、`oracle_fixed = 10`。
+`winners_pool = 200 − 20 − 10 − 10 − 10 = 150`。`Σ 获胜权重 = 200`(A 100 + C 100)。
+
+- **A**:利润 `150×100/200 = 75`,惩罚 0 → **赔付 175**。
+- **C**:利润 75,时间惩罚 `75×50% = 37`(→ LP)→ **赔付 138**。
+- **LP 奖励** = `liq_fee 10 + 惩罚 37 = 47`,按在市场中的时间拆分:**maker ~31 / LP1 ~16**。
+- **oracle_take** = `oracle_fee 20 + fixed 10 = 30`。**creator_take** = `creator_fee 10`。
+
+| 角色 | 发送 | 接收 | 净额(本市场) |
+|-------|-------|----------|-------------------|
+| maker | 2000 流动性 + 5 创建费 | 2000 本金 + 10 创建者费 + 31 LP 奖励 | **+36** |
+| orac | (10 注册费,5000 保险锁定) | 30 oracle-take | **+30** |
+| LP1 | 1000 流动性 | 1000 本金 + 16 LP 奖励 | **+16** |
+| A | 100 | 175 | **+75** |
+| C | 100 | 138 | **+38** |
+| B | 200 | 0 | **−200** |
+
+**零和:** in `= 下注 400 + LP 本金 3000 = 3400`;out `= 175+138+0 + 30 + 10 + 47 + 3000 = 3400`。✔
+5 创建费 + 10 注册费进入 **DAO 基金**(不属于市场池)。
+
+## 主账本 —— 争议裁定(预言机裁 A → 翻转为 B)
+
+`disp` 托管 `dispute_fee 1000`。裁决翻转为 **B**;预言机被罚没。
+当 `dispute_penalty_percent = 10000` 且共识强度 **100%**:`slash = 5000×100%×100% = 5000`。
+奖励切分:`reward_target = fee×3 = 3000` → `bonus = 3000 − 1000 = 2000`(≤ slash)。提出者获得
+`fee 1000 + bonus 2000 = 3000`。余额 `slash − bonus = 3000 → forfeit_pool`。
+
+现在 **B 获胜**。`losers_sum = 200`(A 100 + C 100)。费用 20/10/10 + fixed 10。
+`winners_pool = 200 − 50 + forfeit 3000 = 3150`。`Σ 获胜权重 = 200`(B)。
+- **B**:利润 `3150×200/200 = 3150` → **赔付 3350**。
+- **oracle_take** 仍为 `30`(即使翻转,*市场*费用仍按冻结配置支付——惩罚是**保险罚没**,独立资金)。
+ **creator_take** 10。**LP 奖励** = liq 10。
+
+| 角色 | 发送 | 接收 | 净额(本市场) |
+|-------|-------|----------|-------------------|
+| maker | 2000 + 5 | 2000 本金 + 10 创建者费 + ~6 LP 奖励 | **+11** |
+| orac | 保险 −**5000** 罚没 | 30 oracle-take | **−4970** |
+| LP1 | 1000 | 1000 本金 + ~4 LP 奖励 | **+4** |
+| A | 100 | 0 | **−100** |
+| C | 100 | 0 | **−100** |
+| B | 200 | 3350 | **+3150** |
+| disp | 1000 争议费 | 3000(退费 + 2000 奖励) | **+2000** |
+
+**零和:** in `= 下注 400 + LP 本金 3000 + dispute_fee 1000 + 罚没 5000 = 9400`;
+out `= B 3350 + 预言机 30 + 创建者 10 + lp_bonus 10 + LP 本金 3000 + 提出者 3000 = 9400`。✔
+罚没 5000 拆为提出者奖励 2000 + forfeit 3000(→ 经赢家池归 B)。
+
+## 实现状态(已与代码核对)
+
+**常规操作 —— 全部 21 个**存在于 `operation` variant(`operations.hpp`),在 `pm_evaluator.cpp` 校验 + 求值:
+`pm_oracle_register`、`pm_oracle_update`、`pm_create_market`、`pm_oracle_accept_market`、`pm_place_bet`、
+`pm_commit_bet`、`pm_reveal_bet`、`pm_cancel_bet`、`pm_add_liquidity`、`pm_withdraw_liquidity`、
+`pm_resolve_market`、`pm_no_contest`、`pm_dispute_create`、`pm_dispute_vote`、`pm_dispute_resolve`、
+`pm_transfer_position`、`pm_lazy_deposit`、`pm_lazy_withdraw`、`pm_leverage_open`、`pm_leverage_close`、
+`pm_leverage_convert`。✔
+
+**虚拟操作** —— 由 `database::process_pm_markets()` / 求值器发出:
+
+| 虚拟操作 | 触发? | 触发点(代码) |
+|------------|--------|----------------|
+| `pm_market_accepted` | ✔ | `pm_oracle_accept_market` **以及**自预言机 `pm_create_market` |
+| `pm_payout` | ✔ | 结算时**每笔活跃下注**——携带 `account`、`market_id`、`bet_id`、`side`/`outcome_index`、`amount`(本金)、`payout`(**输则 0**) |
+| `pm_auto_payout` | ✔ | 结算时**每个市场一次**——汇总标记(`bets_sum`),与逐笔 `pm_payout` 并列 |
+| `pm_commit_forfeit` | ✔ | 超过 `reveal_deadline` 未揭示的承诺 |
+| `pm_dispute_finalize` | ✔ | 委员会 `voting_end_time` |
+| `pm_dispute_auto_close` | ✔ | `auto_close_time`(防冻结) |
+| `pm_oracle_missed_penalty` | ✔ | 预言机错过 `result_expiration` |
+| `pm_lazy_recall` | ✔ | 闲置分配的渐进式召回步 |
+| `pm_batch_settle` | ✔ | 纪元边界 |
+| `pm_leverage_liquidate` | ✔ | 盘中清算:reason **0** 对向下注、**1** 撤注(`cascade_liquidate`) |
+| `pm_leverage_resolve` | ✔ | 杠杆头寸的**结算**:携带 `market_id`、`outcome_index`、`won`、`pool_received`/`bettor_received`、`leverage`(= `total_bet/collateral`) |
+
+读取方法见[插件 API](../plugins/prediction-market-api)
+(`get_account_leverage_positions`、`get_market_leverage_positions`、`get_creator_ban`、`get_dispute_votes`、…);
+逐下注者结果(`pm_payout`)与杠杆结算(`pm_leverage_resolve`)亦见于 `account_history`。
+
+## 典型场景中的角色
+
+每个参与者都被贯穿市场 **M**(及杠杆子市场 **L**)来追踪:其交互图、它发送的**签名**操作、触及它的**虚拟**
+操作、两种结果的**发送 / 接收**账本,以及代码核对指引。每个按角色的账本都是上述两份主账本的切片。
+
+### 做市商(创建者 + 首个 LP)
+
+做市商创建市场 M,注入 **2000** 流动性(成为首个 `pm_liquidity_object`),并提出预言机的**报价上限**。它
+**不**裁定(那是预言机的事)。
+
+```mermaid
+flowchart LR
+ maker -->|pm_create_market| M[(pm_market_object status=0)]
+ maker -->|seed 2000| LP0[(pm_liquidity_object provider=maker)]
+ M -. fee 5 .-> DAO[(committee_fund)]
+ orac -->|pm_oracle_accept_market| M2[(M status=1)]
+ M2 -. VIRTUAL .-> VA[[pm_market_accepted]]
+ M2 ==>|pm_auto_payout| RET[principal 2000 + creator_fee + LP bonus]
+ RET --> maker
+```
+
+- **发送:** `pm_create_market`(将 `oracle_fee_percent`/`oracle_fixed_fee` 设为**报价上限**,加上自身的
+ `creator_fee_percent` 5% 与 `liquidity_fee_percent` 5%;支付 `pm_market_creation_fee` 5 → DAO,锁定
+ `liquidity` 2000);可选 `pm_add_liquidity` / `pm_withdraw_liquidity`(本金安全,从 `betting_expiration`
+ 到裁定锁定)。
+- **被触及:** `pm_market_accepted`(预言机接受,或自预言机在创建时);`pm_auto_payout`(返还 LP 本金 +
+ 按时间加权的 LP 奖励份额)。
+
+| 结果 | 发送 | 接收 | 净额 |
+|------|------|------|------|
+| 正常(A) | 2000 流动性 + 5 创建费(→DAO) | 2000 本金 + **creator_fee 10** + **LP 奖励 ~31** | **+36** |
+| 争议(→B) | 2000 + 5 | 2000 本金 + creator_fee 10 + LP 奖励 ~6 | **+11** |
+
+翻转时创建费仍从冻结的市场配置**照付**——争议惩罚的是**预言机**(保证金罚没),而非做市商。LP 本金无条件返还。
+
+- **自预言机:** `oracle == creator` → 创建即生效,`pm_market_accepted` 带 `self_oracle=true`,做市商另外
+ 赚取 `oracle_take`。
+- **核对:** `pm_create_market_evaluator`;LP 经 `settle_liquidity`;`committee_fund += pm_market_creation_fee`。
+ **观察:** `get_market`、`list_markets_by_creator`、`get_market_liquidity`(`earned_fee`)、`get_market_meta`。
+
+### 预言机(注册 → 接受-报价 → 裁定)
+
+外部预言机 **orac** 质押保证金,在接受时**报价**其费用(≤ 做市商报价且 ≤ `pm_max_oracle_fee_percent`)并裁定。
+其市场费从输家池支付;保证金仅在错过截止或争议败诉时承压。
+
+```mermaid
+flowchart LR
+ orac -->|pm_oracle_register insurance 5000| O[(pm_oracle_object)]
+ orac -. reg-fee 10 .-> DAO[(committee_fund)]
+ orac -->|pm_oracle_accept_market quote fee 10% + fixed 10| M[(M status=1)]
+ M -. VIRTUAL .-> VA[[pm_market_accepted]]
+ orac -->|pm_resolve_market A| M3[(M status=3)]
+ M3 ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|oracle_take 30| orac
+```
+
+- **发送:** `pm_oracle_register`(锁定保证金 5000,支付 reg-fee 10 → DAO,设定建议价目表);
+ `pm_oracle_accept_market`(**报价** fee 10% + fixed 10,各 ≤ 创建者报价且 ≤ 中位上限;冻结到 M);
+ `pm_resolve_market`(设 `winning_outcome`,开启宽限);可选 `pm_oracle_update` / `pm_no_contest`。常驻价目表
+ 亦可在创建时自动接受市场使其上线——见预言机操作文档。
+- **被触及:** `pm_market_accepted`;`pm_auto_payout`(计入 `oracle_take`);`pm_oracle_missed_penalty`
+ (从未裁定 → 罚没 `pm_oracle_penalty_percent` 保证金 → DAO,全额退还下注)。
+
+| 结果 | 发送 | 接收 | 净额 |
+|------|------|------|------|
+| 正常(A) | 保证金 5000(锁定)+ reg-fee 10(→DAO) | **oracle_take 30** = fee 20 + fixed 10 | **+30** |
+| 争议(→B) | 保证金 −**5000 罚没** | oracle_take 30 | **−4970** |
+
+即便翻转,预言机仍保留小额**市场费**(冻结配置);惩罚是**保证金罚没**,拆分为争议者奖励与赢家 `forfeit_pool`。
+报价**低于**报价允许(价格=声誉);**高于**则拒绝。
+
+- **核对:** `pm_oracle_register_evaluator`、`pm_oracle_accept_market_evaluator`(≤ 报价、≤ 上限、冻结)、
+ `process_pm_markets` 错过截止扫描。**观察:** `get_oracle`、`list_oracles`、`get_market`(冻结条款)。
+
+### 预言机 —— 争议中被维持(争议胜方)
+
+预言机裁定 **A**;争议者挑战但裁决**维持** A。预言机保留市场费**并**收取被没收的 `dispute_fee`;保证金不动,
+`disputes_won++`。
+
+```mermaid
+flowchart LR
+ orac -->|pm_resolve_market A| M[(market resolved A)]
+ disp -->|pm_dispute_create| D[(dispute)]
+ D ==>|uphold A| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|dispute_fee 1000| orac
+ FIN --> AUTO[[pm_auto_payout settles A]]
+ AUTO -->|oracle_take 30| orac
+```
+
+| 结果 | 发送 | 接收 | 净额 |
+|------|------|------|------|
+| 争议,维持 | 保证金 5000(**不**罚没) | oracle_take 30 + **dispute_fee 1000** | **+1030** |
+| 正常(无争议) | 保证金 5000(锁定) | oracle_take 30 | **+30** |
+
+挑战反噬争议者并**支付给预言机**。善意市场(`dispute_penalty_percent < 0`)甚至能在改变结果时给预言机费用
+奖励——承认诚实错误。**核对:** `pm_dispute_finalize` / `pm_dispute_resolve` 的 uphold 分支。**观察:**
+`get_oracle`(`disputes_won`)、`get_dispute`。
+
+### 预言机 —— 被翻转 + 罚没(争议败方)
+
+预言机裁定 **A**;争议**翻转为 B**,保证金被**罚没**。它仍收取微小的冻结市场费(费用与惩罚是两笔钱),但损失
+大部分保证金与声誉。
+
+```mermaid
+flowchart LR
+ orac -->|pm_resolve_market A| M[(resolved A)]
+ disp -->|pm_dispute_create proposed=B| D[(dispute)]
+ D ==>|overturn to B| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|slash 5000| INS[oracle.insurance ↓]
+ INS --> SPLIT[bonus 2000 → disputer 3000 → forfeit_pool → B]
+ FIN --> AUTO[[pm_auto_payout settles B]]
+ AUTO -->|oracle_take 30| orac
+```
+
+`slash = 保证金 5000 × dispute_penalty_percent (100%) × consensus_strength (100%) = 5000`,是**再分配**而非
+销毁:`bonus 2000 →` 争议者,`3000 → forfeit_pool →` 新赢家(B)。净 **−4970** 对比无争议的 **+30**。罚没随
+**共识强度**(`winning_rshares / max_rshares`)缩放;`dispute_penalty_percent < 0`(善意)→ **无**罚没。被
+罚没的预言机往往也被**封禁**(下一角色)。**核对:** `pm_dispute_finalize` / `pm_dispute_resolve` 的 overturn
+分支;费用仍取自 `mkt.oracle_fee_percent`。**观察:** `get_oracle`(`total_insurance_slashed`、`banned_until`)、
+`get_dispute`。
+
+### 被封禁的预言机(及被封禁的创建者)
+
+封禁是**状态**,非转账:`pm_oracle_object.banned_until`(创建者则是 `pm_creator_ban_object`)阻止该角色承接
+**新**市场直到时间戳过去。它通常伴随翻转罚没,但本身不移动代币。
+
+```mermaid
+flowchart LR
+ resolver -->|pm_dispute_resolve ban_oracle| O[(pm_oracle_object banned_until = T)]
+ orac -->|pm_create_market / accept| CHK{now < banned_until?}
+ CHK -->|yes| REJ[REJECTED: 'Oracle is banned']
+ CHK -->|no, expired| OK[allowed again]
+ resolver -->|ban_creator| CB[(pm_creator_ban_object)]
+ maker -->|pm_create_market| CHK2{banned?}
+ CHK2 -->|yes| REJ2[REJECTED: 'Creator is banned']
+```
+
+- **谁设定:** 账户模式 → `pm_dispute_resolve`(`ban_oracle`/`ban_creator` + `…_until`);委员会模式 →
+ `pm_dispute_finalize` 在翻转时按共识缩放封禁。`banned_until = time_point_sec::maximum()` ⇒ **永久**。
+- **代币:** 封禁本身为 **0**(纯状态);伴随的罚没是上面的翻转情形。保证金仍锁定,封禁解除且无活跃市场后可退。
+- 封禁在快照中存续并按账户为键——重新注册无法抹除。**核对:** `pm_create_market_evaluator`(`"Oracle is
+ banned"` / `"Creator is banned"`)。**观察:** `get_oracle`(`banned_until`、`bans_received`)、
+ **`get_creator_ban(account)`**。
+
+### 下注者 A —— 早期赢家
+
+**A** **早期在 A 侧下注 100**(无时间惩罚),M 裁定为 A 时获胜。赔付 = 本金 + 按权重比例的赢家池份额。
+
+```mermaid
+flowchart LR
+ A -->|pm_place_bet side=A 100| BET[(pm_bet_object weight 100)]
+ BET --> M[(market M reserves shift)]
+ M ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|payout 175| A
+```
+
+| 结果 | 发送 | 接收 | 净额 |
+|------|------|------|------|
+| 正常(A) | 100 | **175** | **+75** |
+| 争议(→B) | 100 | **0** | **−100** |
+
+`profit = winners_pool 150 × weight 100 / Σweight 200 = 75`;无惩罚 → 赔付 `100 + 75`。翻转使 A 成为**输**侧。
+赢利**仅**来自输家下注(+ forfeit 池),绝不来自增发。**发送:** `pm_place_bet`(`side=0`,instant);可选
+`pm_transfer_position` / `pm_cancel_bet`。**核对:** `pm_place_bet_evaluator`、`settle_market`。**观察:**
+`get_account_positions`(`expected_payout`)、`get_market_weight_sums`;已实现的 `pm_payout` 见 `account_history`。
+
+### 下注者 B —— 输家
+
+**B** **在 B 侧下注 200**。M 裁定为 **A** 时,B 的下注资助赢家,B 一无所获。在争议路径中 B 成为赢家。
+
+```mermaid
+flowchart LR
+ B -->|pm_place_bet side=B 200| BET[(pm_bet_object status active)]
+ BET --> M[(market M)]
+ M ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|status=resolved, payout 0| BET
+```
+
+| 结果 | 发送 | 接收 | 净额 |
+|------|------|------|------|
+| 正常(A) | 200 | **0** | **−200** |
+| 争议(→B) | 200 | **3350** | **+3150** |
+
+B 的 200 **就是** `losers_sum`(支付 40 费用 + 150 赢家池 + LP 奖励)——同注分彩的「输家资助赢家」规则。翻转时
+B 获胜,预言机 forfeit 3000 注入 B 池(`payout = 200 + 3150`)。输的下注仍由 `pm_payout` 以 **payout=0** 记录。
+**核对:** `settle_market` 输家分支。**观察:** `get_account_positions`、`get_market_bets`、`get_dispute`。
+
+### 下注者 C —— 迟到赢家(时间惩罚)
+
+**C** **在 A 侧下注 100** 但**迟**(下注窗口 T+85%),故**时间惩罚**仅扣其*利润*(非本金)。权重与 A 相同但
+所得更少;被扣部分流向 LP。
+
+```mermaid
+flowchart LR
+ C -->|pm_place_bet side=A 100 at T+85%| BET[(pm_bet_object weight 100 time_penalty 50%)]
+ BET --> M[(market M)]
+ M ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|payout 138| C
+ VP -. penalty 37 .-> LPb[LP bonus]
+```
+
+| 结果 | 发送 | 接收 | 净额 |
+|------|------|------|------|
+| 正常(A) | 100 | **138** | **+38** |
+| 争议(→B) | 100 | **0** | **−100** |
+
+`profit = 75`;`penalty = 75 × 50% = 37`(→ LP);`payout = 100 + 75 − 37 = 138`——同权重下比 A 的 +75 少
+**−37**。节点在下注时按市场惩罚曲线(`time_penalty_type/value`、`penalty_curve_type`)盖上 `time_penalty`。它
+遏制最后一秒抢狙并补贴流动性,而非协议。**核对:** `pm_place_bet_evaluator`(曲线求值)、`compute_settlement`。
+**观察:** `get_account_positions`(`time_penalty`)、`get_market_bets`。
+
+### 下注者 D —— 杠杆 ×10 赢家
+
+**D** 开 **×10** 仓:**10 抵押 + 90 贷款**(来自懒惰池)= 在 A 侧 **100**,于隔离杠杆市场 **L**
+(`pm_leverage_enabled=true`,`R = 10%`)。A 获胜时,D 在偿还贷款 + 利息后保留全部 100 的上行。
+`pool_profit = loan 90 × R 10% = 9`;`obligation = 90 × 1.10 = 99`。
+
+```mermaid
+flowchart LR
+ D -->|pm_leverage_open collateral 10 + loan 90| POS[(pm_leverage_position total_bet 100, obligation 99)]
+ POOL[(lazy pool)] -.loan 90.-> POS
+ POS --> L[(market L, side A)]
+ L ==>|settle: force_close at cancel_value| VR[[pm_leverage_resolve won=true, leverage=10]]
+ VR -->|min(cv,obligation) 99| POOL
+ VR -->|cv 200 − 99 = 101| D
+```
+
+| 结果 | 发送 | 接收 | 净额 |
+|------|------|------|------|
+| 正常(A) | 抵押 **10** | cancel_value 200 − obligation 99 = **101** | **+91** |
+| 争议(→B) | 抵押 10 | 0 | **−10** |
+
+盈利仓按其 `cancel_value` 平仓;池收回 `obligation 99`(贷款 90 + **9 利息**),D 在自有 10 上保留余下 →
+**+91**(池 **+9**)。杠杆按清算结算,**绝不**经 `pm_auto_payout`。零和(L):in `10 + 90 + 100 = 200`;out
+`101 + 99 = 200`。**发送:** `pm_leverage_open`;可选 `pm_leverage_close`(仅当 `cv ≥ obligation`)/
+`pm_leverage_convert`。**被触及:** `pm_leverage_resolve`(结算强制平仓,`reason=expiration`)。**核对:**
+`force_close_positions` → `liquidate_position(reason=2)`。**观察:** **`get_account_leverage_positions`** /
+**`get_market_leverage_positions`**、`get_lazy_pool`。
+
+### 下注者 E —— 杠杆 ×5 被清算
+
+**E** 开 **×5** 仓:**20 抵押 + 80 贷款** = 在 B 侧 **100**(市场 **L**,`R = 10%`)。裁定前一笔**对向下注**将
+曲线推向不利于 B;**级联清算**强制平仓。**E 失去抵押,但池始终被补足。** `obligation = 80 × 1.10 = 88`。
+
+```mermaid
+flowchart LR
+ E -->|pm_leverage_open collateral 20 + loan 80| POS[(pm_leverage_position obligation 88)]
+ POOL[(lazy pool)] -.loan 80.-> POS
+ X -->|pm_place_bet side=A| L[(market L)]
+ L ==>|cascade at PRE-bet reserves cv 88 ≤ threshold| VL[[pm_leverage_liquidate reason=opposing_bet]]
+ VL -->|pool_received 88 = loan 80 + profit 8| POOL
+ VL -->|bettor_received 0| E
+```
+
+E 在裁定**之前**被清算,故最终 A/B 结果(是否争议)不改变它:
+
+| 角色 | 发送 | 接收 | 净额 |
+|------|------|------|------|
+| **E** | 抵押 **20** | **0** | **−20** |
+| **池** | 贷款 80 | **88**(贷款 80 + R% 8) | **+8** |
+
+对向下注清算在**下注前**储备执行,此时 `cancel_value ≥ loan`,故 `pool_received = min(cv, obligation)` 至少返
+还贷款——池**绝不**亏损。
+
+> **唯一可能为负的路径**是同侧 **`pm_cancel_bet`(Case B)**:取消会撤回一笔*先前的大额*同侧下注(超过单笔滑点
+> 上限),为对取消者公平,它**先**按其提交价执行——故级联可能落到 `cancel_value < loan`:
+> `shortfall = obligation − cancel_value`,`lazy_pool.free_balance −= shortfall`。此**坏账**是**有界的**
+> (`≤ cancel_value_before × SL%`)且**罕见**(池在其余所有仓位上的 R% 足以抵消)。由
+> `leverage_cancel_bet_cascade_bad_debt` 测试覆盖。
+
+池保护是结构性的(`max_per_position`、`max_position_ratio`、`safety_margin`、滑点上限、`expiration_buffer`)。
+`pm_leverage_enabled=false` **仅**阻止**新**开仓——清算级联**不**受该标志门控,故治理永远无法在途中剥夺池的保护
+(`leverage_disabled_keeps_liquidation_protection`)。**核对:** `pm_place_bet` → `cascade_liquidate(reason=0)`;
+`pm_cancel_bet` → `cascade_liquidate(reason=1)`;`liquidate_position`。**观察:**
+**`get_account_leverage_positions`**(`status=1`、`pool_received`、`bettor_received`)、`get_lazy_pool`。
+
+### 市场内流动性提供者(LP1)
+
+**LP1** 向活跃 M 添加 **1000** 流动性(在做市商种子之后)。本金**始终**返还;之上赚取**按时间加权**的 LP 奖励
+份额(流动性费 + 时间惩罚 + 零头)。与懒惰池提供者不同,后者一次存入并被自动分配到多个市场。
+
+```mermaid
+flowchart LR
+ LP1 -->|pm_add_liquidity 1000| L1[(pm_liquidity_object provider=LP1)]
+ L1 --> M[(market M reserves)]
+ M ==>|settle| SL[[settle_liquidity]]
+ SL -->|principal 1000 + bonus ~16| LP1
+ LP1 -->|pm_withdraw_liquidity after resolution| OUT[principal-safe exit]
+```
+
+| 结果 | 发送 | 接收 | 净额 |
+|------|------|------|------|
+| 正常(A) | 1000 | **1000 本金 + ~16 奖励** | **+16** |
+| 争议(→B) | 1000 | 1000 本金 + ~4 奖励 | **+4** |
+
+LP 奖励池 = `liq_fee 10 + 时间惩罚 37 = 47`,按 `本金 × 在场秒数` 分配(早期做市商 ~31,较晚的 LP1 ~16)。
+**本金保证**是架构性的——种子在任何赢家获付之前返还;LP 只会放弃奖励,绝不损失本金。提取从
+`betting_expiration` 到裁定锁定。**核对:** `pm_add_liquidity_evaluator`(记录 `deposit_time`)、
+`settle_liquidity` → `distribute_lp`。**观察:** `get_market_liquidity`(`earned_fee`)、`get_market_weight_sums`。
+
+### 懒惰流动性池(系统对象)
+
+**单例** `pm_lazy_pool_object`——不是账户。存款人一次性注资;池**自动分配**一片给每个已接受市场作为静默 LP
+(`pm_liquidity_object`,`provider` 为空)、**资助杠杆贷款**并**回收**闲置分配。它赚取 LP 收益 + 杠杆利息,按
+MasterChef 方式记账(一个全局 `reward_per_share`,O(1)——见白皮书)。字段:`total_shares`、`free_balance`、
+`allocated_balance`、`earned_balance`、`reward_per_share`、`leverage_fund_used`。
+
+```mermaid
+flowchart TD
+ LZ1 -->|pm_lazy_deposit 1000| POOL[(pm_lazy_pool free 1000 / shares 1000)]
+ POOL ==>|on market accept alloc 20% = 200| ALLOC[(pm_lazy_allocation + pm_liquidity provider=∅)]
+ ALLOC -->|market settles| YLD[route_pool_lp_return principal 200 + yield 20]
+ YLD --> POOL
+ POOL -->|leverage loan 90| Dpos[D position]
+ Dpos -->|close/resolve: 90 + interest 9| POOL
+ POOL -. idle market .-> VR[[pm_lazy_recall]]
+ VR -->|step back to free| POOL
+```
+
+| 池资金流 | 效果 |
+|----------|------|
+| `pm_lazy_deposit` | `free_balance += amount`,铸造 shares |
+| 自动分配(接受时) | `free → allocated`(静默 LP) |
+| 市场结算 | `route_pool_lp_return`:本金 + 收益 → `free`;收益 → `earned` 与 `reward_per_share` |
+| 杠杆开仓(D/E) | `free −= loan`,`leverage_fund_used += loan` |
+| 杠杆平仓 / 结算 / 对向下注清算 | `min(cv, obligation) → free`;`cv ≥ loan` ⇒ **绝不亏损** |
+| cancel-bet 清算(仅 Case B) | 回收 `cv`,**可能 < loan** → 有界**坏账** |
+| `pm_lazy_recall`(闲置市场) | 闲置分配的一个 10% 步 → `free` |
+| `pm_lazy_withdraw` | 销毁 shares → 本金 + pending;紧急惩罚留在池中 |
+
+在典型场景中池净赚 **+37 earned**(市场 M 收益 +20,杠杆 D 利息 +9,杠杆 E 对向下注回收 +8)。作为市场 LP 其
+本金无条件返还;只有*奖励*收益随争议变化。
+
+> 池从单一 `free_balance` **同时**承担两种角色:市场 LP 分配(`maybe_allocate_lazy`)与杠杆贷款
+> (`leverage_fund_used` 限制后者)。所有杠杆参数都在 **`pm_leverage_open` 时**对当前中位值检查,故此后属性波动
+> 只影响*新*开仓,绝不影响已放出的贷款。
+
+池中 VIZ 为**流动**而非 vested → **无**验证人调度或 committee-request 权重。**例外(HF14):** 对 **PM 委员会
+争议**,存款人的池权益**被计入**——经 `get_vesting_share_price()` 转为 vesting-shares 并加入其 `pm_dispute_vote`
+权重(见下文委员会裁决者)。**核对:** `apply_hardfork(CHAIN_HARDFORK_14)`(单例)、`maybe_allocate_lazy`、
+`route_pool_lp_return`。**观察:** `get_lazy_pool`。
+
+### 懒惰池中的流动性提供者(LZ1)
+
+**LZ1** 一次性向池存入 **1000**,任其在市场 + 杠杆贷款间分散。它赚取池聚合收益的份额(`reward_per_share`),而
+非任一市场的结果。两种退出:**计划**(锁定后)与**紧急**(锁定前,对*利润*罚一笔)。
+
+```mermaid
+flowchart LR
+ LZ1 -->|pm_lazy_deposit 1000| DEP[(pm_lazy_deposit_object shares 1000, unlock=+7d)]
+ DEP --> POOL[(lazy pool)]
+ POOL -. yield accrues .-> RPS[reward_per_share ↑]
+ LZ1 -->|pm_lazy_withdraw| OUT{planned or emergency?}
+ OUT -->|planned, t≥unlock| P[principal 1000 + pending 29]
+ OUT -->|emergency, t|pm_dispute_create proposed=B escrow fee 1000| D[(pm_dispute_object status open)]
+ D --> VOTE{committee vote or account resolve}
+ VOTE ==>|overturn to B| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|fee 1000 + bonus 2000| disp
+ FIN -.slash 5000 from oracle.-> SPLIT[bonus 2000 → disp 3000 → forfeit_pool → winners]
+```
+
+| 结果 | 发送 | 接收 | 净额 |
+|------|------|------|------|
+| 争议,翻转(「胜」) | dispute_fee **1000** | fee 1000 退回 + **bonus 2000** | **+2000** |
+
+`reward_target = fee × pm_dispute_reward_multiplier (3×) = 3000` → `bonus = 3000 − 1000 = 2000`,**以实际罚没
+为上限**;余下(3000)→ `forfeit_pool` → 新赢家。disp 冒险 1000,最终 **+2000**。(委员会模式:disp **不**为自己
+投票——由 SHARES 选民投。)**核对:** `pm_dispute_create_evaluator`、`pm_dispute_finalize`/`pm_dispute_resolve`
+的 overturn 分支。**观察:** `get_dispute`、`get_dispute_votes`。
+
+### 争议者 —— 没收费用(预言机被维持)
+
+**disp** 争议预言机的 **A**,但裁决**维持预言机**。托管费用**没收给预言机**作为补偿,市场按原裁定结算(A 胜)。
+
+```mermaid
+flowchart LR
+ disp -->|pm_dispute_create proposed=B escrow fee 1000| D[(pm_dispute_object)]
+ D --> VOTE{committee vote or account resolve}
+ VOTE ==>|uphold oracle A| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|dispute_fee 1000| orac[oracle compensation]
+ FIN -->|market settles as A| AUTO[[pm_auto_payout]]
+```
+
+| 结果 | 发送 | 接收 | 净额 |
+|------|------|------|------|
+| 争议,维持(「负」) | dispute_fee **1000** | **0** | **−1000** |
+
+费用是争议者的利益绑定:错误/轻率的争议向预言机付费。这种不对称(错则失费、对则赢数倍)保持渠道诚实。一个始终
+**未决**的争议(预言机沉默 / 无法定人数)被强制关闭并**退还**费用(净 0)——见下文争议自动关闭,与按实质败诉不同。
+**核对:** `pm_dispute_finalize`/`pm_dispute_resolve` 的 uphold 分支。**观察:** `get_dispute`、`get_oracle`
+(获得费用,`disputes_won++`)。
+
+### 裁决者 —— 委员会(按权益加权,dispute_mode = 0)
+
+*整个 SHARES 选民*以**按权益加权投票**裁决;无单一裁决者账户。裁决在 `voting_end_time` 由 `pm_dispute_finalize`
+确定性计票。
+
+**投票权重** = 实时 **`effective_vesting_shares`**(`vesting − delegated + received`)**加上转为 vesting-shares
+的懒惰池权益**,因为许多 DAO 成员把 VIZ 停在池里(在那里它是流动的):
+
+```
+pool_claim_viz = pool_NAV × deposit.shares / pool.total_shares
+pool_weight = pool_claim_viz × get_vesting_share_price()
+voter_weight = effective_vesting_shares + pool_weight
+```
+
+参与法定人数分母同样是 `total_vesting_shares + (pool_NAV → vesting-shares)`。7 天存款锁防止存款-投票-提取博弈。
+
+```mermaid
+flowchart LR
+ V1[voter · eff_vshares] -->|pm_dispute_vote outcome,percent| D[(pm_dispute_votes)]
+ V2[voter · eff_vshares] -->|pm_dispute_vote| D
+ D ==>|voting_end_time| FIN[[pm_dispute_finalize]]
+ FIN -->|argmax rshares, threshold check| VERDICT{uphold / overturn}
+ VERDICT -->|consensus_strength scales slash & bans| OUT[settle]
+```
+
+- **发送(投票者):** `pm_dispute_vote` —— **auth `regular`**。`vote_outcome = -1` 维持,否则提议正确结果;
+ `vote_percent ∈ [-10000, 10000]`。投票者在投票开放期间可**任意次修改**选票——重复投票**覆盖**先前的(最新者
+ 胜,无「Already voted」)。
+- **无 commit-reveal —— 刻意,且不会改变。** 委员会争议是**公开听证**:实时计票可见(`get_dispute_votes`),票
+ 不隐藏。DAO 的价值在于尽可能真实透明地裁决争议;投票中会浮现新论据,投票者*被期望*更新;且投票者**不因**与多数
+ 一致而**获酬**,故 commit-reveal 通常的反羊群(选美博弈)理由在此不适用。
+
+| 角色 | 发送 | 接收 |
+|------|------|------|
+| 每位投票者 | 0 | **0** —— 投票是治理职责,非有偿行为 |
+
+投票者从不收代币;影响力是纯权益权重。经济流向按上文争议主账本归于争议者、预言机与下注者。小众市场可能不达门槛 →
+落入下文争议自动关闭。**核对:** `pm_dispute_vote_evaluator`(在 `by_market_voter` 上 modify-or-create);
+`pm_dispute_finalize`(`lazy_vote_weight`、`get_vesting_share_price`、法定人数、argmax、`consensus_strength`)。
+**观察:** `get_dispute_votes`(实时计票 + finalize 投影:`quorum_percent_bp`、`expected_uphold`、
+`expected_outcome`、`expected_consensus_strength_bp`)。测试:`committee_dispute_lazy_pool_voting_weight`、
+`committee_dispute_flips_outcome`。
+
+### 裁决者 —— 单账户(中心化,dispute_mode = 1)
+
+市场指名一个 `dispute_resolver` 账户(如监管多签)独自裁决——**无权益权重、无 DAO 投票**。在创建时设定,须与
+`oracle` 和 `creator` 都不同(防自裁)。操作集与委员会模式相同;只是*由谁裁决*不同。
+
+```mermaid
+flowchart LR
+ disp -->|pm_dispute_create| D[(dispute, mode=1)]
+ resolver -->|pm_dispute_resolve correct_outcome=B penalty_amount, ban flags| FIN[[verdict]]
+ FIN -->|slash penalty_amount| orac[oracle.insurance ↓]
+ FIN -->|fee + reward| disp
+ FIN --> AUTO[[pm_auto_payout settles B]]
+```
+
+- **发送:** `pm_dispute_resolve` —— **仅指名 `dispute_resolver` 的 `active` auth**:`correct_outcome`、
+ `penalty_amount`(待罚没保证金——固定额,**不**按权益缩放)、`ban_oracle`/`ban_creator`(+ `…_until`)。
+
+| 角色 | 发送 | 接收 |
+|------|------|------|
+| 裁决者 | 0 | **0** —— 中立仲裁者 |
+
+裁决后的规范与委员会模式相同;只是罚没规模不同(裁决者设定的 `penalty_amount`,无 `consensus_strength` 缩放,
+因只有一个决策者)。裁决者的 KYC/白名单是**客户端层**事务。**核对:** `pm_dispute_resolve_evaluator`(仅指名裁
+决者,`dispute_mode==1`)。**观察:** `get_dispute`、`get_oracle`、**`get_creator_ban(account)`**。
+
+### 争议被强制结束(防冻结自动关闭)
+
+一个始终**未决**的争议——预言机沉默且(委员会)无法定人数——不能永远冻结市场。在 `auto_close_time`,
+`pm_dispute_auto_close` 处理器强制结束它:**全员退款**,争议者费用**退还**,无响应的预言机受罚。不选赢家。
+
+```mermaid
+flowchart LR
+ disp -->|pm_dispute_create escrow fee 1000| D[(dispute, status open)]
+ D -. oracle silent / no quorum .-> WAIT[auto_close_time reached]
+ WAIT ==>|VIRTUAL| AC[[pm_dispute_auto_close]]
+ AC -->|refund all bets| bettors
+ AC -->|fee 1000 back| disp
+ AC -->|insurance slash → DAO| orac
+```
+
+| 角色 | 发送 | 接收 | 净额 |
+|------|------|------|------|
+| A / B / C | 下注 | 全额退款 | **0** |
+| maker / LP1 | 流动性 | 本金退回 | **0**(无奖励) |
+| disp | dispute_fee 1000 | **退回 1000** | **0** |
+| orac | 保证金 −罚没 → DAO | — | **− 罚没** |
+
+这**不是**「争议者输了」:退回的费用(净 0)不同于被没收的费用(争议者输家,净 −1000)。无人获利;市场作废以打破
+冻结,代价落在未响应的预言机上。同样的作废-退款形态也覆盖 `pm_oracle_missed_penalty` 与 `pm_no_contest`。调校
+`pm_dispute_auto_close_sec`(14 天)对 `pm_dispute_vote_period_sec`(3 天),使诚实争议先行解决。**核对:**
+`process_pm_markets` 自动关闭扫描(`refund_all_bets` + `return_liquidity` + 费用计入;`disputes_auto_closed++`)。
+**观察:** `get_dispute`(状态 → 自动关闭)、`get_market`、`get_oracle`。
diff --git a/@l10n/zh-CN/docs/protocol/operations/overview.md b/@l10n/zh-CN/docs/protocol/operations/overview.md
index 602109cc12..31dcf15306 100644
--- a/@l10n/zh-CN/docs/protocol/operations/overview.md
+++ b/@l10n/zh-CN/docs/protocol/operations/overview.md
@@ -52,6 +52,32 @@ VIZ Ledger 操作是包含在交易中的原子状态变更动作。每个操作
| 58 | `use_invite_balance_operation` | active | [邀请](./invites.md) |
| 60 | `fixed_award_operation` | regular | [奖励](./awards.md) |
| 61 | `target_account_sale_operation` | master | [账户市场](./account-market.md) |
+| 64 | `set_reward_sharing_operation` | active | [验证者](./validators.md) |
+| 66 | `pm_oracle_register_operation` | active | [预测市场](./prediction-markets.md) |
+| 67 | `pm_oracle_update_operation` | active | [预测市场](./prediction-markets.md) |
+| 68 | `pm_create_market_operation` | active | [预测市场](./prediction-markets.md) |
+| 69 | `pm_oracle_accept_market_operation` | active | [预测市场](./prediction-markets.md) |
+| 70 | `pm_place_bet_operation` | active | [预测市场](./prediction-markets.md) |
+| 71 | `pm_commit_bet_operation` | active | [预测市场](./prediction-markets.md) |
+| 72 | `pm_reveal_bet_operation` | active | [预测市场](./prediction-markets.md) |
+| 73 | `pm_cancel_bet_operation` | active | [预测市场](./prediction-markets.md) |
+| 74 | `pm_add_liquidity_operation` | active | [预测市场](./prediction-markets.md) |
+| 75 | `pm_withdraw_liquidity_operation` | active | [预测市场](./prediction-markets.md) |
+| 76 | `pm_resolve_market_operation` | active | [预测市场](./prediction-markets.md) |
+| 77 | `pm_no_contest_operation` | active | [预测市场](./prediction-markets.md) |
+| 78 | `pm_dispute_create_operation` | active | [预测市场](./prediction-markets.md) |
+| 79 | `pm_dispute_vote_operation` | regular | [预测市场](./prediction-markets.md) |
+| 80 | `pm_dispute_resolve_operation` | active | [预测市场](./prediction-markets.md) |
+| 81 | `pm_transfer_position_operation` | active | [预测市场](./prediction-markets.md) |
+| 82 | `pm_lazy_deposit_operation` | active | [预测市场](./prediction-markets.md) |
+| 83 | `pm_lazy_withdraw_operation` | active | [预测市场](./prediction-markets.md) |
+| 91 | `pm_leverage_open_operation` | active | [预测市场](./prediction-markets.md) |
+| 92 | `pm_leverage_close_operation` | active | [预测市场](./prediction-markets.md) |
+| 93 | `pm_leverage_convert_operation` | active | [预测市场](./prediction-markets.md) |
+| 98 | `pm_dispute_oracle_respond_operation` | active | [预测市场](./prediction-markets.md) |
+| 99 | `pm_unban_operation` | active | [预测市场](./prediction-markets.md) |
+
+> ID 是链上单一 `operation` 变体中的固定索引(仅追加)。本表中的空缺为按 ID 交错的**虚拟**操作(见下文)—— 例如 62–63、65、84–90、94–97、100。
---
@@ -83,6 +109,19 @@ VIZ Ledger 操作是包含在交易中的原子状态变更动作。每个操作
| 59 | `expire_escrow_ratification_operation` | 托管截止日期错过 | [虚拟操作](../virtual-operations.md) |
| 62 | `bid_operation` | 拍卖出价 | [虚拟操作](../virtual-operations.md) |
| 63 | `outbid_operation` | 拍卖被超价 | [虚拟操作](../virtual-operations.md) |
+| 65 | `stakeholder_reward_operation` | 向利益相关者的收益分成结算 | [验证者](./validators.md) |
+| 84 | `pm_batch_settle_operation` | 批量纪元边界结算 | [预测市场](./prediction-markets.md) |
+| 85 | `pm_commit_forfeit_operation` | 提交-揭示托管被没收(未揭示) | [预测市场](./prediction-markets.md) |
+| 86 | `pm_auto_payout_operation` | 市场结算(按市场赔付标记) | [预测市场](./prediction-markets.md) |
+| 87 | `pm_dispute_finalize_operation` | 委员会投票计票完成 | [预测市场](./prediction-markets.md) |
+| 88 | `pm_dispute_auto_close_operation` | 争议防冻结自动关闭 | [预测市场](./prediction-markets.md) |
+| 89 | `pm_oracle_missed_penalty_operation` | 预言机错过裁定截止 | [预测市场](./prediction-markets.md) |
+| 90 | `pm_lazy_recall_operation` | 懒惰池渐进式召回步 | [预测市场](./prediction-markets.md) |
+| 94 | `pm_leverage_liquidate_operation` | 杠杆头寸被清算 | [预测市场](./prediction-markets.md) |
+| 95 | `pm_leverage_resolve_operation` | 杠杆头寸在裁定时结算 | [预测市场](./prediction-markets.md) |
+| 96 | `pm_market_accepted_operation` | 市场上线(预言机接受 / 自预言机 / 自动) | [预测市场](./prediction-markets.md) |
+| 97 | `pm_payout_operation` | 每下注者的同注分彩赔付 | [预测市场](./prediction-markets.md) |
+| 100 | `pm_ban_expired_operation` | 临时预言机/创建者封禁失效 | [预测市场](./prediction-markets.md) |
---
diff --git a/@l10n/zh-CN/docs/protocol/operations/prediction-markets.md b/@l10n/zh-CN/docs/protocol/operations/prediction-markets.md
index 25501781d3..03ec881778 100644
--- a/@l10n/zh-CN/docs/protocol/operations/prediction-markets.md
+++ b/@l10n/zh-CN/docs/protocol/operations/prediction-markets.md
@@ -106,6 +106,8 @@ flowchart TD
预言机接受(`status → active`)或拒绝(流动性退还创建者;`status → deleted`)一个待定市场。接受时预言机通过 `oracle_fee_percent` + `oracle_fixed_fee` **报出其实际条款**——各须 `≤` 该市场创建者的报价,且 `oracle_fee_percent ≤ pm_max_oracle_fee_percent`。报价被**冻结入市场**并发出 `pm_market_accepted` 虚拟操作(使历史解析器看到上线 + 条款)。此后结算只读这些冻结字段——绝不读实时中位值。
+预言机须在创建后的 `pm_oracle_accept_window_sec`(默认 1 小时)内行动。若到市场的 `accept_deadline` 仍未接受或拒绝,逐块 cron 将作废市场(`status → deleted`),向创建者退还种子流动性(**不**含不可退还的创建费),并发出 `pm_market_expired`(见虚拟操作)。
+
| 字段 | 类型 | 说明 |
|------|------|------|
| `market_id` | `int64` | 待定市场 |
@@ -155,7 +157,14 @@ flowchart TD
### `pm_resolve_market_operation`(ID 76)
**Auth:** `oracle` 的 `active`
-预言机裁定为 `winning_outcome`。开启争议宽限窗口(`result_expiration + pm_dispute_grace_sec`);其过后由 `pm_auto_payout` 结算。
+预言机裁定为 `winning_outcome`。开启争议宽限窗口(`result_expiration + pm_dispute_grace_sec`);其过后由 `pm_auto_payout` 结算。预言机的裁定声明**存储在市场上**(如同预言机的 `rules_url`),因此客户端可直接通过 `get_market` 读取,无需扫描历史。
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `market_id` | `int64` | 目标市场 |
+| `winning_outcome` | `int16_t` | 获胜结果索引 |
+| `decision_url` | `string` | 证据链接,`≤ MAX_PM_DECISION_URL_LEN`;存储在市场上 |
+| `decision_reason` | `string` | 自由文本理由,`≤ MAX_PM_DISPUTE_REASON_LEN`;存储在市场上(`decision_reason`) |
### `pm_no_contest_operation`(ID 77)
**Auth:** `oracle` 的 `active`
@@ -177,7 +186,39 @@ flowchart TD
### `pm_dispute_resolve_operation`(ID 80)
**Auth:** `resolver` 的 `active`
-由市场配置的 `dispute_resolver` 做出账户模式裁决。可罚没 `penalty_amount` 的保证金,并将预言机/创建者封禁至给定时间。
+由市场配置的 `dispute_resolver` 做出账户模式裁决。可罚没 `penalty_amount` 的保证金,并将预言机/创建者封禁至给定时间(`ban_*_until = time_point_sec::maximum()` = 永久)。
+
+> **封禁是一项合规/监管功能,仅限账户模式。** 当市场将其争议路由到账户模式的 `dispute_resolver`(例如监管机构或持牌仲裁者)时,该解析者可在同一裁决中对**预言机与市场创建者双方**施加处罚——临时或永久——在保证金罚没之外:这让担任解析者的监管机构得以将恶意预言机或屡犯的创建者逐出平台。**委员会/DAO 模式(`dispute_mode == 0`)按设计没有封禁权**——它是透明的公开听证,只罚没保证金并调整声誉(`pm_dispute_finalize`),绝不封禁。此处设定的封禁会将发起的 `resolver` 记入目标的 `banned_by`,因此只有该解析者可通过 `pm_unban` 提前解除;否则封禁在 `banned_until` 时失效(cron 发出 `pm_ban_expired`)。
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `market_id` | `int64` | 争议市场 |
+| `correct_outcome` | `int16_t` | 最终正确结果(`-1` = 作废/无争议) |
+| `penalty_amount` | `asset`(VIZ) | 罚没的预言机保证金 |
+| `ban_oracle` / `ban_oracle_until` | `bool` / `time_point_sec` | 将预言机封禁至给定时间 |
+| `ban_creator` / `ban_creator_until` | `bool` / `time_point_sec` | 将创建者封禁至给定时间(禁止创建市场) |
+
+### `pm_dispute_oracle_respond_operation`(ID 98)
+**Auth:** `oracle` 的 `active`
+
+市场的预言机在开放的争议上发布**公开反驳**。由于争议是公开听证,文本存储在争议对象上(`oracle_response` / `oracle_response_time`,可通过 `get_dispute` 读取),以便每位投票者/解析者权衡。仅在争议开放且 `now ≤ oracle_response_deadline` 时允许;重新发布会覆盖先前响应。
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `market_id` | `int64` | 争议市场 |
+| `response` | `string` | 反驳文本,非空,`≤ MAX_PM_DISPUTE_REASON_LEN` |
+
+### `pm_unban_operation`(ID 99)
+**Auth:** `resolver` 的 `active`
+
+**提前**解除由账户模式 `pm_dispute_resolve` 施加的封禁。仅目标的 `banned_by` 中记录的账户(施加封禁的解析者)可解除;`unban_oracle` / `unban_creator` 中至少须设定一个,且对应封禁当前须处于活跃状态。将 `banned_until` 设为过去时间并清空 `banned_by`。(未在此解除的封禁只会在 `banned_until` 时到期——届时 cron 发出 `pm_ban_expired`。)
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `resolver` | `account_name_type` | 施加封禁的账户(须等于目标的 `banned_by`) |
+| `target` | `account_name_type` | 被封禁的预言机 / 创建者 |
+| `unban_oracle` | `bool` | 清除预言机封禁(`pm_oracle_object.banned_until`) |
+| `unban_creator` | `bool` | 清除创建者封禁(`pm_creator_ban_object.banned_until`) |
### `pm_transfer_position_operation`(ID 81)
**Auth:** `from` 的 `active`
@@ -213,6 +254,12 @@ flowchart TD
| 95 | `pm_leverage_resolve_operation` | 结算——杠杆头寸被强制关闭:`outcome_index`、`won`、`pool_received`/`bettor_received`、`leverage` |
| 96 | `pm_market_accepted_operation` | 求值器——市场上线:预言机接受、自预言机或自动接受;冻结条款 + `self_oracle` 标志 |
| 97 | `pm_payout_operation` | 结算——每个有效下注:`amount`(本金)、`side`/`outcome_index`、`payout`(**输则为 0**) |
+| 100 | `pm_ban_expired_operation` | 临时的预言机/创建者封禁在 `banned_until` 时失效:cron 将其清除(字段 `account`、`oracle`、`creator`)。提前手动解除改用已签名的 `pm_unban` |
+| 101 | `pm_market_expired_operation` | 待定市场的 `accept_deadline` 到期:预言机未在 `pm_oracle_accept_window_sec` 内接受/拒绝——市场作废,种子退还(创建费保留)。字段 `oracle`、`creator`、`market_id`、`refunded_liquidity` |
+
+> ID 91–93 是*常规*操作 `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert`(见规范);
+> ID 98–99 是*常规*操作 `pm_dispute_oracle_respond`/`pm_unban`(见上)。每位下注者的结果是
+> `pm_payout`;每市场的 `pm_auto_payout` 仍为结算标记。
---
diff --git a/@l10n/zh-CN/docs/protocol/operations/validators.md b/@l10n/zh-CN/docs/protocol/operations/validators.md
index 7c83b51228..d7eb913e1d 100644
--- a/@l10n/zh-CN/docs/protocol/operations/validators.md
+++ b/@l10n/zh-CN/docs/protocol/operations/validators.md
@@ -163,4 +163,39 @@
---
+## `set_reward_sharing_operation`(ID 64)
+
+**授权:** `owner` 的 `active`
+
+**HF13 验证者收益分成。** 验证者选择将其区块奖励的一部分转发给其**利益相关者**——即为其投票的账户——按时间加权的投票权重比例分配。`sharing_rate` 为该比例,以基点表示;分成池累积后在每个纪元结束时通过 `stakeholder_reward` 虚拟操作分配。
+
+| 字段 | 类型 | 描述 |
+|------|------|------|
+| `owner` | `account_name_type` | 设置分成比例的验证者 |
+| `sharing_rate` | `uint16_t` | 转发给利益相关者的区块奖励比例,以基点表示(0 = 无,10000 = 100%) |
+
+```json
+[64, {
+ "owner": "alice",
+ "sharing_rate": 2500
+}]
+```
+
+- `sharing_rate` 上限为 10000(100%)。
+- 分配按**时间加权**的投票权重进行,因此新添加的投票在成熟前获得较小的份额。
+
+---
+
+## `stakeholder_reward_operation`(ID 65)— 虚拟
+
+在每个分配纪元,当 `sharing_rate` 非零的验证者向利益相关者支付其应得的分成区块奖励时发出。虚拟操作(从不签名);出现在 `account_history` 中。
+
+| 字段 | 类型 | 描述 |
+|------|------|------|
+| `validator` | `account_name_type` | 分享奖励的验证者 |
+| `stakeholder` | `account_name_type` | 接收份额的投票者 |
+| `shares` | `asset`(SHARES) | 记入利益相关者的金额 |
+
+---
+
参见:[数据类型](../data-types.md)、[操作概述](./overview.md)、[链属性](../../governance/chain-properties.md)。
diff --git a/@l10n/zh-CN/docs/protocol/virtual-operations.md b/@l10n/zh-CN/docs/protocol/virtual-operations.md
index 843380b33d..9c22fd7ff3 100644
--- a/@l10n/zh-CN/docs/protocol/virtual-operations.md
+++ b/@l10n/zh-CN/docs/protocol/virtual-operations.md
@@ -351,4 +351,32 @@
---
+## 预测市场 (HF14)
+
+由 PM 共识逻辑发出 —— **并非**按时钟的 cron。两个来源:
+- **已签名操作的求值器**,在其应用的瞬间 —— `pm_market_accepted`(接受 / 自预言机 / 自动接受)与 `pm_leverage_liquidate`(对向或取消下注将杠杆头寸推过阈值时)。
+- **截止处理器 `process_pm_markets()`**,每块运行:结算已达到**到期 / 截止 / 争议宽限结束 / 纪元边界**的市场(上限 `pm_processing_cap_per_block`,最早截止优先)。
+
+参见 [预测市场操作](./operations/prediction-markets.md)。(ID 91–93 是*常规*操作 `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert`,ID 98–99 是*常规*操作 `pm_dispute_oracle_respond`/`pm_unban` —— 均见该页。)
+
+| ID | 操作 | 触发 |
+|----|------|------|
+| 84 | `pm_batch_settle_operation` | 到达纪元边界:排队下注按纪元开盘快照执行 |
+| 85 | `pm_commit_forfeit_operation` | `reveal_deadline` 未揭示:罚金 → `forfeit_pool`,其余退还 |
+| 86 | `pm_auto_payout_operation` | 争议宽限到期(按市场):同注分彩结算 + 返还 LP 本金 |
+| 87 | `pm_dispute_finalize_operation` | 到达 `voting_end_time`:计票裁定;预言机罚金;重判/维持 |
+| 88 | `pm_dispute_auto_close_operation` | 到达 `auto_close_time` 且预言机未响应:防冻结退款,罚没保证金 → DAO |
+| 89 | `pm_oracle_missed_penalty_operation` | `result_expiration` 已过仍未裁决:罚没 → DAO,全额退还下注 |
+| 90 | `pm_lazy_recall_operation` | 闲置的懒惰池分配到达回收步:一个分阶段步返还入池 |
+| 94 | `pm_leverage_liquidate_operation` | 求值器 —— 市场进行中的杠杆清算(对向 `0` / 取消 `1` 下注级联) |
+| 95 | `pm_leverage_resolve_operation` | 结算 —— 杠杆头寸按 `cancel_value` 强制关闭:`outcome_index`、`won`、`pool_received`/`bettor_received`、`leverage` |
+| 96 | `pm_market_accepted_operation` | 求值器 —— 市场上线:预言机接受、自预言机或自动接受;冻结条款 + `self_oracle` 标志 |
+| 97 | `pm_payout_operation` | 结算 —— 每个有效下注:`amount`(本金)、`side`/`outcome_index`、`payout`(**输则为 0**);与按市场的 `pm_auto_payout` 并列 |
+| 100 | `pm_ban_expired_operation` | 临时预言机/创建者封禁在 `banned_until` 失效:cron 将其清除(`account`、`oracle`、`creator`)。提前手动解除改用已签名的 `pm_unban` |
+| 101 | `pm_market_expired_operation` | 待定市场的 `accept_deadline` 到期:预言机未在 `pm_oracle_accept_window_sec` 内接受/拒绝——市场作废(`status -1`),种子流动性退还(`refunded_liquidity`),创建费**不**退还(`oracle`、`creator`、`market_id`、`refunded_liquidity`) |
+
+所有 PM 资金流动严格零和(无增发);结算守恒 `Σ out == Σ 下注 + LP 本金 + forfeit_pool`。
+
+---
+
参见:[操作概述](./operations/overview.md)、[奖励](./operations/awards.md)、[委员会](./operations/committee.md)。
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 82aa5bef6e..b0df9d552e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -209,6 +209,7 @@ add_subdirectory(programs)
if(BUILD_CONSENSUS_TESTS)
enable_testing()
add_subdirectory(tests/consensus_sim)
+ add_subdirectory(tests/pm)
endif()
if(ENABLE_INSTALLER)
diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index 150f3f181d..211e61e450 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -1,4 +1,5 @@
import { defineConfig, type DefaultTheme } from 'vitepress';
+import { withMermaid } from 'vitepress-plugin-mermaid';
const githubRepo = 'https://github.com/VIZ-Blockchain/viz-cpp-node';
@@ -51,6 +52,15 @@ interface SidebarLabels {
awards: string;
subscriptions: string;
accountMarket: string;
+ predictionMarkets: string;
+ predictionMarketApi: string;
+ predictionMarketsSection: string;
+ onixOverview: string;
+ onixWhitepaper: string;
+ onixSpecification: string;
+ pmHub: string;
+ pmWorkflows: string;
+ pmConcepts: string;
proposals: string;
storage: string;
sharedMemory: string;
@@ -123,6 +133,15 @@ const en: SidebarLabels = {
awards: 'Awards',
subscriptions: 'Subscriptions',
accountMarket: 'Account Market',
+ predictionMarkets: 'Prediction Markets',
+ predictionMarketApi: 'Prediction Market API',
+ predictionMarketsSection: 'Prediction Markets (Onix)',
+ onixOverview: 'Onix Overview',
+ onixWhitepaper: 'Whitepaper',
+ onixSpecification: 'Specification',
+ pmHub: 'Overview & map',
+ pmWorkflows: 'Workflows & diagrams',
+ pmConcepts: 'Concept analysis',
proposals: 'Proposals',
storage: 'Storage',
sharedMemory: 'Shared Memory',
@@ -195,6 +214,15 @@ const ru: SidebarLabels = {
awards: 'Награды',
subscriptions: 'Подписки',
accountMarket: 'Рынок аккаунтов',
+ predictionMarkets: 'Прогнозные рынки',
+ predictionMarketApi: 'API прогнозных рынков',
+ predictionMarketsSection: 'Прогнозные рынки (Onix)',
+ onixOverview: 'Обзор Onix',
+ onixWhitepaper: 'Whitepaper',
+ onixSpecification: 'Спецификация',
+ pmHub: 'Обзор и карта',
+ pmWorkflows: 'Воркфлоу и диаграммы',
+ pmConcepts: 'Анализ концептов',
proposals: 'Предложения',
storage: 'Хранилище',
sharedMemory: 'Разделяемая память',
@@ -267,6 +295,15 @@ const zhCN: SidebarLabels = {
awards: '奖励',
subscriptions: '订阅',
accountMarket: '账户市场',
+ predictionMarkets: '预测市场',
+ predictionMarketApi: '预测市场 API',
+ predictionMarketsSection: '预测市场(Onix)',
+ onixOverview: 'Onix 概览',
+ onixWhitepaper: '白皮书',
+ onixSpecification: '规范',
+ pmHub: '总览与地图',
+ pmWorkflows: '工作流与图',
+ pmConcepts: '概念分析',
proposals: '提案',
storage: '存储',
sharedMemory: '共享内存',
@@ -305,6 +342,20 @@ function buildSidebar(t: SidebarLabels, prefix: string): DefaultTheme.SidebarIte
{ text: t.keyConcepts, link: p('/introduction/key-concepts') },
],
},
+ {
+ text: t.predictionMarketsSection,
+ items: [
+ { text: t.pmHub, link: p('/prediction-markets/') },
+ { text: t.onixOverview, link: p('/prediction-markets/onix') },
+ { text: t.onixWhitepaper, link: p('/prediction-markets/whitepaper') },
+ { text: t.onixSpecification, link: p('/prediction-markets/specification') },
+ { text: t.predictionMarkets, link: p('/protocol/operations/prediction-markets') },
+ { text: t.virtualOperations, link: p('/protocol/virtual-operations') },
+ { text: t.predictionMarketApi, link: p('/plugins/prediction-market-api') },
+ { text: t.pmWorkflows, link: p('/prediction-markets/workflows') },
+ { text: t.pmConcepts, link: p('/prediction-markets/concepts-analysis') },
+ ],
+ },
{
text: t.runANode,
items: [
@@ -346,6 +397,7 @@ function buildSidebar(t: SidebarLabels, prefix: string): DefaultTheme.SidebarIte
{ text: t.snapshot, link: p('/plugins/snapshot') },
{ text: t.chain, link: p('/plugins/chain') },
{ text: t.databaseApi, link: p('/plugins/database-api') },
+ { text: t.predictionMarketApi, link: p('/plugins/prediction-market-api') },
{ text: t.webserver, link: p('/plugins/webserver') },
],
},
@@ -370,6 +422,7 @@ function buildSidebar(t: SidebarLabels, prefix: string): DefaultTheme.SidebarIte
{ text: t.awards, link: p('/protocol/operations/awards') },
{ text: t.subscriptions, link: p('/protocol/operations/subscriptions') },
{ text: t.accountMarket, link: p('/protocol/operations/account-market') },
+ { text: t.predictionMarkets, link: p('/protocol/operations/prediction-markets') },
{ text: t.proposals, link: p('/protocol/operations/proposals') },
],
},
@@ -432,6 +485,7 @@ function localizedNav(
const p = (path: string) => `${prefix}${path}`;
return [
{ text: introLabel, link: p('/introduction/what-is-viz') },
+ { text: 'Onix', link: p('/prediction-markets/') },
{ text: nodeLabel, link: p('/node/getting-started') },
{ text: protocolLabel, link: p('/protocol/data-types') },
{ text: apiLabel, link: p('/api/json-rpc') },
@@ -441,7 +495,7 @@ function localizedNav(
// ─── Config ─────────────────────────────────────────────────────────────────
-export default defineConfig({
+export default withMermaid(defineConfig({
title: 'VIZ Ledger Documentation',
description: 'Official documentation for VIZ Ledger — hybrid DLT with Fair-DPOS consensus',
base: '/viz-cpp-node/',
@@ -486,4 +540,4 @@ export default defineConfig({
},
},
},
-});
+}));
diff --git a/docs/advanced/hardfork-management.md b/docs/advanced/hardfork-management.md
index e4fffae7ec..f1a00b3dee 100644
--- a/docs/advanced/hardfork-management.md
+++ b/docs/advanced/hardfork-management.md
@@ -48,6 +48,8 @@ When the head block time passes `next_hardfork_time` and sufficient validators s
| 10 | Inflation model |
| 11 | Emission model changes |
| 12 | Emergency consensus recovery (see below) |
+| 13 | Distribution epoch length (`chain_properties_hf13`) |
+| 14 | Prediction Markets (Onix): 18 ops + 7 virtual ops, CPMM/LMSR pricing, parimutuel settlement, oracles, disputes, commit-reveal, lazy pool; chain properties v5 |
---
diff --git a/docs/governance/chain-properties.md b/docs/governance/chain-properties.md
index 52ae9beb4e..5517dfa254 100644
--- a/docs/governance/chain-properties.md
+++ b/docs/governance/chain-properties.md
@@ -122,8 +122,28 @@ Properties were introduced in hardfork stages:
| `chain_properties_hf4` | 1 | HF4 | inflation_validator_percent, inflation_ratio_committee_vs_reward_fund, inflation_recalc_period |
| `chain_properties_hf6` | 2 | HF6 | data_operations_cost_additional_bandwidth, validator_miss_penalty_percent, validator_miss_penalty_duration |
| `chain_properties_hf9` | 3 | HF9 | create_invite_min_balance, committee_create_request_fee, create_paid_subscription_fee, account_on_sale_fee, subaccount_on_sale_fee, validator_declaration_fee, withdraw_intervals |
+| `chain_properties_hf13` | 4 | HF13 | distribution_epoch_length |
+| `chain_properties_pm` | 5 | HF14 | ~30 prediction-market parameters + kill-switches `pm_commit_reveal_enabled`, `pm_lazy_pool_enabled` |
-Use version index 3 (`chain_properties_hf9`) for all new validator property submissions.
+Use version index **5** (`chain_properties_pm`) for all new validator property submissions. (Index 4 is `chain_properties_hf13`, which added `distribution_epoch_length`.)
+
+### Prediction-market parameters (v5, HF14) {#pm-parameters}
+
+All median-voted; see [Prediction Market Operations](../protocol/operations/prediction-markets.md).
+
+All PM percentages are **bp (10000 = 100.00%)**, like the other `*_percent` properties — no permille (‰) anywhere.
+
+- **Oracle:** `pm_min_oracle_insurance`, `pm_max_oracle_fee_percent` (the *only* governed fee cap — on the oracle %), `pm_oracle_registration_fee`, `pm_oracle_penalty_percent`, `pm_oracle_dispute_response_sec`, `pm_oracle_accept_window_sec` (default 3600 = 1h — the named oracle must accept or reject a pending market within this window; on expiry the cron refunds the creator's seed liquidity, but **not** the creation fee, and voids the market → `pm_market_expired`).
+- **Risk / coverage** *(percent of a market's betting volume, 100 = 1.0×):* `pm_listing_min_coverage_percent` (250 = 2.5×) — markets whose oracle insurance covers less than this share of their volume are hidden from the default `list_markets` catalog (revealed via `show_risky`); `pm_betting_min_coverage_percent` (150 = 1.5×) — advisory threshold, published for clients to require an explicit risk confirmation before betting (not enforced on-chain; must be `≤ pm_listing_min_coverage_percent`).
+- **Market:** `pm_min_liquidity`, `pm_market_creation_fee`, `pm_max_outcomes`, `pm_max_market_duration`. *(There is no aggregate fee cap; creator/liquidity fees are uncapped and self-limiting, with a static `sum ≤ 100%` solvency bound.)*
+- **Batch / commit-reveal:** `pm_batch_epoch_blocks`, `pm_reveal_window_blocks`, `pm_min_batch_bet`, `pm_commit_no_reveal_penalty_percent`, `pm_commit_reveal_enabled`.
+- **Disputes:** `pm_dispute_fee`, `pm_dispute_grace_sec`, `pm_dispute_vote_period_sec`, `pm_dispute_auto_close_sec`, `pm_dispute_approve_min_percent`, `pm_no_contest_penalty_percent`, `pm_dispute_reward_multiplier` (bp multiplier, 10000 = 1×).
+- **Time penalty:** `pm_default_time_penalty_percent`, `pm_max_time_penalty`.
+- **Lazy pool:** `pm_lazy_pool_enabled`, `pm_lazy_alloc_percent`, `pm_lazy_max_total_alloc_percent`, `pm_lazy_recall_step_percent`, `pm_lazy_lock_sec`, `pm_lazy_emergency_penalty_percent`, `pm_lazy_min_liquidity_fee_percent` (default 200 = 2% — the pool refuses to co-provide liquidity to a market whose `liquidity_fee_percent` is below this reward floor).
+- **Leverage (opt-in):** `pm_leverage_enabled`, `pm_leverage_fund_percent`, `pm_leverage_max_per_position_bp`, `pm_leverage_max_position_ratio_percent`, `pm_leverage_min_market_liquidity`, `pm_leverage_safety_margin_percent`, `pm_leverage_max_slippage_percent`, `pm_leverage_m_factor_percent`, `pm_leverage_pool_profit_percent`, `pm_leverage_expiration_buffer_sec`, `pm_conversion_profit_cost_percent`.
+- **Fairness:** `pm_processing_cap_per_block`.
+
+The three `*_enabled` flags (`pm_commit_reveal_enabled`, `pm_lazy_pool_enabled`, `pm_leverage_enabled`) are live kill-switches: the validator median can disable commit-reveal, the lazy pool, or leverage without a new hardfork.
---
diff --git a/docs/node/configuration.md b/docs/node/configuration.md
index e78b9d010a..df5894d672 100644
--- a/docs/node/configuration.md
+++ b/docs/node/configuration.md
@@ -158,6 +158,11 @@ Leave these unset on non-validator nodes.
# Allow production even if chain is stale (development/testnet only)
enable-stale-production = false
+# Disable minority-fork detection (single-operator testnet/fork ONLY).
+# Unlike enable-stale-production, this is never auto-cleared by healthy
+# participation. Never enable on a real public network.
+disable-minority-fork-detection = false
+
# Minimum participation % required to produce blocks (0–99)
required-participation = 33
@@ -203,4 +208,4 @@ All options listed by source file:
| `plugins/chain/plugin.hpp` | `shared-file-size`, `min-free-shared-file-size`, `inc-shared-file-size`, `block-num-check-free-size`, `single-write-thread`, `enable-plugins-on-push-transaction`, `read-wait-micro`, `max-read-wait-retries`, `write-wait-micro`, `max-write-wait-retries`, `skip-virtual-ops`, `clear-votes-before-block`, `track-account-range`, `history-whitelist-ops`, `history-blacklist-ops`, `history-start-block` |
| `plugins/p2p/p2p_plugin.hpp` | `p2p-endpoint`, `p2p-max-connections`, `p2p-seed-node`, `checkpoint` |
| `plugins/webserver/webserver_plugin.hpp` | `webserver-http-endpoint`, `webserver-ws-endpoint`, `webserver-thread-pool-size` |
-| `plugins/validator/validator.hpp` | `enable-stale-production`, `required-participation`, `validator`, `private-key`, `emergency-private-key`, `fork-collision-timeout-blocks`, `ntp-server`, `ntp-request-interval`, `debug-block-production` |
+| `plugins/validator/validator.hpp` | `enable-stale-production`, `disable-minority-fork-detection`, `required-participation`, `validator`, `private-key`, `emergency-private-key`, `fork-collision-timeout-blocks`, `ntp-server`, `ntp-request-interval`, `debug-block-production` |
diff --git a/docs/node/validator-node.md b/docs/node/validator-node.md
index e5f810ad4b..6c187d3e8b 100644
--- a/docs/node/validator-node.md
+++ b/docs/node/validator-node.md
@@ -164,6 +164,9 @@ If fewer than 33% of validators are participating, production stops to prevent s
### Minority Fork Detection
If the node's fork database shows 21+ consecutive blocks all from this node's own validators, it automatically rolls back to LIB and resyncs. This catches network isolation.
+> [!WARNING] Single-operator forks
+> On a testnet or mainnet fork where **one operator controls all validators**, "21 blocks all ours" is the normal healthy state, so this detector loops forever resetting to LIB. `enable-stale-production = true` does **not** help here: healthy participation (≥33%) auto-clears that override every block. Use `disable-minority-fork-detection = true` instead — it bypasses both the standard and DLT detection paths and is never auto-cleared. **Never enable it on a real public network** — it removes the isolation guard.
+
### Production Watchdog
If no block has been produced for 180 seconds (60s for emergency master) while `should_be_producing` is true, the watchdog automatically clears stuck flags (`minority_fork_recovering`, P2P catchup, chain syncing) and attempts to resume.
diff --git a/docs/plugins/prediction-market-api.md b/docs/plugins/prediction-market-api.md
new file mode 100644
index 0000000000..596c86470e
--- /dev/null
+++ b/docs/plugins/prediction-market-api.md
@@ -0,0 +1,269 @@
+# `prediction_market_api` Plugin
+
+Read-only JSON-RPC access to HF14 prediction-market state (markets, bets, oracles, liquidity, disputes, the lazy pool, and the v5 chain properties). The plugin returns the raw consensus `pm_*` objects directly, plus a few computed DTOs for values that are derived rather than stored.
+
+**Enable:** add `prediction_market_api` to the node's plugin list (registered in `vizd` by default). Depends on `chain` + `json_rpc`.
+
+All list methods page with `from` (skip count) and `limit` (`≤ 1000`).
+
+---
+
+## Methods
+
+### Markets
+
+| Method | Args | Returns |
+|--------|------|---------|
+| `get_market` | `market_id` | `pm_market_object` |
+| `list_markets` | `status, from, limit, [show_risky]` | `pm_market_object[]` |
+| `list_markets_by_oracle` | `oracle, from, limit` | `pm_market_object[]` |
+| `list_markets_awaiting_resolution` | `oracle, from, limit` | `pm_market_object[]` |
+| `list_markets_by_creator` | `creator, from, limit` | `pm_market_object[]` |
+| `get_market_outcomes` | `market_id` | `pm_outcome_object[]` |
+| `get_market_weight_sums` | `market_id` | `pm_market_weight_sums` (computed) |
+| `get_market_bets` | `market_id, from, limit` | `pm_bet_object[]` |
+| `get_market_liquidity` | `market_id, from, limit` | `pm_liquidity_object[]` |
+| `get_market_full` | `market_id, [account]` | `pm_market_full` (computed) |
+
+`status` for `list_markets`: `-1` deleted, `0` waiting, `1` active, `2` closed, `3` resolved.
+
+`list_markets_awaiting_resolution` returns the markets that need **this oracle's result now**: active (`status 1`) markets whose betting window has already closed (`betting_expiration ≤ head_block_time`) and are therefore not yet resolved. "Awaiting" is not a distinct status — a market stays `active` from open through betting-close until it is resolved — so it cannot be isolated by `status` alone. This method walks the `by_betting_expiration` index (keyed `status, betting_expiration, id`) over just the bounded prefix of active markets whose betting has passed, avoiding a full scan of the oracle's (mostly resolved) history that an Oracle Console would otherwise have to do client-side.
+
+`get_market_full` is a **one-call enriched view** for a market-detail screen: it returns the market + outcomes + weight sums + oracle (with reliability) + parsed metadata, and — when the optional `account` is given — that account's bets, leverage positions and LP **on this market**. Saves the thin client several round-trips.
+
+**Risk listing filter** (security-threat-model §4.3): by default `list_markets` hides under-insured markets — those whose oracle insurance covers less than **2.5×** the market's betting volume. Pass `show_risky = true` to reveal them. Markets are only hidden, never blocked/deleted (betting is always permitted on-chain — a consensus bet-block would be a censorship vector). The lazy-pool exposure penalties (active-market 5% recursive + fault stamps) are enforced in consensus on allocation.
+
+### Market metadata (off-chain parsed)
+
+Each market carries a free-form, consensus-opaque `metadata` JSON string. This plugin parses the keys it
+indexes (category / subcategory / tags / banned jurisdictions / title / image / condition_id /
+**description**) into a `pm_market_meta_object` for discovery, jurisdiction filtering and display —
+**display/indexing only, never consensus**. `description` holds the short resolution rules (how the oracle
+will resolve the market — surface-level, for clients); the market's on-chain `url` still points to the full
+legal terms at the source.
+
+| Method | Args | Returns |
+|--------|------|---------|
+| `get_market_meta` | `market_id` | `pm_market_meta_object` (or error if none) |
+| `list_markets_by_category` | `category, from, limit, [jurisdiction], [subcategory], [tag], [sort]` | `pm_market_meta_object[]` |
+| `get_market_categories` | — | `pm_market_categories` (computed) |
+
+`list_markets_by_category` excludes markets whose `banned_jurisdictions` contains the optional
+`jurisdiction` ISO code — so a regulated client passes its own jurisdiction to get only the markets it
+may list. Optional `subcategory` (exact) and `tag` (CSV membership) narrow the set; `sort` ∈ `newest`
+(market id desc, default) · `oldest` · `volume` (`bets_sum` desc) · `expiration` (`betting_expiration`
+asc). `get_market_categories` returns the live taxonomy — per-category / per-subcategory counts plus the
+top 20 hot tags (jurisdiction-* excluded) — aggregated over currently indexed markets, so a browse UI can
+build its filter chips without hard-coding a taxonomy. The meta object:
+```
+{ market: pm_object_id,
+ category, subcategory, tags, // strings; tags comma-joined
+ banned_jurisdictions, // comma-joined ISO codes; empty = allowed everywhere
+ title, image, condition_id, // display + source back-link
+ description, // short resolution rules (url = full legal terms)
+ expiry } // pruned after the dispute window closes + TTL
+```
+
+### Positions & oracles
+
+| Method | Args | Returns |
+|--------|------|---------|
+| `get_account_positions` | `account, from, limit` | `pm_position[]` (bet + `expected_payout`) |
+| `get_account_leverage_positions` | `account, from, limit` | `pm_leverage_position_object[]` |
+| `get_market_leverage_positions` | `market_id, from, limit` | `pm_leverage_position_object[]` |
+| `get_creator_ban` | `account` | `pm_creator_ban_object` (or error if none) |
+| `get_oracle` | `owner` | `pm_oracle` (object + `reliability_score`) |
+| `list_oracles` | `from, limit` | `pm_oracle_object[]` |
+
+### Leverage previews (Boost)
+
+Read-only quotes that call the **same in-node margin math** the evaluators use, so a preview matches what the corresponding `pm_leverage_*` op would compute at the head block. They are non-consensus estimates (reserves move between the read and the broadcast — always send the on-chain slippage guards).
+
+| Method | Args | Returns |
+|--------|------|---------|
+| `get_leverage_quote` | `market_id, outcome_index, collateral` | `pm_leverage_quote` (computed) |
+| `get_leverage_close_preview` | `position_id` | `pm_leverage_close_preview` (computed) |
+| `get_leverage_convert_preview` | `position_id` | `pm_leverage_convert_preview` (computed) |
+
+`get_leverage_quote` mirrors `pm_leverage_open`: it returns the max solvent loan and resulting max leverage, the pool/position caps, up to 12 slider stops (each with tokens, threshold, current & worst-case cancel value), and — when leverage is not possible — `available = false` with a `failed_constraints[]` list. `get_leverage_close_preview` / `get_leverage_convert_preview` mirror `pm_leverage_close` / `pm_leverage_convert` at the current reserves (cancel value, pool obligation, what the bettor receives, whether it is closeable/convertible, and the conversion fee at the current median `pm_conversion_profit_cost_percent`).
+
+> Per-bettor settlement is emitted as the `pm_payout` virtual op (stake, side/outcome, realized payout —
+> `0` on a loss); a leveraged position's settlement is the `pm_leverage_resolve` virtual op (with
+> `outcome_index`, `won`, `leverage`). Both appear in `account_history`; the leverage-position objects
+> themselves are queryable via the two methods above.
+
+### Disputes, lazy pool, governance
+
+| Method | Args | Returns |
+|--------|------|---------|
+| `get_dispute` | `market_id` | `pm_dispute_object` |
+| `get_dispute_votes` | `market_id` | `pm_dispute_votes` (votes + live tally) |
+| `get_lazy_pool` | — | `pm_lazy_pool_object` |
+| `get_lazy_deposit` | `account` | `pm_lazy_deposit_object` |
+| `get_lazy_allocations` | `from, limit` | `pm_lazy_allocation_object[]` |
+| `get_market_lazy_allocation` | `market_id` | `pm_lazy_allocation_object` (or error if none) |
+| `get_pm_chain_properties` | — | `chain_properties_pm` (median, v5) |
+
+`get_lazy_allocations` lists the lazy pool's per-market allocation records (for a pool dashboard); `get_market_lazy_allocation` fetches the one for a given market. Oracle penalty stamps need no separate method — they ship on `pm_oracle_object` (`penalty_stamps`, `last_penalty_stamp_time`) via `get_oracle`.
+
+### Charts — kline / weight history
+
+A time series for plotting how each outcome's weight evolves. The plugin appends one point **every time a market's per-outcome weights change** — a bet, a cancel, a liquidation, a batch settle, a leverage open, or a leverage settlement — as a timestamped snapshot of the parimutuel weight (staked amount) on every outcome. This is **non-consensus** plugin state (kept in chainbase, undo/redo-safe, never part of the state hash); history accrues from the moment the plugin is first enabled on the node.
+
+**Retention:** the kline history is pruned **together with the market's metadata**, on the same schedule — `result_expiration` + dispute grace + `pmm-ttl-days` (default 7). So a market's full chart is available throughout its life and for the retention window after settlement, then both indexes are cleaned up (draining over several blocks for very long histories) to keep node storage bounded.
+
+| Method | Args | Returns |
+|--------|------|---------|
+| `get_market_kline` | `market_id, [from], [limit]` | `pm_kline[]` (ascending by `seq`) |
+
+Pagination is **offset-from-newest** (kept deliberately simple for thin clients): `from` is how many of the **newest** points to skip, `limit ≤ 1000` is the page size.
+- `(market_id, 0, 1000)` → the latest ≤ 1000 changes.
+- `(market_id, 1000, 1000)` → the previous 1000 (one page further back) — repeat with `from += 1000` to lazy-load older history.
+
+Plot it as: x = `timestamp` (unix seconds), and one line per outcome `i` with y = `weights[i]` (or normalized `weights[i] / Σweights` for the implied probability).
+
+---
+
+## Computed DTOs
+
+These wrap raw objects with values derived at read time (non-consensus).
+
+**`pm_position`** — a bet plus its parimutuel payout:
+```
+{ bet: pm_bet_object,
+ expected_payout: share_type, // payout if this side wins (or realized once settled)
+ market_status: int8,
+ resolved_outcome: int16 }
+```
+`expected_payout` byte-mirrors `settle_market`: for an active bet it is the conditional payout if the chosen side wins (`amount + winners_pool × weight / Σweight − time_penalty`); once settled it is the realized `resolved_amount`.
+
+**`pm_oracle`** — oracle object + `reliability_score` (bp `[0..10000]`, API heuristic blending the resolution-success ratio with the dispute-win ratio, minus a per-ban penalty). Non-consensus.
+
+**`pm_market_weight_sums`** — per side/outcome `bets_sum` and `weight_sum` (weight sums are computed by scanning bets, since they are not stored):
+```
+{ market_type: uint8, bets_sum: share_type,
+ outcomes: [ { outcome_index, label, bets_sum, weight_sum } ] }
+```
+
+**`pm_dispute_votes`** — the committee tally **plus a stake-weighted projection of the finalize cron**,
+so a caller can show the live quorum status and the verdict that would be applied under the current votes:
+```
+{ votes: pm_dispute_vote_object[],
+ // legacy rough tally (weight = |vote_percent|, NOT stake) — kept for compatibility
+ uphold_weight, challenge_weight, total_weight,
+ challenger_leads: bool, // ≥ pm_dispute_approve_min_percent (rough)
+ proposed_outcome: int16,
+ // ── accurate stake-weighted projection (mirrors pm_dispute_finalize) ──
+ // every *_shares value is vesting-shares: effective_vesting_shares + lazy-pool stake → shares
+ participation_shares, // Σ weight of accounts that voted (= max_rshares)
+ electorate_shares, // total_vesting_shares + pool_NAV→shares (quorum base)
+ quorum_required_shares, // electorate × pm_dispute_approve_min_percent
+ quorum_percent_bp: int32, // participation / electorate (bp, 10000 = 100.00%)
+ quorum_reached: bool, // participation_shares ≥ quorum_required_shares
+ oracle_defense_shares, change_shares, // rshares defending the oracle vs. backing a change
+ outcome_change_shares: int64[], // per-outcome backing rshares (size = outcome_count)
+ expected_uphold: bool, // true ⇒ oracle resolution stands if finalized now
+ expected_outcome: int16, // outcome that would be set at finalize now
+ expected_consensus_strength_bp: int32 } // winning / participation (bp); 0 when uphold
+```
+> The projection uses the **same** stake weighting and lazy-pool→shares bridge as the on-chain
+> `pm_dispute_finalize`, so `expected_outcome` / `quorum_reached` match what the cron will apply at
+> `voting_end_time` *given the votes cast so far* (votes are revisable until then — see
+> [dispute operations](../protocol/operations/prediction-markets.md)).
+
+**`pm_kline`** — one charting point (per-outcome weight snapshot at a moment in time):
+```
+{ seq: uint32, // 0-based, contiguous, monotonic per market (the change index)
+ timestamp: uint32, // unix seconds — x coordinate
+ reason: uint8, // 0 bet, 1 cancel, 2 liquidation, 3 batch settle, 4 leverage open, 5 leverage resolve
+ bets_sum: share_type, // total staked across all outcomes at this point
+ weights: share_type[] }// per-outcome staked weight (y values), index = outcome_index
+```
+
+**`pm_market_full`** — one-call enriched market view (`oracle`/`meta` are `null` when absent; the `my_*`
+arrays are empty unless an `account` argument was supplied):
+```
+{ market: pm_market_object,
+ outcomes: pm_outcome_object[], // empty for binary markets
+ weight_sums: pm_market_weight_sums,
+ oracle: pm_oracle | null,
+ meta: pm_market_meta_object | null,
+ my_positions: pm_position[], // account's bets on THIS market
+ my_leverage_positions: pm_leverage_position_object[],
+ my_liquidity: pm_liquidity_object[] }
+```
+
+**`pm_leverage_quote`** — leverage-open preview (from `pm::leverage::*`, the same math the evaluator runs):
+```
+{ available: bool, outcome_index, collateral,
+ max_loan, max_leverage_x100, // 100 = 1.00×
+ pool_free_amount, fund_available, per_position_cap, market_position_cap,
+ pool_profit_percent, safety_margin_percent, max_slippage_percent, m_factor_percent,
+ expiration_buffer_sec, auto_close_time, // betting_expiration − buffer
+ stops: [ { leverage_x100, loan, total_bet, expected_tokens, pool_profit,
+ liquidation_threshold, current_cancel_value, worst_case_cancel_value } ],
+ failed_constraints: [ { constraint, reason } ] } // populated when !available
+```
+**`pm_leverage_close_preview`** — `{ position_id, outcome_index, cancel_value, pool_obligation, bettor_receives, collateral, loan, pool_profit_charge, closeable: bool, loss_vs_collateral, loss_percent_bp }`.
+**`pm_leverage_convert_preview`** — `{ position_id, outcome_index, cancel_value, pool_obligation, current_profit, conversion_profit_cost_percent, conversion_fee, total_user_payment, convertible: bool }`.
+
+**`pm_market_categories`** — browse taxonomy with live counts:
+```
+{ categories: [ { category, count, subcategories: [ { subcategory, count } ] } ], // sorted by count desc
+ hot_tags: [ { tag, count } ] } // top 20 (jurisdiction-* excluded)
+```
+
+---
+
+## Example
+
+Fetch a bettor's positions:
+```bash
+curl -s --data '{"jsonrpc":"2.0","id":1,"method":"call",
+ "params":["prediction_market_api","get_account_positions",["alice",0,100]]}' \
+ http://127.0.0.1:8090
+```
+
+Fetch the latest 1000 chart points for market `42`, then the previous 1000:
+```bash
+# newest page
+curl -s --data '{"jsonrpc":"2.0","id":1,"method":"call",
+ "params":["prediction_market_api","get_market_kline",[42,0,1000]]}' http://127.0.0.1:8090
+# one page older
+curl -s --data '{"jsonrpc":"2.0","id":1,"method":"call",
+ "params":["prediction_market_api","get_market_kline",[42,1000,1000]]}' http://127.0.0.1:8090
+```
+
+Thin-client charting (lazy-load older history on scroll-back), turning each point into
+per-outcome series of `{ x: unixtime, y: weight }`:
+```js
+async function call(method, params) {
+ const r = await fetch('http://127.0.0.1:8090', { method: 'POST',
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'call',
+ params: ['prediction_market_api', method, params] }) });
+ return (await r.json()).result;
+}
+
+// Pull pages of 1000 from newest backwards until we have `want` points (or run out).
+async function loadKline(marketId, want = 3000) {
+ const points = [];
+ for (let from = 0; points.length < want; from += 1000) {
+ const page = await call('get_market_kline', [marketId, from, 1000]);
+ if (!page.length) break; // reached the start of history
+ points.unshift(...page); // pages are ascending; prepend older pages
+ if (page.length < 1000) break;
+ }
+ return points;
+}
+
+// One {x,y} series per outcome — feed straight into any charting lib.
+function toSeries(points, outcomeCount) {
+ const series = Array.from({ length: outcomeCount }, () => []);
+ for (const p of points)
+ for (let i = 0; i < outcomeCount; i++)
+ series[i].push({ x: p.timestamp, y: Number(p.weights[i]) });
+ return series;
+}
+```
+
+See [Prediction Market Operations](../protocol/operations/prediction-markets.md) for the on-chain objects these methods expose, and [Chain Properties](../governance/chain-properties.md) for the v5 governance parameters.
diff --git a/docs/plugins/validator.md b/docs/plugins/validator.md
index c331de515a..28e9b54d7d 100644
--- a/docs/plugins/validator.md
+++ b/docs/plugins/validator.md
@@ -24,6 +24,7 @@ chain::plugin, p2p::p2p_plugin, snapshot::snapshot_plugin
| `private-key` | — | WIF private key(s) for signing; may be repeated |
| `emergency-private-key` | — | WIF key for emergency consensus; auto-adds `CHAIN_EMERGENCY_VALIDATOR_ACCOUNT` to the validator set |
| `enable-stale-production` | `false` | Bypass participation and sync checks (testnet / network recovery only) |
+| `disable-minority-fork-detection` | `false` | Skip minority-fork detection entirely (single-operator testnet/fork only). Never auto-cleared by healthy participation — see [Minority Fork Detection](#minority-fork-detection) |
| `required-participation` | `3300` | Minimum validator participation in **basis points** (3300 = 33%) |
| `fork-collision-timeout-blocks` | `21` | Consecutive fork-collision deferrals before forcing production (one full validator round) |
@@ -116,7 +117,8 @@ When a competing block exists at `head_block_num + 1`:
Before each production attempt (after HF12 safety checks), the plugin walks the last 21 blocks in `fork_db`. If all 21 were produced by the node's own configured validators, the node is isolated on a minority fork.
- **Default action:** Call `p2p().resync_from_lib()` — pop blocks to LIB, reset fork DB, re-initiate P2P sync, reconnect seed nodes. Returns `minority_fork`.
-- **With `enable-stale-production=true`:** Log a warning, continue producing.
+- **With `enable-stale-production=true`:** Log a warning, continue producing. **Note:** at ≥33% participation this override is auto-cleared every block, so on a single-operator fork it does *not* stop the detector — use `disable-minority-fork-detection` instead.
+- **With `disable-minority-fork-detection=true`:** Both the standard and DLT detection paths are skipped entirely, and the flag is never auto-cleared. For single-operator testnet/forks where "21 blocks all ours" is the healthy steady state. **Never enable on a real public network** — it removes the isolation guard.
- **Skipped when:** Emergency consensus is active (committee blocks would always match our configured set). A DLT-specific slave isolation check replaces it in emergency mode.
---
@@ -217,7 +219,7 @@ Included in P2P FORWARD stagnation logs when the node is stuck with no peer ahea
| `no_private_key` | Config missing `private-key` for the signing key that's registered on-chain |
| `low_participation` | Network participation < 33%; check peer connectivity or set `enable-stale-production=true` |
| `fork_collision` | Competing block at next height; wait for vote-weight resolution or 21-deferral timeout |
-| `minority_fork` | Isolated; plugin auto-resyncs to LIB |
+| `minority_fork` | Isolated; plugin auto-resyncs to LIB. On a single-operator fork this loops — set `disable-minority-fork-detection=true` |
| Watchdog fires repeatedly | Sync or catchup flag stuck; watchdog auto-clears if head is advancing |
| `SLOT-HIJACK` logs | Emergency master blanked our key; restore via `validator_update_operation` |
diff --git a/docs/prediction-markets/concepts-analysis.md b/docs/prediction-markets/concepts-analysis.md
new file mode 100644
index 0000000000..361daa7d33
--- /dev/null
+++ b/docs/prediction-markets/concepts-analysis.md
@@ -0,0 +1,244 @@
+---
+title: Concept analysis — Onix vs the 90 prediction-market concepts
+description: How the live VIZ on-chain implementation (Onix protocol, Forecaster client) maps onto the 90 prediction-market theory concepts — what is solved, inherent, not needed, or roadmap.
+---
+
+# Concept analysis — Onix vs the 90 theory concepts
+
+> **Forecaster** is the thin client to the VIZ on-chain prediction market — the access layer that lets
+> people anywhere participate by signing `pm_*` operations (see the [section overview](./)).
+> **Onix** is the protocol it talks to. This page maps each of the **90 PM-Atlas prediction-market
+> concepts** onto **how the live VIZ on-chain implementation addresses it**, and **whether it is needed**
+> for this architecture.
+>
+> Grounding docs: [whitepaper](./whitepaper), [specification](./specification),
+> [workflows & disputes](./workflows).
+
+## Legend
+
+| Mark | Meaning |
+|------|---------|
+| ✅ **Resolved** | Onix design directly solves or handles it |
+| ⚪ **Inherent** | A property Onix exhibits/inherits by construction (no extra work) |
+| ➖ **Not needed** | Architecturally unnecessary under Onix |
+| 🟡 **Partial / Roadmap** | Partly addressed today; rest is on the VIZ roadmap |
+| 🏛 **Client-layer** | Handled by the jurisdictional client, not the protocol |
+| 🔴 **Open / Risk** | Still a live concern; not fully solved |
+
+The single biggest structural difference from every other platform: **LP principal is structurally guaranteed (winners paid only from losers' forfeited stakes), pricing is CPMM for binary and LMSR-softmax + parimutuel settlement for multi, and there is no order book.** Most "liquidity & trading" concepts that exist to manage market-maker inventory risk simply **do not apply** because Onix has no inventory-bearing maker.
+
+> **On-chain actualization (HF14 / live).** This mapping was first written against the whitepaper/spec. Several items then marked *roadmap* are now **implemented as consensus operations** and verified in `consensus_sim`:
+> - **Batch auctions + commit-reveal betting** — `pm_commit_bet` / `pm_reveal_bet` / `pm_batch_settle`, per-market `allow_batch` / `allow_instant_bet`, median kill-switch `pm_commit_reveal_enabled`. **Binary only** (multi forces `allow_instant_bet` — no LMSR batch yet).
+> - **Opt-in leverage subsystem** — `pm_leverage_open/close/convert`, lazy-pool-funded, kill-switch `pm_leverage_enabled` (default off). Directly answers **Position Collateralization (#30)**.
+> - **The Lazy Pool itself** — singleton, auto-allocation, MasterChef accounting, leverage loans, graduated recall.
+> - **`endogeneity_tier`** market field; on-chain **creator bans** (`pm_creator_ban_object`); richer settlement virtual ops (`pm_payout` per bettor, `pm_leverage_resolve`, `pm_market_accepted`, `pm_auto_payout`) + matching plugin API.
+> - **New since the spec:** **lazy-pool stake counts as governance weight** in PM disputes *and* DAO committee-request voting (converted to vesting-shares, HF14-gated).
+>
+> **Deliberately NOT built:** commit-reveal *dispute* voting — committee disputes are **open public hearings by design** (votes stay public via `pm_dispute_vote`, and ballots are revisable until close). **Still roadmap:** automated exogenous data oracles. Rows and the mitigations table below are updated to this live state.
+
+---
+
+## 1. Information Theory
+
+| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed |
+|---|---------|---------|--------------------------------------------------|
+| 1 | **Brier Score** | ⚪ Inherent | Not a protocol mechanism but the *metric* by which Onix markets are judged. On VIZ, every bet/resolution is a consensus-validated event, so per-market price histories and outcomes are fully on-chain → Brier scoring of the platform (and of oracles) is computable by anyone. Needed only as an analytics/reputation input, not core logic. |
+| 2 | **Calibration** | ⚪ Inherent | Onix prices are genuine probabilities (CPMM `P(A)=reserve_b/(reserve_a+reserve_b)`, LMSR softmax sums to 1). Calibration is an emergent property to *measure*, improved indirectly by the time-penalty (discourages no-info last-second bets) and deep LP liquidity. Not something the protocol enforces. |
+| 3 | **Credibility Markets** | 🟡 Partial | The bonded-oracle + 14-metric reputation + composite trust score is effectively a credibility market for *resolvers*. Staking credibility against an outcome is native. A general "stake reputation on claims" product is a possible client-layer build, not core. |
+| 4 | **Distribution Markets** | 🟡 Roadmap | Onix Multi (3–10 discrete outcomes) approximates a distribution via bucketed outcomes. True continuous distribution markets (CDF/scalar) are **not** in scope today; would need a scalar-outcome operation. Listed-adjacent to the roadmap's "category-level AMMs." |
+| 5 | **Endogeneity** | 🟡 Partial (mitigated) | The risk that the market changes the thing it predicts. Category-specific (econ-data clean, political/social risky). Mitigated by the **live `endogeneity_tier` market field** (oracle-tagged 1/2/3) and **opt-in commit-reveal/batch betting (now on-chain)** that stops the public price from "leaking" mid-window (the thermostat channel); exogenous resolution via automated data oracles is **still roadmap**. See [Mitigations §](#mitigations-for-the-reflexivity-family). |
+| 6 | **Forecasting Accuracy** | ⚪ Inherent | The whole value proposition. Onix improves it indirectly: zero-risk LP → deeper books → less slippage → more informed participation → better prices. Accuracy is the output to measure, not a feature to build. |
+| 7 | **Info Finance** | ⚪ Inherent | Onix *is* an info-finance instrument: consensus-level operations turn information into priced, settleable positions. VIZ migration makes the information layer censorship-resistant and composable. |
+| 8 | **Information Aggregation** | ✅ Resolved | Core function. CPMM/LMSR pricing aggregates dispersed bets into a single probability; deep risk-free LP liquidity is precisely the lever Onix pulls to make aggregation work (the flywheel in §7.3 of the whitepaper). |
+| 9 | **Information Asymmetry** | 🟡 Partial | Onix's parimutuel/CPMM design means informed traders extract from *other losing bettors*, not from the LP — so asymmetry doesn't bankrupt liquidity (unlike CLOB/LMSR makers). **Opt-in commit-reveal + batch betting is now live (binary):** committed bets settle at a uniform batch price, removing the mempool-direction leak; per-market `allow_batch` + median kill-switch keep it optional. |
+| 10 | **Legibility** | ✅ Resolved | Every financial action is a consensus-validated VIZ operation with a `market_log` audit trail (before/after reserves). Fully legible/auditable by any node — strictly more legible than a centralized backend or opaque CLOB. New settlement virtual ops (**`pm_payout`** per bettor, **`pm_leverage_resolve`**, **`pm_market_accepted`**) + dedicated plugin API methods make per-bettor outcomes and leverage resolutions directly queryable. |
+| 11 | **Longshot Bias** | 🟡 Partial | CPMM/LMSR pricing can still exhibit favorite-longshot bias from bettor behavior; Onix doesn't correct it directly. The time penalty and deep liquidity dampen distortion, but bias is a behavioral output, not eliminated. |
+| 12 | **Noise Decomposition** | ⚪ Inherent | Analytical lens, not a protocol feature. On-chain price/volume series on VIZ make signal-vs-noise decomposition feasible for analysts. Not needed in core. |
+| 13 | **Nowcasting** | ⚪ Inherent | Onix prices update continuously per bet (~3s VIZ blocks), giving real-time nowcast estimates. Inherent to any live AMM market; no extra mechanism. |
+| 14 | **Price Discovery** | ✅ Resolved | CPMM and LMSR-softmax are continuous price-discovery engines; price coherence (`Σ price = 1`) holds *by construction* with no arbitrage/split-merge layer needed. |
+| 15 | **Probability Infrastructure** | ✅ Resolved | This is essentially Onix's thesis on VIZ: prediction markets as **first-class consensus operations** (`pm_*`), not smart contracts — a base-layer probability primitive. Directly the migration goal. |
+| 16 | **Superforecasting** | ⚪ Inherent | Individual-skill concept; Onix rewards accurate bettors via the losers→winners payout. Position transfers + reputation could support superforecaster identity, but it's a participant trait, not protocol logic. |
+| 17 | **Wisdom of Crowds** | ✅ Resolved | The mechanism Onix monetizes. Risk-free LP lowers the barrier so more of the crowd participates, sharpening the aggregate. Core to the design rationale. |
+| 18 | **Yes Bias** | 🟡 Partial | Behavioral tilt toward "Yes." Onix's symmetric CPMM and profit-only time penalty don't structurally favor Yes, but they don't correct human bias either. Mitigated by liquidity depth; a measurement concern. |
+
+---
+
+## 2. Mechanism Design
+
+| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed |
+|---|---------|---------|--------------------------------------------------|
+| 19 | **Binary Contracts** | ✅ Resolved | Onix Binary = CPMM (`x·y=k`) on two outcomes. AM-GM proof guarantees `reserve_a+reserve_b ≥ L`, so LP principal is covered. This is the primary market type. |
+| 20 | **Combinatorial Prediction Markets** | ➖ Not needed (today) | LMSR decomposes naturally over combinatorial spaces, but Onix Multi caps at 3–10 *independent* outcomes and deliberately omits CTF split/merge. Combinatorial/conditional bundles are explicitly out of scope; not required for the LP-guarantee model. |
+| 21 | **Incentive Compatibility** | ✅ Resolved | LMSR inherits IC from the log scoring rule (truth-telling dominant). Onix adds incentive alignment via bonded oracles (insurance > manipulation profit), losers-fund-winners settlement, and time-weighted LP rewards. |
+| 22 | **Keynesian Beauty Contest** | ✅ Resolved (market) / ⚪ (dispute layer — accepted by design) | KBC is a pathology of *relative/peer scoring*. The Onix **market** layer pays bettors against external ground truth (parimutuel), so it is structurally anti-KBC — you profit by *deviating* from the crowd price when it's wrong. The only KBC exposure is the **stake-weighted committee dispute vote** (a peer mechanism). **Decision: commit-reveal dispute voting will NOT be implemented** — a committee dispute is an **open public hearing**, and the DAO's credibility depends on resolving disputes as transparently as possible; hiding ballots would erode that trust. The residual KBC risk is accepted and structurally small: voters are **not paid** for matching the majority (no bandwagon bounty) and **ballots are revisable** until close (so honest updates on new evidence are expected, not suppressed). Pooled DAO members are also **enfranchised** (lazy-pool stake → vesting-shares, HF14). See [Mitigations §](#mitigations-for-the-reflexivity-family). |
+| 23 | **LMSR** | ✅ Resolved (the key innovation) | The concept file notes LMSR *failed for binaries* (permanent loss on the 0/1 boundary). Onix's answer: **use CPMM for binary, and use LMSR only for multi where the maker is NOT the counterparty** — parimutuel settlement pays winners from losers, so the LMSR subsidy is never at risk (`LP max loss = 0` vs `b·ln(N)`). This is the central design move. |
+| 24 | **LOX (Log-Odds Excess Lateness)** | ➖ Not needed | A specialized scoring/lateness metric. Onix instead uses a **quadratic time penalty on profit** to handle late-bet incentives — a simpler, settlement-time mechanism. LOX scoring is not part of the model. |
+| 25 | **Market Manipulation** | 🟡 Partial | Bonded oracle (bond must exceed manipulation profit), DPoS-validated operations, and committee dispute arbitration raise manipulation cost. Price manipulation via large bets is bounded by depth and — on **batch/commit-reveal markets (now live)** — by uniform-price settlement that neutralises speed-based sniping (the "sniper's tax"); active surveillance is still roadmap. |
+| 26 | **Market Scoring Rules** | ✅ Resolved | Onix Multi is a market scoring rule (LMSR) implementation, repurposed with parimutuel settlement. Directly used. |
+| 27 | **Multi-Outcome Markets** | ✅ Resolved | Onix Multi handles N=3–10 via LMSR softmax pricing + parimutuel payout, with `b = S/ln(N)`. First-class market type. |
+| 28 | **Parimutuel Markets** | ✅ Resolved (foundational) | Settlement in *both* market types is parimutuel: losers' forfeited stakes form `winners_pool`, distributed by token share. This is what makes the LP guarantee structural rather than insured. |
+| 29 | **Peer Prediction** | ➖ Not needed | Truth-telling-without-ground-truth schemes. Onix relies on bonded oracles + committee dispute, not peer-prediction scoring. Could inform subjective-market resolution but not used. |
+| 30 | **Position Collateralization** | ✅ Resolved (+ opt-in leverage, live) | By default every bet is fully prepaid (full amount enters reserves; no fees at bet time) — total collateralization by construction. Onix now **also** ships the concept's "next level": an **opt-in leverage subsystem** (`pm_leverage_open/close/convert`, kill-switch `pm_leverage_enabled`, default off). Margin is a **loan from the Lazy Pool** (no token emission — zero-sum preserved), so the position stays fully collateralized *from the system's view*. The binary "jump risk" that breaks CLOB liquidation engines (per this concept) is handled by **liquidating against pre-bet reserves**: opposing-bet / settlement force-close recovers `min(cancel_value, obligation) ≥ loan`, so the pool gets loan + interest back; the **only** bounded bad-debt path is a same-side `pm_cancel_bet` (Case B). The liquidation cascade is deliberately **not** gated by the kill-switch, so toggling leverage off never strips protection from open positions. |
+| 31 | **Proper Scoring Rules** | ✅ Resolved | LMSR is the cost-function dual of the log proper scoring rule; Onix Multi inherits its truthful-elicitation property. |
+| 32 | **Reflexivity** | 🟡 Partial (mitigated) | Parent of endogeneity. Mitigated by the same toolkit — **commit-reveal/batch betting + `endogeneity_tier` are now live**, exogenous resolution still roadmap — **plus on-chain creator bans** (`pm_creator_ban_object`) for harmful reflexivity (assassination/"hit" markets, propaganda markets that create a "constituency for the outcome"); the prohibited-category *list* itself stays client-layer. Deep risk-free LP depth also raises the cost of newsworthy price manipulation. See [Mitigations §](#mitigations-for-the-reflexivity-family). |
+
+---
+
+## 3. Liquidity & Trading
+
+> **Headline:** Onix has **no order book and no inventory-bearing market maker**. A large class of these concepts exists specifically to manage CLOB/maker inventory risk and therefore **do not apply** to Onix.
+
+| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed |
+|---|---------|---------|--------------------------------------------------|
+| 33 | **Adverse Selection** | ✅ Resolved (reframed) | The classic problem (informed flow bankrupts the maker) **cannot bankrupt the Onix LP**: winners are paid from losers, never from LP principal (AM-GM / parimutuel guarantee). Informed traders extract from other *bettors*, not the LP. Eliminates the core LP failure mode. |
+| 34 | **Arbitrage** | ⚪ Inherent | Intra-market arbitrage is unnecessary: price coherence (`Σ price = 1`) holds by construction in both CPMM and LMSR-softmax. No split/merge arbitrage layer needed. |
+| 35 | **Batched Auctions** | ✅ Resolved (opt-in, live) | Implemented as a **per-market uniform-price batch** (`mode=1` bets + commit-reveal → `pm_batch_settle` at each `pm_batch_epoch_blocks` epoch); only the **net residual** moves the AMM, so all same-side fills clear at one price and speed-based sniping (the "sniper's tax") is neutralised. Per-market `allow_batch` + median kill-switch `pm_commit_reveal_enabled`; **binary only** today (multi forces `allow_instant_bet`). The LP `Σreserve ≥ L` invariant is untouched — each batch is one valid CPMM transition. |
+| 36 | **Bid-Ask Spread** | ➖ Not needed | No order book → no quoted spread. "Cost of trading" appears as CPMM/LMSR slippage, governed by liquidity depth, not maker spreads. Concept doesn't map. |
+| 37 | **Bonding Trades** | ⚪ Inherent | Bets *are* bonded trades: capital is committed into reserves and only released at resolution (or via cancellation/transfer). Native behavior. |
+| 38 | **Continuous Double Auction** | ➖ Not needed | CDA is the CLOB model Onix explicitly rejects in favor of AMM pricing. Not used. |
+| 39 | **Covariance Markets** | ➖ Not needed | Trading correlation between events requires combinatorial/conditional structure Onix deliberately omits. Out of scope. |
+| 40 | **Cross-Platform Arbitrage** | 🟡 Partial | Onix prices can diverge from Polymarket/Kalshi; arbitrage across platforms is possible but external to the protocol. VIZ's open API + headless client make price data accessible; no native cross-platform bridge. |
+| 41 | **Execution Quality** | ✅ Resolved (reframed) | No partial fills/queue position. Execution quality = deterministic slippage + optional `min_tokens`/`min_return` slippage guards, validated at consensus. Predictable by construction. |
+| 42 | **Gap Risk** | ✅ Resolved (for LP) | Gap risk (sudden jump to 0/1 wiping the maker) is the failure mode Onix's structural LP guarantee eliminates — the LP never holds the losing side's terminal risk. Bettors still bear their own outcome risk (as intended). |
+| 43 | **Hedging** | 🟡 Partial | Bettors can hedge by taking offsetting positions, transferring positions (`pm_transfer_position`), bet cancellation (if allowed) via reverse CPMM, and now **opt-in leverage** (`pm_leverage_open/convert`) for capital-efficient offsetting. No native multi-leg derivatives; basic + leveraged hedging is possible. |
+| 44 | **Implied Correlation** | ➖ Not needed | Requires multi-event/combinatorial markets Onix omits. Out of scope. |
+| 45 | **Insider Trading** | 🟡 Partial / 🏛 Client | Protocol can't detect insider info; mitigated by time penalty (late-info bets earn less profit) and bonded-oracle resolution. KYC/surveillance to police insiders is a **client-layer** responsibility (regulated clients). |
+| 46 | **Kelly Criterion** | ⚪ Inherent | A bettor staking strategy, not a protocol feature. Onix exposes clean probabilities and full collateralization so Kelly sizing is computable by participants; the new **opt-in leverage** lets a bettor act on a fractional-Kelly edge with margin (pool-funded, liquidation-bounded). No core involvement beyond exposing the primitives. |
+| 47 | **Liquidity Fragmentation** | 🟡 Roadmap | Per-market pools fragment liquidity today. The whitepaper's top-priority roadmap item — **shared/category-level AMM pools** — is the architectural fix. Lazy Pool already mutualizes *deposits* across markets. |
+| 48 | **Liquidity Provision** | ✅ Resolved (core differentiator) | Risk-free LP is the headline: principal structurally guaranteed, time-weighted fee rewards, Lazy Pool auto-allocation + MasterChef accounting. Solves the "LPs lose money" problem that motivates the whole protocol. |
+| 49 | **Market Making** | ✅ Resolved (reframed) | No active maker needed — the AMM + LP pool *is* the maker, and it bears no inventory risk. "Market making" collapses into passive, risk-free liquidity provision. |
+| 50 | **Minimum Viable Liquidity** | ✅ Resolved | Enforced floor: min initial liquidity 100 VIZ; Lazy Pool auto-seeds every new market with `free_balance × allocation_%`. MVL is structurally bootstrapped rather than left to chance. |
+| 51 | **Order Book** | ➖ Not needed | Onix is AMM-based; no order book by design. |
+| 52 | **Orderflow Arbitrage** | ➖ Not needed | No order book / no PFOF-style flow routing → not applicable. |
+| 53 | **Relative Value Trading** | ➖ Not needed | Cross-instrument RV requires correlated/combinatorial markets Onix omits. Out of scope. |
+| 54 | **Retail Flow** | ✅ Resolved (reframed) | In CLOB models retail flow subsidizes maker losses to toxic flow. In Onix there is no maker to protect — retail and informed bettors all pay into the same parimutuel pool; LP is indifferent. The "retail-vs-toxic" tension dissolves at the LP layer. |
+| 55 | **Semantic Tick Size** | ➖ Not needed | Tick granularity is a CLOB concept. Onix prices are continuous AMM functions; precision is the fixed mVIZ unit (1/1000). No tick design needed. |
+| 56 | **Temporal Arbitrage** | 🟡 Partial | Betting earlier vs later carries different risk; Onix's **time penalty on profit** is precisely the mechanism that prices in lateness, dampening "wait-for-certainty" temporal arbitrage. Not eliminated, but explicitly disincentivized. |
+| 57 | **Time Arbitrage** | 🟡 Partial | Same family as #56 — exploiting information timing. Quadratic time penalty + ~3s block cadence reduce, but don't remove, the edge. Addressed by design intent. |
+| 58 | **Toxic Flow** | ✅ Resolved (for LP) | The defining CLOB/LMSR problem (sniper clears the book at 10¢ on a 99¢ outcome, maker eats 80¢) **does not hit the Onix LP** — payouts come from losers' stakes, and the LP subsidy is returned unconditionally. Toxic flow simply means informed bettors win the parimutuel pool, as intended. Major structural win. |
+| 59 | **Wash Trading** | 🟡 Partial / 🏛 Client | No fees at bet time removes one wash incentive, but volume-faking is still possible; transfers are pure reassignment (no fee farming there). Detection/surveillance is a client + roadmap surveillance concern. |
+
+---
+
+## 4. Oracle & Resolution
+
+| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed |
+|---|---------|---------|--------------------------------------------------|
+| 60 | **Corruption Value Multiple (CVM)** | ✅ Resolved (by design principle) | The protocol's explicit security invariant: oracle **insurance bond must exceed potential manipulation profit**. Risk factor (insurance/bets ratio) feeds the composite trust score. CVM is directly the bonding rationale. |
+| 61 | **Dispute Resolution** | ✅ Resolved | Full system: 12h grace, `dispute_fee`, mandatory oracle response, per-market resolver (`dispute_mode==0` committee stake-weighted vote / `==1` named resolver), insurance slashing, 3-outcome no-contest disputes, 14-day auto-close anti-freeze, on-chain creator/oracle bans. Among the most fully specified parts. **HF14 addition:** lazy-pool depositors keep their dispute vote weight (pool NAV → vesting-shares, added to `effective_vesting_shares`). |
+| 62 | **Oracle Design** | ✅ Resolved | Bonded oracle model: registration fee, ≥5000 VIZ insurance, explicit acceptance, evidence-backed resolution, 14-metric reputation, freshness decay, ban mechanics. Core subsystem. |
+| 63 | **Resolution Criteria** | 🟡 Partial / 🏛 Client | Market question/criteria live in `url`/description (custom_json, display-only). Protocol enforces *process* (who resolves, disputes) but not criterion *quality* — ambiguous criteria are a creator/client responsibility, policed retroactively via disputes + **on-chain creator bans** (`pm_creator_ban_object`, now live). |
+| 64 | **Self-Resolving Markets** | ➖ Not needed (today) | Onix resolution is oracle-driven, not algorithmic self-resolution. Automated data oracles (Chainlink-style feeds) are a high-priority roadmap item for objective markets, which would approximate self-resolution. |
+| 65 | **UMA Protocol** | ➖ Not needed (replaced) | UMA's optimistic oracle (used by Polymarket) is functionally replaced by Onix's bonded oracle + VIZ committee dispute model. Same problem, native VIZ solution — no external oracle dependency. |
+
+---
+
+## 5. Governance & Decisions
+
+| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed |
+|---|---------|---------|--------------------------------------------------|
+| 66 | **Attention Markets** | ➖ Not needed | Trading attention/virality is a distinct product; Onix is event-resolution focused. Could be a client-layer market category, not core. |
+| 67 | **Conditional Tokens** | ➖ Not needed (explicit) | Whitepaper §7.2 argues CTF split/merge is **architecturally unnecessary** — price coherence is mathematical, not enforced by tokens. The *one* useful CTF feature (transferable positions) is reimplemented natively as `pm_transfer_position` with encrypted memos. Deliberately omitted. |
+| 68 | **Decision Markets** | 🟡 Possible | Onix Multi could express decision markets, but conditional "if-policy-then-metric" structure isn't native (no conditional tokens). Buildable at client layer; not a core primitive. |
+| 69 | **Futarchy** | 🟡 Possible (client) | Concept file: futarchy = decision markets on conditional futures. Onix lacks native conditional markets, so full futarchy isn't supported in core. VIZ's stake-weighted committee already governs *parameters*; governance-by-market would be a client/roadmap construction. |
+| 70 | **Hyperstition Markets** | ➖ Not needed (deliberate) | Reflexivity-as-a-feature (coordinate, don't forecast). This is a *design choice, not a bug to fix*: Onix's requirement that an outcome be **externally verifiable by a bonded oracle** structurally excludes hyperstition markets by default. Could exist as a separate client-layer "coordination market" product with milestone resolution, but is not a core target. See [Mitigations §](#mitigations-for-the-reflexivity-family). |
+| 71 | **Impact Markets** | ➖ Not needed | Retrospective-funding/impact-certificate markets are a separate domain. Possible client-layer category; not core. |
+| 72 | **No-Loss Prediction Markets** | 🟡 Adjacent | Onix isn't no-loss for *bettors* (losers forfeit stakes — that funds winners). But it **is** "no-loss" for *LPs* (principal guaranteed). The yield-funded no-loss variant (stake yield, principal returned) is a different model; LP-side no-loss is already delivered. |
+| 73 | **Opportunity Markets** | ⚪ Inherent (adjacent) | The Lazy Pool's **opportunity-cost protection** (graduated recall, active-market penalty, fault stamps) addresses capital-opportunity-cost directly — though "opportunity markets" as a product category is out of scope. |
+
+---
+
+## 6. Business & Platforms
+
+| # | Concept | Verdict | How Forecaster-on-VIZ handles it / is it needed |
+|---|---------|---------|--------------------------------------------------|
+| 74 | **AI agents** | 🟡 Roadmap | Headless client + open VIZ operations make programmatic agents (bettors, LPs, automated oracles) straightforward. AI-driven liquidity/resolution is a natural extension, not yet specified. |
+| 75 | **Cross-subsidization** | ⚪ Inherent | The Lazy Pool cross-subsidizes liquidity across many markets from one deposit; MasterChef `reward_per_share` shares fee yield. Cross-subsidization is built into the pool economics. |
+| 76 | **Demand markets** | ➖ Not needed | Markets that gauge/aggregate demand are a product category; not a core Onix primitive. Client-layer. |
+| 77 | **Distribution moat** | 🟡 Strategy | Onix's moat is risk-free LP yield + VIZ-native infrastructure (the flywheel). Distribution (Telegram WebApp today → headless web client) is a go-to-market concern, partly addressed by platform-independence post-migration. |
+| 78 | **Election markets** | 🏛 Client | Supported as ordinary binary/multi markets; their *legality* is a jurisdictional-client matter (whitelisted oracles, category filters). Protocol-neutral. |
+| 79 | **Event contracts** | ⚪ Inherent | Every Onix market *is* an event contract. The regulatory classification of these contracts is a client/legal question, not protocol logic. |
+| 80 | **Federal preemption** | 🏛 Client (N/A to protocol) | Concept file: turns on whether US event contracts are "swaps." VIZ DLT is **infrastructure, not an operator** (whitepaper §6.2) — like Bitcoin is a ledger. Legal obligations attach to clients, not consensus. Not a protocol concern. |
+| 81 | **Long-tail markets** | ✅ Resolved | The exact niche LMSR's bounded-loss enables — and Onix makes it *risk-free* to seed via Lazy Pool auto-allocation + min-liquidity floor. Long-tail viability is a core selling point. |
+| 82 | **Market structure** | ✅ Resolved (defined) | Onix defines a clear structure: AMM pricing, parimutuel settlement, bonded oracles, DPoS-governed parameters, consensus-level ops. A coherent, novel market structure vs CLOB platforms. |
+| 83 | **Market surveillance** | 🟡 Roadmap / 🏛 Client | Full on-chain audit trail (`market_log`, every op consensus-validated) makes surveillance *possible* by anyone. Active surveillance/enforcement is a client + roadmap concern. |
+| 84 | **Network effects** | 🟡 Strategy | The flywheel (risk-free LP → depth → bettors → fees → more LP) is the intended network effect. Shared liquidity pools (roadmap) strengthen it. Go-to-market, not protocol mechanics. |
+| 85 | **Parlays** | ➖ Not needed | Multi-leg combined bets need conditional/combinatorial structure Onix omits. Out of scope (could be a client construction over independent markets). |
+| 86 | **Platform competition** | 🟡 Strategy | Competes on the unique "passive yield without impermanent loss" angle vs Polymarket/Kalshi (whitepaper §7.1 comparison table). Strategic positioning, not protocol logic. |
+| 87 | **Polymarket** | ⚪ Reference | The primary benchmark. Onix differs on every axis: CPMM/LMSR vs CLOB, zero LP risk vs inventory risk, native ops vs Polygon contracts, bonded oracle vs UMA, no CTF. Used as comparison, not adopted. |
+| 88 | **Regulatory arbitrage** | 🏛 Client | Jurisdictional-client model means each region builds its compliant (or permissionless) client on neutral VIZ rails. Regulatory positioning lives entirely at the client layer. |
+| 89 | **Regulatory classification** | 🏛 Client | Whether markets are swaps/gaming/securities is decided per-jurisdiction at the client layer; the protocol is classification-neutral (same `pm_*` ops for permissionless and regulated clients). Not a protocol concern. |
+
+---
+
+## Summary — what the Onix-on-VIZ design actually changes
+
+**Solved structurally (the core wins):**
+- LP-side **adverse selection, toxic flow, gap risk, impermanent loss, market-maker inventory risk** → all eliminated because winners are paid only from losers' forfeited stakes and LP principal is returned unconditionally (CPMM AM-GM proof; LMSR parimutuel settlement).
+- **LMSR's binary-market failure** → sidestepped by using CPMM for binary and confining LMSR to multi-outcome markets where the maker is not the counterparty.
+- **Liquidity provision, minimum viable liquidity, long-tail viability** → risk-free LP + Lazy Pool auto-allocation.
+- **Oracle design, dispute resolution, CVM** → bonded oracle + 14-metric reputation + stake-weighted committee disputes.
+- **Price discovery, arbitrage, conditional tokens** → price coherence is mathematical (`Σ price = 1`), so no order book, no split/merge, no internal arbitrage layer needed.
+
+**Not needed / deliberately omitted:** order book, CDA, bid-ask spread, semantic tick size, orderflow arbitrage, CTF split/merge, combinatorial/covariance/correlation/relative-value/parlay markets, UMA, peer prediction, LOX.
+
+**Pushed to the jurisdictional client layer:** federal preemption, regulatory classification/arbitrage, election-market legality, KYC/insider-trading enforcement, surveillance.
+
+**Newly implemented since the spec-era mapping (now live on-chain, HF14):** opt-in batch auctions + commit-reveal betting (binary), the opt-in leverage subsystem (position collateralization), the Lazy Pool, `endogeneity_tier`, on-chain creator bans, per-bettor/leverage settlement vops + plugin API, and lazy-pool governance weight in PM disputes + DAO committee-request voting.
+
+**On the VIZ roadmap (partial today):** shared/category liquidity pools (fixes fragmentation), automated data oracles (→ self-resolving objective markets), distribution markets, AI agents. *(Note: commit-reveal **dispute** voting is **not** on this list — it is deliberately rejected; dispute hearings stay public. Commit-reveal **betting** is already live.)*
+
+**Still open / behavioral (mitigated, not eliminated):** longshot/yes bias, market manipulation via depth, cross-platform arbitrage. The reflexivity family (endogeneity, reflexivity, KBC, hyperstition) has a concrete mitigation plan — see below.
+
+---
+
+## Mitigations for the reflexivity family
+
+Endogeneity, reflexivity, the Keynesian beauty contest (KBC), and hyperstition are **one root problem at different layers**: the market/price influences the outcome it measures. One small set of primitives addresses all four.
+
+### Root-cause map
+
+| Layer | Concept | Channel |
+|-------|---------|---------|
+| Forecaster-level | **Keynesian Beauty Contest** | herding to visible consensus in *relative/peer scoring* |
+| Market-level | **Endogeneity** | market *existence/visibility* changes behavior (category-specific) |
+| Market-level | **Reflexivity** | general price↔reality feedback; manipulation-as-propaganda |
+| By-design | **Hyperstition** | reflexivity used *intentionally* to coordinate an outcome |
+
+### Mitigation primitives
+
+| Primitive | Status | Fixes | Notes |
+|-----------|--------|-------|-------|
+| **Commit-reveal dispute voting** | **rejected (will NOT be built)** | KBC | A committee dispute is an **open public hearing**: `pm_dispute_vote` is a public ballot and **stays that way by design** — DAO credibility depends on transparent adjudication. Ballots are **revisable** until close (re-vote overwrites) so voters update honestly on new evidence; KBC residual is accepted (voters aren't paid for matching the majority). |
+| **Commit-reveal betting (batched)** | **live (opt-in, binary)** | endogeneity, reflexivity, info-asymmetry | `pm_commit_bet`/`pm_reveal_bet`/`pm_batch_settle` hide in-flight order direction/size so the public price doesn't "leak" during the betting window (kills the thermostat channel). Settled as a uniform-price **batch** (see below). |
+| **`endogeneity_tier` market field** | **live field** | endogeneity | Oracle tags tier 1 (econ data — clean), 2 (sports/scheduled), 3 (political/social — risky); UI surfaces the reflexive-risk level; clients can restrict tier-3. |
+| **Exogenous resolution (automated data oracles)** | roadmap (high) | endogeneity, reflexivity | Resolution bound to an external feed (BLS/Fed/sports API) the market can't influence → clean thermometer. |
+| **Prohibited-category list + creator ban** | **creator ban live on-chain**; list client-layer | harmful reflexivity, hyperstition | Block markets where YES creates a "constituency for the outcome" (assassination/"hit"/terror markets, propaganda markets). Enforced via on-chain creator ban (`pm_creator_ban_object`) + client category filter. |
+| **Deep risk-free LP liquidity** | core today | manipulation-driven reflexivity | The flywheel makes the book deep, so moving price for a "newsworthy" manipulated headline is expensive. |
+
+### KBC: why the market layer is already safe
+
+KBC is a pathology of **relative scoring** (pay for closeness to peers → herd to peers). Onix's market layer pays bettors against **external ground truth via parimutuel settlement** — you are rewarded for *deviating* from a wrong crowd price, not for matching it. So the bettor layer is structurally anti-KBC. The only relative/peer mechanism in the protocol is the **stake-weighted committee dispute vote**, and the residual KBC risk there is **accepted by design** — the dispute is kept an open public hearing (no commit-reveal) because DAO credibility depends on transparent adjudication; what bounds the risk instead is that voters aren't paid for matching the majority and ballots stay revisable as evidence comes in.
+
+### Commit-reveal vs. the CPMM `a·b=k` invariant
+
+CPMM is **path-dependent** (tokens depend on reserves at execution time), so commit-reveal *cannot* be done bet-by-bet against the live curve — reveal ordering would re-introduce MEV and leak price. The fix (and why §8.3 pairs commit-reveal with the batch-auction model): **stop updating the curve per-bet; update it once per epoch via a uniform-price batch settlement.**
+
+1. **Commit:** submit `hash(side, amount, salt, min_tokens)` and escrow `amount`.
+2. **Reveal:** reveal `(side, amount, salt)`; reveals are collected but **not applied** until epoch close (seeing others' reveals is useless — yours is already committed).
+3. **Settle once:** opposing flow (`A_in` vs `B_in`) nets between bettors at a single clearing price `p*`; only the **net residual** moves the AMM, so `k` is recomputed **once**. All A-fills get `p*`, all B-fills get `1−p*` → no intra-batch ordering advantage.
+4. **`min_tokens` floor:** since price is invisible at commit time, a per-bet token floor is mandatory; if `tokens < min_tokens` at settlement, the bet is rejected and refunded from escrow.
+5. **Anti-griefing:** non-reveal forfeits a penalty from escrow to the LP-fee/DAO pool, killing the "commit optionality, reveal only winners" attack.
+
+**The LP guarantee is untouched:** each batch settlement is still a valid CPMM transition, so AM-GM `reserve_a + reserve_b ≥ L` still holds. Only the *granularity* of curve updates changes (per-bet → per-epoch). Onix Multi is analogous via the aggregated LMSR cost function.
+
+**Phasing:** (1) uniform-price batch auctions first — already kill ordering MEV/front-running cheaply; (2) commit-reveal hiding on top — adds in-flight confidentiality, enabled selectively for tier-3 (endogeneity-sensitive) markets. **Both (1) and (2) are now implemented on-chain for binary markets** (multi still forces instant betting — no LMSR batch yet).
+
+### Net verdict change
+
+| Concept | Before | After |
+|---------|--------|-------|
+| Keynesian Beauty Contest | 🔴 Open | ✅ market / ⚪ dispute — votes public **by design** (no commit-reveal; revisable ballots, unpaid voters, pooled voters enfranchised) |
+| Endogeneity | 🔴 Open | 🟡 mitigated — `endogeneity_tier` + commit-reveal/batch **live**; exogenous oracles roadmap |
+| Reflexivity | 🔴 Open | 🟡 mitigated — creator ban **live on-chain**; category list client-layer |
+| Hyperstition | 🔴 Open | ➖ excluded by design (optional client product) |
diff --git a/docs/prediction-markets/index.md b/docs/prediction-markets/index.md
new file mode 100644
index 0000000000..76b5ebe344
--- /dev/null
+++ b/docs/prediction-markets/index.md
@@ -0,0 +1,53 @@
+---
+title: Prediction Markets (Onix) — overview & map
+description: The VIZ on-chain prediction-market stack — Onix protocol and the Forecaster client — with a full documentation tree from whitepaper to spec, objects, operations, workflows, and concept analysis.
+---
+
+# Prediction Markets on VIZ
+
+VIZ Ledger runs prediction markets as **first-class consensus operations** (`pm_*`), live since HF14.
+Two names you will see throughout:
+
+- **Onix** — the **protocol**: the on-chain market engine (CPMM binary + LMSR multi, parimutuel
+ zero-sum settlement, bonded oracles, lazy pool, opt-in leverage, batch / commit-reveal betting).
+- **Forecaster** — the **thin client** to that protocol on VIZ Ledger. It is a headless, platform-
+ independent front-end that lets **people anywhere in the world participate in the on-chain
+ prediction market** — create markets, bet, provide liquidity, oracle, and dispute — by signing
+ `pm_*` operations directly against public VIZ nodes. The protocol is neutral; Forecaster (and any
+ jurisdictional client built like it) is the access layer.
+
+## Documentation map
+
+```mermaid
+flowchart TD
+ ROOT["Prediction Markets (Onix)"]
+ ROOT --> OV["Overview — the one-page pitch (this stack at a glance)"]
+ ROOT --> WP["Whitepaper — the thesis: why LP-risk-free, the two market types, the flywheel"]
+ ROOT --> SP["Specification — formal mechanics + §17 On-Chain Object Model"]
+ ROOT --> OPS["Operations — the signed pm_* consensus operations"]
+ ROOT --> VOPS["Virtual Operations — deterministic vops emitted at settlement / by deadline"]
+ ROOT --> API["Plugin API — prediction_market_api read methods"]
+ ROOT --> WF["Workflows & diagrams — one canonical market traced through every role"]
+ ROOT --> CA["Concept analysis — 90 PM-theory concepts vs the live VIZ implementation"]
+```
+
+## Start here
+
+| Page | What it is |
+|------|-----------|
+| [Overview](./onix) | One-page positioning: AMM-priced parimutuel with structurally risk-free liquidity. |
+| [Whitepaper](./whitepaper) | The industry thesis — LP guarantee, Onix Binary (CPMM) + Onix Multi (LMSR), oracles, lazy pool, leverage, governance. |
+| [Specification](./specification) | Formal spec: parameters, state machine, pricing, settlement, disputes, lazy pool, leverage, and the **[On-Chain Object Model](./specification#17-on-chain-object-model)** (every `pm_*_object` and its lookup index). |
+| [Operations](../protocol/operations/prediction-markets) | The 21 signed consensus operations (`pm_create_market`, `pm_place_bet`, …). |
+| [Virtual Operations](../protocol/virtual-operations) | Deterministic virtual ops (`pm_payout`, `pm_market_accepted`, `pm_leverage_resolve`, `pm_batch_settle`, …). |
+| [Plugin API](../plugins/prediction-market-api) | `prediction_market_api` — read-only access to markets, bets, oracles, disputes, the lazy pool, and the median-voted parameters. |
+| [Workflows & interaction diagrams](./workflows) | One canonical binary market traced through every participant, with the zero-sum master ledger for normal and disputed resolution. |
+| [Concept analysis (Onix vs 90 concepts)](./concepts-analysis) | How the on-chain implementation maps onto the prediction-market theory atlas — what's solved, inherent, not needed, or roadmap. |
+
+## Governance
+
+All economic parameters are delegate **median-voted** and live in the `chain_properties_pm` struct —
+see [Chain Properties → Prediction-market parameters](../governance/chain-properties#pm-parameters).
+There is no hard fork to tune fees, penalties, lazy-pool, leverage, or batch/commit-reveal timing;
+three live kill-switches (`pm_commit_reveal_enabled`, `pm_lazy_pool_enabled`, `pm_leverage_enabled`)
+let the validator median disable a whole subsystem without a fork.
diff --git a/docs/prediction-markets/onix.md b/docs/prediction-markets/onix.md
new file mode 100644
index 0000000000..93460b8af4
--- /dev/null
+++ b/docs/prediction-markets/onix.md
@@ -0,0 +1,93 @@
+---
+title: Onix — AMM-Priced Parimutuel Prediction Markets
+description: Onix puts continuous AMM price discovery on top of parimutuel settlement, so prices move like an AMM while liquidity carries the risk profile of a tote — the market maker can never be bankrupted.
+---
+
+# Onix — AMM-Priced Parimutuel Prediction Markets
+
+> Continuous **AMM price discovery** on top of **parimutuel settlement** — the price moves like an
+> AMM, while liquidity carries the risk profile of a tote: **the market maker can never be
+> bankrupted.**
+
+::: info Onix & Forecaster
+**Onix** is the on-chain protocol. **Forecaster** is the thin client to it on VIZ Ledger — the headless,
+platform-independent access layer that lets people anywhere in the world participate in the on-chain
+prediction market by signing `pm_*` operations directly against public VIZ nodes. See the
+[section overview & map](./) for the full documentation tree.
+:::
+
+## The one idea
+
+Onix **decouples price from payout**:
+
+- **Price (discovery)** — a CPMM curve (binary) or LMSR-softmax (multi) updates a live probability on
+ every bet and assigns each bet a **weight** (its claim ticket).
+- **Payout (settlement)** — winners are paid **only** from the losers' forfeited stakes, split by
+ weight: pure **parimutuel**, strictly zero-sum (the protocol never mints a token).
+
+Everything distinctive about Onix follows from this split.
+
+## Why it matters — three things
+
+::: tip 1 · Liquidity that cannot be drained
+Because winners are paid from losers and never from LP principal, the liquidity provider **cannot be
+bankrupted** — no impermanent loss, no inventory risk, no death-by-sniper. Guaranteed *by construction*
+(AM–GM for CPMM, conservation for LMSR), not by insurance.
+:::
+
+::: tip 2 · Passive yield without IL — the Lazy Pool
+One deposit auto-spreads as silent liquidity across many markets and funds opt-in leverage, with
+MasterChef-style reward accounting. Earn prediction-market liquidity yield **without** picking markets
+or bearing impermanent loss.
+:::
+
+::: tip 3 · Native to the chain, zero-sum
+Markets are first-class consensus operations (`pm_*`), not smart contracts: censorship-resistant,
+composable, ~3-second blocks, no oracle bridge. The protocol never emits tokens — it only redistributes.
+:::
+
+## How a bet works
+
+1. **You bet** `X` on an outcome. `X` enters the curve; the curve returns your **weight** — more weight
+ if you bet earlier, before the price moves.
+2. **The board updates.** The live coefficient for a side is
+ `1 + opposing_pool × (1 − commission) / own_pool`, with the commission (oracle + creator + LP) already
+ baked in.
+3. **At resolution**, the losers' stakes (minus commission) are split among the winners by weight. Your
+ payout = your stake back **+** your share of the losing pool. LP principal is returned untouched.
+
+## How it compares
+
+| | CLOB / AMM (Polymarket, Kalshi) | Plain parimutuel (tote) | **Onix** |
+|---|---|---|---|
+| Live price | yes | no (pool ratio only) | **yes (CPMM / LMSR)** |
+| Odds locked at bet time | yes | no | no (honest parimutuel) |
+| LP / maker can be bankrupted | **yes** (IL, snipers, gap risk) | n/a | **no (structural)** |
+| Yield-bearing liquidity layer | fragile | none | **Lazy Pool, no IL** |
+| Lives in | contracts / backend | backend | **consensus (`pm_*`)** |
+| Token emission | sometimes | no | **no (zero-sum)** |
+
+## The honest tradeoff
+
+::: warning Odds are parimutuel — they drift until close
+Onix does **not** lock your coefficient at bet time. The board moves as money flows, and the final
+coefficient is known only at close — exactly like a tote. This is not a flaw to patch: the *only* way to
+lock odds is to have a counterparty bear the risk (a bookmaker, or an AMM LP that can lose). Onix's drift
+is the direct price of its LP guarantee — risk lives **between bettors**, so no one's liquidity can burn.
+:::
+
+## What's novel
+
+- **AMM weighting + parimutuel settlement** in one integrated engine — continuous price discovery
+ *without* maker inventory risk.
+- **Structural, provable LP safety** instead of insured or subsidized liquidity.
+- **A mutualized, yield-bearing liquidity layer** (the Lazy Pool) that also funds opt-in leverage —
+ liquidations run against pre-bet reserves so the pool is always made whole.
+- **Opt-in anti-MEV** (batch / commit-reveal betting) and **transparent governance** (bonded oracles,
+ public-hearing disputes with revisable votes) — all layered on the safe base without ever touching the
+ LP guarantee.
+
+## Learn more
+
+- Protocol operations — [Prediction Markets](../protocol/operations/prediction-markets)
+- Plugin API — [Prediction Market API](../plugins/prediction-market-api)
diff --git a/docs/prediction-markets/specification.md b/docs/prediction-markets/specification.md
new file mode 100644
index 0000000000..9b956e928d
--- /dev/null
+++ b/docs/prediction-markets/specification.md
@@ -0,0 +1,977 @@
+# Onix Protocol Specification
+
+**Version:** 2.0 (on-chain / HF14)
+**Status:** Formal technical specification — now realized as consensus operations on VIZ DLT
+
+---
+
+> **On-chain (HF14).** Implemented as first-class consensus operations (`pm_*`) on VIZ DLT and verified in
+> `consensus_sim`. **All percentages are basis points (bp): 10000 = 100.00%**; all durations are
+> governance parameters in **seconds / blocks**. Median-voted parameters live in the `chain_properties_pm`
+> struct (§3); per-market fields are the `pm_create_market` operation; all state is in the chainbase
+> objects of §17. Oracle fees use offer→quote (creator ceiling → oracle freezes its quote at accept,
+> emitting `pm_market_accepted`). Disputes have two modes — committee (`dispute_mode = 0`, default:
+> stake-weighted **public** `pm_dispute_vote`, revisable until close, Lazy-Pool stake counts) and account
+> (`dispute_mode = 1`: a named `dispute_resolver`).
+
+## Table of Contents
+
+1. [Definitions and Roles](#1-definitions-and-roles)
+2. [Currency and Precision](#2-currency-and-precision)
+3. [System Parameters](#3-system-parameters)
+4. [Market State Machine](#4-market-state-machine)
+5. [Onix Binary: Constant Product Market Maker](#5-onix-binary-constant-product-market-maker)
+6. [Onix Multi: LMSR with Parimutuel Settlement](#6-onix-multi-lmsr-with-parimutuel-settlement)
+7. [Fee Structure](#7-fee-structure)
+8. [Time Penalty for Late Bets](#8-time-penalty-for-late-bets)
+9. [Liquidity Provision](#9-liquidity-provision)
+10. [Resolution and Payout](#10-resolution-and-payout)
+11. [Bet Cancellation](#11-bet-cancellation)
+12. [Dispute System](#12-dispute-system)
+13. [Oracle Penalty for Missed Resolution](#13-oracle-penalty-for-missed-resolution)
+14. [Oracle Reputation Scoring](#14-oracle-reputation-scoring)
+15. [Position Transfers](#15-position-transfers)
+16. [Lazy Liquidity Pool](#16-lazy-liquidity-pool)
+16a. [Opt-In Leverage](#16a-opt-in-leverage-lazy-pool-funded)
+16b. [Batch / Commit-Reveal Betting](#16b-batch-commit-reveal-betting-anti-mev)
+17. [On-Chain Object Model](#17-on-chain-object-model)
+
+---
+
+## 1. Definitions and Roles
+
+| Role | Definition |
+|------|-----------|
+| **Market Creator** | Pays `pm_market_creation_fee` (`pm_create_market`); sets the question, outcomes, liquidity, fee ceilings, and timing parameters |
+| **Oracle** | Registers (fee: `pm_oracle_registration_fee`), deposits insurance (min: `pm_min_oracle_insurance`), quotes its fee terms in **basis points** (≤ creator ceiling) + fixed fee at acceptance, accepts/rejects markets, provides outcome decisions |
+| **Bettor** | Places bets on outcomes; receives tokens proportional to stake and current reserves |
+| **Liquidity Provider (LP)** | Supplies capital to market pools; earns time-weighted share of liquidity fees + penalty pool |
+| **Lazy Pool Provider** | Deposits VIZ into the Lazy Liquidity Pool with a lock period; pool auto-allocates to markets and distributes rewards via `reward_per_share` accumulator |
+| **Dispute Resolver** | Account mode only (`dispute_mode = 1`): the per-market `dispute_resolver` account arbitrates. Committee mode (`dispute_mode = 0`) uses no resolver — the SHARES electorate votes |
+| **DAO / committee fund** | The chain's existing committee fund. Receives `pm_market_creation_fee` and extra oracle penalties |
+
+---
+
+## 2. Currency and Precision
+
+All amounts are stored as integers with precision = 1/1000 (milli-VIZ). `1000` internal units = 1.000 VIZ.
+
+Time penalty values use precision = 1/1,000,000 (micro-units).
+
+---
+
+## 3. System Parameters
+
+### Median-voted parameters (`chain_properties_pm`)
+
+All economic parameters are delegate median-voted (no hard fork to tune) and live in the on-chain
+`chain_properties_pm` struct. Each delegate publishes its preferred values through the standard
+**`versioned_chain_properties_update_operation`** (op ID 46) — `chain_properties_pm` is the current
+(v5, HF14) version of that versioned struct — and the network applies the **per-field median** of the
+active delegates. The two risk-coverage knobs (`pm_listing_min_coverage_percent`,
+`pm_betting_min_coverage_percent`) are part of this same v5 struct and are tuned exactly the same way.
+**All percentages are basis points (bp, 10000 = 100.00%); durations are seconds or blocks** — except the
+two coverage knobs, which are percent-of-volume (100 = 1.0×). Exact defaults and ranges are in
+[Chain Properties](../governance/chain-properties#pm-parameters); the authoritative source is the struct itself.
+
+| Group | Parameters |
+|---|---|
+| Registration & floors | `pm_oracle_registration_fee`, `pm_min_oracle_insurance`, `pm_market_creation_fee`, `pm_min_liquidity`, `pm_max_outcomes`, `pm_max_market_duration` |
+| Fees & penalties (bp) | `pm_max_oracle_fee_percent`, `pm_oracle_penalty_percent`, `pm_no_contest_penalty_percent`, `pm_default_time_penalty_percent`, `pm_max_time_penalty` |
+| Acceptance window | `pm_oracle_accept_window_sec` (default 3600 = 1h; pending markets not accepted/rejected within this are voided by the cron — seed refunded, creation fee kept) |
+| Risk / coverage (% of volume) | `pm_listing_min_coverage_percent` (250 = 2.5×; markets covered below this are hidden from the default catalog, shown via `show_risky`), `pm_betting_min_coverage_percent` (150 = 1.5×; advisory client risk-confirm threshold, `≤` the listing one, not enforced on-chain) |
+| Disputes | `pm_dispute_fee`, `pm_dispute_grace_sec`, `pm_oracle_dispute_response_sec`, `pm_dispute_vote_period_sec`, `pm_dispute_auto_close_sec`, `pm_dispute_approve_min_percent` (bp), `pm_dispute_reward_multiplier` (bp) |
+| Lazy pool | `pm_lazy_pool_enabled`, `pm_lazy_alloc_percent`, `pm_lazy_max_total_alloc_percent`, `pm_lazy_recall_step_percent`, `pm_lazy_lock_sec`, `pm_lazy_emergency_penalty_percent`, `pm_lazy_min_liquidity_fee_percent` (default 200 = 2%; pool skips markets whose `liquidity_fee_percent` is below this reward floor) |
+| Leverage | `pm_leverage_enabled`, `pm_leverage_fund_percent`, `pm_leverage_max_per_position_bp`, `pm_leverage_max_position_ratio_percent`, `pm_leverage_min_market_liquidity`, `pm_leverage_safety_margin_percent`, `pm_leverage_max_slippage_percent`, `pm_leverage_m_factor_percent`, `pm_leverage_pool_profit_percent`, `pm_leverage_expiration_buffer_sec`, `pm_conversion_profit_cost_percent` |
+| Batch / commit-reveal | `pm_commit_reveal_enabled`, `pm_batch_epoch_blocks`, `pm_reveal_window_blocks`, `pm_commit_no_reveal_penalty_percent` (bp), `pm_min_batch_bet` |
+| Processing | `pm_processing_cap_per_block` |
+
+The recipient of `pm_market_creation_fee` and extra oracle penalties is the chain's existing committee/DAO
+fund — not a PM-specific account.
+
+### Per-market parameters (`pm_create_market` operation)
+
+Set by the creator at creation; the oracle fee fields are a **ceiling** the oracle quotes against at
+acceptance (offer→quote). Full field reference: [Prediction Market operations](../protocol/operations/prediction-markets).
+
+| Field | Description |
+|---|---|
+| `oracle`, `market_type` (0 binary / 1 multi), `outcomes`, `url` | market definition |
+| `oracle_fee_percent`, `oracle_fixed_fee` | oracle fee **ceiling** (bp + fixed); the oracle freezes its quote ≤ this (and ≤ median `pm_max_oracle_fee_percent`) at accept |
+| `creator_fee_percent`, `liquidity_fee_percent` | creator & LP fees (bp of the losers' pool) |
+| `liquidity`, `lmsr_b` | seed liquidity; `lmsr_b` for multi markets |
+| `betting_expiration`, `result_expiration` | timers |
+| `time_penalty_type`, `time_penalty_value`, `penalty_curve_type` | late-bet penalty shape |
+| `allow_early_resolution`, `allow_cancellation` | toggles |
+| `allow_batch`, `allow_instant_bet` | betting modes (binary) |
+| `endogeneity_tier` | 1 econ-data / 2 sports / 3 political (display/risk hint) |
+| `dispute_mode` (0 committee / 1 account), `dispute_resolver` | dispute routing |
+| `dispute_penalty_percent` | oracle penalty policy on a successful dispute (bp, signed) |
+| `metadata` | free-form client JSON (consensus-opaque; parsed off-chain) |
+
+---
+
+## 4. Market State Machine
+
+### States
+
+| Status | Name | Description |
+|--------|------|-------------|
+| -1 | Deleted | Oracle rejected, **or** the acceptance window (`pm_oracle_accept_window_sec`) expired; seed liquidity returned to creator (creation fee kept) |
+| 0 | Waiting | Awaiting oracle review |
+| 1 | Active | Accepting bets until `betting_expiration` |
+| 2 | Closed | Betting ended, awaiting oracle resolution |
+| 3 | Resolved | Outcome determined, payouts calculated |
+
+### Payout States
+
+| payout_status | Name | Description |
+|---------------|------|-------------|
+| 0 | Not calculated | Pre-resolution |
+| 1 | Calculated | Payouts pending (grace period active) |
+| 2 | Paid | All payouts processed |
+| 3 | Disputed | Dispute filed, payouts frozen |
+
+### Transitions
+
+```mermaid
+stateDiagram-v2
+ direction LR
+ state "Waiting (0)" as Waiting
+ state "Active (1)" as Active
+ state "Closed (2)" as Closed
+ state "Resolved (3)" as Resolved
+ state "Deleted (-1)" as Deleted
+ state "Paid out" as Paid
+ [*] --> Waiting
+ Waiting --> Active: oracle accepts
+ Waiting --> Deleted: oracle rejects
+ Waiting --> Deleted: accept window expires (pm_market_expired)
+ Active --> Closed: betting_expiration
+ Active --> Resolved: early resolution (if allowed)
+ Closed --> Resolved: oracle resolves
+ Resolved --> Paid: grace period (12h), no dispute
+ Deleted --> [*]
+ Paid --> [*]
+```
+
+**Preconditions:**
+
+| Transition | Preconditions |
+|-----------|---------------|
+| 0 → 1 | Oracle has insurance ≥ `min_oracle_insurance`; oracle accepts |
+| 0 → 1 (self-oracle) | Creator = oracle; insurance check; auto-approves at creation |
+| 0 → -1 | Oracle rejects; seed liquidity returned to creator |
+| 0 → -1 (expiry) | `now ≥ created_time + pm_oracle_accept_window_sec` with no oracle action; cron voids the market, refunds the seed (creation fee kept), emits `pm_market_expired` |
+| 1 → 3 | Oracle submits resolution with outcome (0, 1, or -1 for no-contest); `allow_early_resolution=1` or `time ≥ betting_expiration` |
+| 2 → 3 | Oracle submits resolution; `time ≤ result_expiration` |
+| 3 → paid | Grace period passed with no dispute; cron processes payouts |
+
+### Market Creation Flow
+
+1. Deduct `market_creation_fee` from creator → DAO fund (non-refundable)
+2. Record `oracle_fixed_fee` from oracle profile on market
+3. Lock `liquidity` from creator balance
+4. Initialize reserves: `reserve_a = floor(liquidity/2)`, `reserve_b = liquidity − reserve_a`
+5. Compute `k = reserve_a × reserve_b`
+6. If self-oracle: auto-approve to status=1 with insurance check
+7. If external oracle: enter status=0 and set `accept_deadline = created_time + pm_oracle_accept_window_sec`
+
+### Oracle Acceptance Flow
+
+A pending market must be resolved by its oracle within the acceptance window
+(`pm_oracle_accept_window_sec`, default 1h). Three outcomes:
+
+- **Accept** (status 0 → 1): (1) transfer `oracle_fixed_fee` from creator to oracle (skipped if
+ self-oracle); (2) increment oracle `markets_accepted`; (3) update `last_active_time`; (4) trigger
+ Lazy Pool auto-allocation (if the pool has free balance **and** the market's `liquidity_fee_percent
+ ≥ pm_lazy_min_liquidity_fee_percent`).
+- **Reject** (status 0 → -1): seed liquidity refunded to creator; no vop.
+- **Expiry** (status 0 → -1): if neither happens by `accept_deadline`, the per-block cron voids the
+ market, refunds the seed liquidity (**not** the creation fee), and emits `pm_market_expired`.
+
+### Audit Trail
+
+Every state-changing action is a consensus operation or virtual operation, permanently recorded in the block log and queryable via `account_history`. Bets, cancels, liquidity add/withdraw, accept/reject, resolution, dispute, dispute-resolve, payout, and penalty all appear as `pm_*` operations/virtual-operations, alongside the market reserves they touched.
+
+---
+
+## 5. Onix Binary: Constant Product Market Maker
+
+### Invariant
+
+```
+k = reserve_a × reserve_b
+```
+
+`k` changes only on liquidity add/withdraw operations.
+
+### Bet Placement (side A)
+
+```
+new_reserve_b = reserve_b + amount
+new_reserve_a = floor(k / new_reserve_b)
+tokens_received = reserve_a − new_reserve_a
+price = amount × 1,000,000 / tokens_received
+```
+
+Symmetric for side B (swap a/b).
+
+### Slippage Protection
+
+Optional `min_tokens` parameter on `place-bet`. If `tokens_received < min_tokens`, transaction rejected.
+
+### Market Initialization
+
+```
+reserve_a = floor(liquidity / 2)
+reserve_b = liquidity − reserve_a
+k = reserve_a × reserve_b
+```
+
+Minimum initial liquidity: 100,000 mVIZ (100 VIZ).
+
+### Weight (Token) Semantics
+
+- `weight` = number of outcome tokens received by bettor (set by the CPMM at bet time)
+- `weight` is a **relative claim**, not a VIZ-denominated payout. Settlement is **parimutuel** (identical to Onix Multi): winners receive their stake back plus a proportional share of the losers' pool, by weight.
+- If bet on side A and outcome A wins: `payout = bet_amount + (weight / total_winning_weight) × winners_pool − time_penalty_on_profit`
+- If outcome A loses: payout = 0 (stake forfeited into the winners' pool)
+
+CPMM is the **pricing engine** (probability + weight assignment); it no longer gates payout. This makes the two market types share one settlement model: *the AMM assigns weights (CPMM for binary, LMSR for multi); losers fund winners pro-rata by weight.*
+
+### Price Display
+
+```
+implied_probability_A = reserve_b / (reserve_a + reserve_b) × 100%
+implied_probability_B = reserve_a / (reserve_a + reserve_b) × 100%
+```
+
+### LP Principal Guarantee (Proof)
+
+Under parimutuel settlement the guarantee is exact and does not rely on the curve geometry:
+
+```
+Money OUT = L (LP principal) + Σ(winning bet_amount) + winners_pool + fees
+ = L + winning_bets + (losers_sum − fees) + fees
+ = L + winning_bets + losing_bets = L + all_bets = Money IN
+```
+
+Total payout is capped at `losers_sum` regardless of weights, so LP principal `L` is returned unconditionally and winners are funded entirely by losers. (The legacy AM-GM bound `reserve_a + reserve_b ≥ L` is no longer needed for solvency; it remains a property of the pricing curve.)
+
+---
+
+## 6. Onix Multi: LMSR with Parimutuel Settlement
+
+### Price Function (Softmax)
+
+For N outcomes with quantity parameters q_1, ..., q_N and liquidity parameter b:
+
+```
+price(i) = exp(q_i / b) / Σ_j exp(q_j / b)
+```
+
+**Invariant:** `Σ_i price(i) = 1` (by definition of softmax).
+
+### Cost Function
+
+```
+C(q) = b × ln(Σ_j exp(q_j / b))
+```
+
+Cost to buy Δ tokens on outcome i:
+
+```
+cost = C(q + Δ·e_i) − C(q)
+ = b × [ln(Σ_j exp(q'_j / b)) − ln(Σ_j exp(q_j / b))]
+where q'_i = q_i + Δ, all other q'_j = q_j
+```
+
+Numerical stability (log-sum-exp trick):
+
+```
+ln(Σ exp(x_j)) = max(x) + ln(Σ exp(x_j − max(x)))
+```
+
+### Liquidity Parameter
+
+```
+b = S / ln(N)
+```
+
+where S = LP subsidy deposit, N = number of outcomes.
+
+### Settlement (at resolution)
+
+```
+1. Oracle declares winning outcome
+2. losers_sum = Σ bet_amount for all non-winning bets
+3. oracle_fee = floor(losers_sum × oracle_fee_percent / 10000)
+4. creator_fee = floor(losers_sum × creator_fee_percent / 10000)
+5. liq_fee = floor(losers_sum × liquidity_fee_percent / 10000)
+6. winners_pool = losers_sum − oracle_fee − creator_fee − liq_fee
+7. For each winning bettor:
+ payout = bet_amount + (their_tokens / total_winning_tokens × winners_pool) − time_penalty
+8. LP subsidy returned unconditionally
+9. LP earns time-weighted share of liq_fee
+```
+
+### LP Principal Guarantee (Proof)
+
+1. LP deposits S VIZ as subsidy. This sets b = S / ln(N).
+2. During betting, users pay VIZ → receive tokens. VIZ accumulates as betting pool.
+3. At resolution: losers forfeit 100% → `losers_sum`. Winners paid from `losers_sum` (not from subsidy).
+4. LP subsidy S returned **unconditionally** — it is architecturally separate from the payout flow.
+
+### Edge Cases
+
+| Scenario | Outcome |
+|----------|---------|
+| All bets on winning outcome | `losers_sum=0`, `winners_pool=0`. Every bettor gets back `bet_amount`. LP subsidy returned. |
+| No bets on winning outcome | `losers_sum=total_bets`. Undistributed `winners_pool` → LP bonus. |
+| Zero-volume market | LP subsidy returned. No fees, no payouts. |
+| Single bettor wins | Bettor receives `bet_amount + winners_pool`. LP subsidy returned. |
+
+### Operations
+
+| Operation | Description |
+|-----------|-------------|
+| `pm_create_market_multi { oracle, outcomes, liquidity, fees, ... }` | Create N-outcome market |
+| `pm_place_bet_multi { market, outcome_index, amount, min_tokens }` | Buy tokens for outcome |
+| `pm_cancel_bet_multi { bet_id, min_return }` | Sell tokens back via reverse LMSR |
+| `pm_add_liquidity_multi { market, amount }` | Add LP subsidy (increases b) |
+| `pm_withdraw_liquidity_multi { liquidity_id }` | Withdraw LP subsidy (min floor enforced) |
+| `pm_resolve_multi { market, winning_outcome }` | Oracle declares winner, triggers settlement |
+
+Binary markets (N=2) use Onix Binary (CPMM). LMSR used only for N > 2.
+
+---
+
+## 7. Fee Structure
+
+### Resolution-Time Fee Computation
+
+All percentage fees computed at resolution from **losing side's total volume**:
+
+```
+losers_sum = Σ bet_amount for all losing bets
+
+oracle_fee = floor(losers_sum × oracle_fee_percent / 10000)
+creator_fee = floor(losers_sum × creator_fee_percent / 10000)
+liquidity_fee = floor(losers_sum × liquidity_fee_percent / 10000)
+winners_pool = losers_sum − oracle_fee − creator_fee − liquidity_fee
+```
+
+Fees are NOT deducted from bets at placement time. Full bet amount enters CPMM/LMSR reserves.
+
+### Oracle Fixed Fee
+
+One-time fee per market. Set by oracle on profile. Paid by creator to oracle at market acceptance. Skipped entirely for self-oracle markets (no balance operation occurs).
+
+### Fee Tracking Fields
+
+- `oracle_fee_earned` — not used at resolution; fee computed from losers_sum
+- `liquidity_fee_earned` — cumulative LP fees already paid to early-withdrawn LPs; at resolution: `LP fee pool = max(0, floor(losers_sum × liquidity_fee_percent / 10000) − liquidity_fee_earned) + penalty_pool`
+- Per-bet `oracle_fee` and `liquidity_fee` recorded for audit; not accumulated on market
+
+### Rounding
+
+All calculations use `floor()`. Undistributed dust (< 1 mVIZ) sent to DAO fund during final payout.
+
+---
+
+## 8. Time Penalty for Late Bets
+
+### Penalty Window
+
+| Type | Window Calculation |
+|------|-------------------|
+| Fixed (type=0) | `penalty_window = time_penalty_value` seconds before expiration |
+| Percentage (type=1) | `penalty_window = time_penalty_value / 100 × (betting_expiration − market_creation_time)` |
+
+### Penalty Calculation
+
+```
+time_to_expiration = betting_expiration − current_time
+
+if time_to_expiration < penalty_window:
+ ratio = 1 − (time_to_expiration / penalty_window)
+
+ if penalty_curve_type == 1: // quadratic
+ penalty_ratio = ratio × ratio
+ else: // linear
+ penalty_ratio = ratio
+
+ time_penalty = floor(penalty_ratio × max_time_penalty)
+else:
+ time_penalty = 0
+```
+
+### Application at Payout (Profit-Only)
+
+```
+profit = floor(winners_pool × weight / total_winning_weight) // parimutuel share of losers' pool
+penalty_deduction = floor(profit × time_penalty / 1,000,000)
+net_payout = bet_amount + profit − penalty_deduction
+```
+
+**Invariant:** `net_payout ≥ bet_amount` — the penalty applies only to the profit share, so winners always receive at least their principal. (Identical for Onix Binary and Onix Multi.)
+
+---
+
+## 9. Liquidity Provision
+
+### Adding Liquidity
+
+```
+add_a = amount × reserve_a / (reserve_a + reserve_b)
+add_b = amount − add_a
+new_reserve_a = reserve_a + add_a
+new_reserve_b = reserve_b + add_b
+new_k = new_reserve_a × new_reserve_b
+```
+
+Records `sec_to_expiration = betting_expiration − current_time` at deposit time.
+
+### Time-Weighted Fee Distribution (at resolution)
+
+```
+fee_pool = remaining_liquidity_fee + total_penalty_pool
+
+weight_i = amount_i × max(1, sec_to_expiration_i)
+total_weight = Σ weight_i
+fee_share_i = floor(fee_pool × weight_i / total_weight)
+lp_payout_i = principal_i + fee_share_i
+```
+
+Each deposit is an independent position. Multiple deposits by the same user are tracked separately.
+
+### Early Withdrawal
+
+**Preconditions:** market status=1, `time < betting_expiration`, `resulting liquidity_sum ≥ 100,000 mVIZ`.
+
+```
+// Fractional withdrawal
+fraction = withdraw_amount / lp_amount
+withdraw_weight_a = floor(weight_a × fraction)
+withdraw_weight_b = floor(weight_b × fraction)
+
+// Reverse reserves
+new_reserve_a = reserve_a − withdraw_weight_a
+new_reserve_b = reserve_b − withdraw_weight_b
+new_k = new_reserve_a × new_reserve_b
+
+// Time-ratio discount
+time_served = current_time − lp_deposit_time
+market_duration = betting_expiration − market_creation_time
+time_ratio = min(1, time_served / market_duration)
+
+// Fee share (conservative: min of both sides)
+fee_from_a = floor(a_bets_sum × liquidity_fee_percent / 10000)
+fee_from_b = floor(b_bets_sum × liquidity_fee_percent / 10000)
+estimated_pool = min(fee_from_a, fee_from_b) − already_paid_to_early_lps
+lp_tw = withdraw_amount × max(1, sec_to_expiration)
+total_tw = Σ (active LP time-weights)
+raw_fee_share = floor(estimated_pool × lp_tw / total_tw)
+fee_share = floor(raw_fee_share × time_ratio)
+
+returned = withdraw_amount + fee_share
+```
+
+**Post-expiration lock:** LP withdrawal blocked when `time ≥ betting_expiration`. All LP positions locked until resolution.
+
+### Principal Safety on Early Withdrawal
+
+Withdrawal subtracts original `weight_a` and `weight_b` (not proportional share of current reserves). If `reserve_a < weight_a` or `reserve_b < weight_b`, withdrawal is **blocked**.
+
+### Creator as First LP
+
+Market creator is automatically the first LP. Their `sec_to_expiration` equals the full market duration, giving maximum time-weight.
+
+---
+
+## 10. Resolution and Payout
+
+### Payout Priority Order
+
+| Priority | Type | Recipient | Amount |
+|----------|------|-----------|--------|
+| 1 | Oracle fee (2) | Oracle | `floor(losers_sum × oracle_fee_percent / 10000)` |
+| 1.5 | Creator fee (7) | Creator | `floor(losers_sum × creator_fee_percent / 10000)` |
+| 2 | Creator LP (1) | Creator | `principal + time-weighted fee share` |
+| 3 | LP return (1) | LPs | `principal + time-weighted fee share` |
+| 4 | Winner bets (0) | Winners | `bet_amount + floor(winners_pool × weight / total_winning_weight) − penalty_deduction` (parimutuel) |
+| 5 | Dispute refund (5) | Dispute participants | (if applicable) |
+| 6 | Oracle penalty bonus (6) | All participants | (if oracle penalized) |
+
+### Losing Side
+
+Payout = 0. Stakes absorbed into reserve pool.
+
+### Zero-Volume Markets
+
+LPs receive full principal. Oracle receives fixed fee (if any). All fee accumulators remain 0.
+
+---
+
+## 11. Bet Cancellation
+
+### Preconditions
+
+| Condition | Check |
+|-----------|-------|
+| Bet is active | `bet.status == 0` |
+| User owns bet | `bet.user == current_user.id` |
+| Market active | `market.status == 1` |
+| Betting open | `current_time < market.betting_expiration` |
+| Cancellation allowed | `market.allow_cancellation == 1` |
+
+### Reverse CPMM Mechanics
+
+For bet on side A (side=0):
+
+```
+new_reserve_a = reserve_a + tokens
+new_reserve_b = floor(k / new_reserve_a)
+amount_returned = reserve_b − new_reserve_b
+if amount_returned <= 0: amount_returned = 0
+```
+
+Symmetric for side B.
+
+### Slippage Protection
+
+Optional `min_return` parameter. If `amount_returned < min_return`, transaction rejected.
+
+### State Changes (atomic)
+
+1. Bet status → 1 (cancelled), `returned_amount` recorded
+2. Market reserves updated
+3. Market bet sums decremented by original bet amount
+4. User balance increased by `amount_returned`, `bets_balance` decreased
+5. History entry (type=4) logged
+6. Market log entry with before/after reserves
+
+---
+
+## 12. Dispute System
+
+### Filing Preconditions
+
+- Filer has placed a bet on the market
+- Within `pm_dispute_grace_sec` after resolution
+- Routing by mode: **committee** (`dispute_mode = 0`) — no resolver account needed, the SHARES electorate votes via `pm_dispute_vote` (public, revisable until `voting_end_time`, weight = `effective_vesting_shares` + Lazy-Pool stake→shares), tallied by the `pm_dispute_finalize` cron; **account** (`dispute_mode = 1`) — the market's named `dispute_resolver` issues `pm_dispute_resolve`
+- No open dispute on market
+- Filer pays `pm_dispute_fee`
+
+### Oracle Response
+
+Mandatory within `pm_oracle_dispute_response_sec`. If missed, `pm_dispute_fee` is auto-slashed from insurance and recorded on the oracle object.
+
+The oracle posts its rebuttal with **`pm_dispute_oracle_respond`** (op ID 98). Because a dispute is an open public hearing, the text is stored **on the dispute object** (`oracle_response` + `oracle_response_time`, readable via `get_dispute`) so every committee voter or account resolver can weigh it before deciding. Only the market's oracle may respond, only while the dispute is open and `now ≤ oracle_response_deadline`; re-posting overwrites the previous response.
+
+### Dispute Lifecycle
+
+```
+Resolution (T=0) → Grace period (T to T+12h) → Dispute filed (T≤12h)
+ → Oracle response (12h window) → Resolver decision (up to 14 days)
+ → After verdict: recalculate or unfreeze → Auto-payout after new grace period
+ → Auto-close fallback (T+14 days): full refund + oracle penalty
+```
+
+### Dispute Upheld (Oracle Wrong — overturned)
+
+The disputer's reward is a carve-out of the slash; the **rest of the slash funds the winning bettors**
+(via `forfeit_pool`), not a resolver and not the DAO. **Neither the committee voters nor the account-mode
+resolver are paid** — voting/resolving is an unpaid duty.
+
+```
+reward_target = floor(dispute_fee × pm_dispute_reward_multiplier / 10000) // bp; 30000 = ×3
+bonus = max(0, reward_target − dispute_fee), capped at the slash
+
+1. Disputer ← dispute_fee (escrow returned) + bonus // bonus drawn from the slashed insurance
+2. forfeit_pool += (slash − bonus) // → winners, through winners_pool at settlement
+```
+
+Slash size: committee mode scales the oracle's `dispute_penalty_percent` by `consensus_strength`; account
+mode uses the resolver's `penalty_amount` (both capped at the remaining insurance).
+
+### Dispute Rejected (Oracle Right — upheld)
+
+```
+Disputer forfeits the whole dispute_fee → oracle (100%, compensation). // no resolver/DAO split
+```
+
+### Recalculation Process (Oracle Wrong)
+
+1. Validate penalty (capped at remaining insurance)
+2. Disputer reward (fee + bonus) paid; slash remainder → `forfeit_pool` (winners)
+3. Oracle insurance slashed
+5. Bans applied (if requested)
+6. Delete all existing non-paid payouts
+7. Flip winning outcome (A↔B)
+8. Regenerate payouts from scratch with corrected outcome
+9. Audit trail recorded
+
+### Resolver Powers — bans are a compliance/regulator feature (account mode only)
+
+The sanctions below are fields of the **account-mode** `pm_dispute_resolve` (op ID 80), issued by the
+market's named `dispute_resolver`. When that resolver is a **regulator or a licensed arbitrator**, this is
+how it enforces off-chain rules: in a single verdict it can slash insurance **and bar both the oracle and
+the market creator** from the platform, temporarily or permanently. **Committee/DAO mode
+(`dispute_mode = 0`) has no ban power by design** — a public hearing only slashes insurance (scaled by
+consensus strength) and adjusts reputation via `pm_dispute_finalize`; it never bans.
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `penalty_amount` | mVIZ | Additional insurance slash (0 to remaining) → DAO fund |
+| `ban_oracle` | 0/1 | Ban oracle |
+| `ban_oracle_until` | unix ts / 0 | 0=permanent, >0=expires |
+| `ban_creator` | 0/1 | Ban creator |
+| `ban_creator_until` | unix ts / 0 | 0=permanent, >0=expires |
+
+A ban records the issuing `resolver` in the target's `banned_by`. **Lifting a ban:** the same resolver
+may lift it **early** with **`pm_unban`** (op ID 99, `unban_oracle` / `unban_creator`); otherwise it simply
+lapses at `banned_until`, at which point the per-block cron clears it and emits the **`pm_ban_expired`**
+virtual op (ID 100) so history/indexers observe the lift.
+
+### Auto-Close (14-day fallback)
+
+| Action | Description |
+|--------|-------------|
+| Plaintiff | Dispute fee refunded |
+| Oracle | `dispute_fee` slashed from insurance |
+| Bets | All refunded (original amounts) |
+| LPs | All refunded (principal only) |
+| Penalty distribution | Slashed amount distributed proportionally to all participants |
+| Dispute status | Set to 3 (auto-closed) |
+
+### No-Contest Declaration
+
+Oracle calls `oracle-no-contest` with `market_id` and `reason`.
+
+1. All bets → pending refund payouts (full original amount)
+2. All LP positions → pending refund payouts (principal only)
+3. Penalty: `oracle_no_contest_penalty_percent`% of `dispute_fee` from insurance
+4. Penalty distributed proportionally to participants
+5. Market: `resolved_outcome = -1`, `payout_status = 1`
+6. Grace period starts (disputable)
+
+### 3-Outcome Resolution (No-Contest Dispute)
+
+Resolver chooses one of:
+- `correct_outcome = 0` — A wins (recalculate payouts)
+- `correct_outcome = 1` — B wins (recalculate payouts)
+- `correct_outcome = -1` — Confirm no-contest (keep refund payouts)
+
+If oracle wrong: pending refund payouts deleted, replaced with correct winner payouts. Standard dispute penalties apply.
+
+---
+
+## 13. Oracle Penalty for Missed Resolution
+
+If oracle fails to resolve by `result_expiration`:
+
+```
+penalty_amount = floor(oracle_insurance × oracle_penalty_percent / 100)
+```
+
+### Distribution
+
+```
+stakes[user_id] += bet_amount (for each active bet)
+stakes[user_id] += liquidity_amount (for each active LP position)
+total_stakes = Σ stakes[user_id]
+
+bonus_i = floor(penalty_amount × stakes[user_id] / total_stakes)
+```
+
+Each participant receives: full refund (principal) + proportional bonus.
+
+Market finalized: status=3, payout_status=2.
+
+---
+
+## 14. Oracle Reputation Scoring
+
+### Raw Metrics (14 counters per oracle)
+
+| Metric | Type | Source |
+|--------|------|--------|
+| `markets_accepted` | counter | oracle-accept-market |
+| `markets_resolved` | counter | resolve-market |
+| `markets_no_contest` | counter | oracle-no-contest |
+| `markets_missed` | counter | cron (missed deadline) |
+| `disputes_received` | counter | create-dispute |
+| `disputes_lost` | counter | resolve-dispute (status=1) |
+| `disputes_won` | counter | resolve-dispute (status=2) |
+| `disputes_auto_closed` | counter | cron (14-day auto-close) |
+| `dispute_responses_missed` | counter | cron (12h response deadline) |
+| `total_volume_resolved` | mVIZ | resolve-market (sum of bets_sum) |
+| `total_insurance_slashed` | mVIZ | all penalty events |
+| `avg_resolution_time` | seconds | resolve-market |
+| `bans_received` | counter | resolve-dispute |
+| `active_since` | timestamp | register-oracle |
+| `last_active_time` | timestamp | accept/resolve/no-contest |
+
+### Derived Rates
+
+Denominator: `total_outcomes = markets_resolved + markets_no_contest + markets_missed`
+
+| Rate | Formula |
+|------|---------|
+| `resolution_rate` | `markets_resolved / total_outcomes` |
+| `dispute_loss_rate` | `disputes_lost / disputes_received` |
+| `no_contest_rate` | `markets_no_contest / total_outcomes` |
+| `deadline_miss_rate` | `markets_missed / total_outcomes` |
+| `dispute_response_rate` | `1 − (dispute_responses_missed / disputes_received)` |
+
+### Reliability Score (0–100)
+
+```
+reliability_score = clamp(0, 100,
+ BASE_SCORE
+ − W_DISPUTE_LOSS × dispute_loss_rate × 100
+ − W_NO_CONTEST × excess_no_contest × 100
+ − W_DEADLINE_MISS × deadline_miss_rate × 100
+ − W_NO_RESPONSE × (1 − dispute_response_rate) × 100
+ + W_VOLUME_BONUS × volume_tier
+ + W_EXPERIENCE × experience_tier × freshness_multiplier
+ − W_BAN_PENALTY × bans_received
+)
+```
+
+Where `excess_no_contest = max(0, no_contest_rate − 0.10)`.
+
+### Weight Defaults
+
+| Weight | Value |
+|--------|-------|
+| BASE_SCORE | 50 |
+| W_DISPUTE_LOSS | 0.40 |
+| W_NO_CONTEST | 0.10 |
+| W_DEADLINE_MISS | 0.20 |
+| W_NO_RESPONSE | 0.15 |
+| W_VOLUME_BONUS | 0–25 (tiered: ≥10K→+5, ≥100K→+10, ≥500K→+15, ≥1M→+20, ≥5M→+25) |
+| W_EXPERIENCE | 0–25 (tiered: ≥7d→+5, ≥30d→+10, ≥90d→+15, ≥180d→+20, ≥365d→+25) |
+| W_BAN_PENALTY | 15 per ban |
+
+### Freshness Decay
+
+| Days since last active | Multiplier |
+|----------------------|-----------|
+| ≤ 30 | 1.00 |
+| 31–90 | 0.75 |
+| 91–180 | 0.50 |
+| > 180 | 0.25 |
+
+### Composite Trust Score
+
+```
+trust_score = reliability_score × risk_factor
+```
+
+| Risk score (insurance/bets) | risk_factor |
+|---------------------------|-------------|
+| ≥ 3.0× | 1.00 |
+| ≥ 2.0× | 0.95 |
+| ≥ 1.0× | 0.85 |
+| < 1.0× | 0.70 |
+
+### New Oracle Detection
+
+`total_outcomes < 5` → `is_new = true`. Distinct badge in UI.
+
+Score computed on read via `compute_oracle_reliability_score()`, not stored.
+
+---
+
+## 15. Position Transfers
+
+### Operation
+
+```
+pm_transfer_position { bet_id, to_user, amount, memo }
+```
+
+- Transfer all or part of a bet's tokens to another account
+- Transferred tokens retain original market and outcome
+- Payout goes to current holder at resolution
+- No slippage, no market impact — pure record reassignment
+- Works for both Onix Binary and Onix Multi positions
+
+### Memo Privacy Model
+
+| Mode | Format | Visibility |
+|------|--------|------------|
+| Plaintext | String not starting with `#` | Public on-chain |
+| Encrypted | String starting with `#` | Private — only sender and recipient can decrypt |
+
+Encryption: ECIES shared-secret `ECDH(sender_memo_private, recipient_memo_public)` using VIZ account memo keys (standard Graphene model). Client-side encryption/decryption.
+
+---
+
+## 16. Lazy Liquidity Pool
+
+### Parameters
+
+| Median-voted parameter | Role |
+|---------|-------------|
+| `pm_lazy_pool_enabled` | pool kill-switch |
+| `pm_lazy_alloc_percent` | share of free balance allocated per market (bp) |
+| `pm_lazy_max_total_alloc_percent` | cap on the pool fraction across active markets (bp) |
+| `pm_lazy_recall_step_percent` | graduated-recall step on idle markets (bp) |
+| `pm_lazy_lock_sec` | deposit lock period (seconds) |
+| `pm_lazy_emergency_penalty_percent` | penalty on locked profit for emergency withdrawal (bp) |
+| `pm_lazy_min_liquidity_fee_percent` | minimum market `liquidity_fee_percent` (bp) for the pool to allocate; below it the market gets no pool liquidity (reward floor) |
+| `pm_min_liquidity` | minimum allocation per market (also the market seed floor) |
+
+### Deposit
+
+- First depositor: `shares = amount`
+- Subsequent: `new_shares = amount × total_shares / free_balance`
+- Lock timer: `unlock_time = now + pm_lazy_lock_sec`
+- Reward settlement before share calculation: `pending += shares × (pool.rps − user.snapshot) / PRECISION`
+
+### Auto-Allocation
+
+On market activation (status → 1):
+
+```
+alloc_amount = free_balance × allocation_percent / 100
+× (1 − active_market_penalty_pct / 100) ^ oracle_active_market_count
+× (1 − fault_penalty_pct / 100) ^ oracle_active_fault_stamps
+```
+
+Reward-floor gate (checked first): if the market's `liquidity_fee_percent <
+pm_lazy_min_liquidity_fee_percent`, the pool allocates **nothing** — it only subsidizes markets whose
+LP fee pays it enough. The creator's own seed remains the sole liquidity.
+
+Checks: `alloc_amount ≥ min_market_allocation`, `allocated + alloc_amount ≤ total × max_total_allocation / 100`.
+
+Pool LP inserted with `user=0`. Participates identically in time-weighted fee distribution.
+
+### Reward Distribution (Lazy Accounting)
+
+On market resolution with pool LP profit:
+
+```
+profit = lp_return − allocation_amount
+if profit > 0 AND total_shares > 0:
+ pool.reward_per_share += profit × PRECISION / total_shares
+```
+
+User reward (computed on read):
+
+```
+live_reward = pending_rewards + shares × (pool.rps − user.snapshot) / PRECISION
+```
+
+### Planned Withdrawal
+
+From consolidated unlocked record (full or partial):
+
+```
+1. Run unlock consolidation
+2. Settle rewards: pending += shares × (rps − snapshot) / PRECISION
+3. Share value = shares_to_burn × free_balance / total_shares
+4. Reward portion = pending_rewards × withdraw_percent / 100
+5. Total payout = share value + reward portion
+```
+
+### Emergency Withdrawal
+
+All deposits (locked + unlocked):
+
+```
+1. Settle rewards
+2. total_value = shares × free_balance / total_shares + pending_rewards
+3. profit = total_value − principal_deposited
+4. if profit > 0: penalty = profit × (locked_shares / total_shares) × emergency_penalty / 100
+5. Penalty → pool reward_per_share
+6. User receives: total_value − penalty
+```
+
+### Opportunity-Cost Protection
+
+> **Governance vs. fixed.** Only the recall **step size** is median-voted — `pm_lazy_recall_step_percent`
+> (how much is pulled per idle step). The rest is **hardcoded** and needs a hardfork to change: the
+> **10-step** division (`window/10`, `check_step ≥ 10`), the idle test (a step is idle when *no new bets*
+> arrived since the last check — `bets_sum ≤ bets_sum_at_check`), and the **5% per-active-market penalty**
+> (`alloc × 95/100`). The fault-stamp penalty and its expiry window are likewise hardcoded.
+
+**A. Graduated Recall:** Market duration divided into **10 fixed steps**. At each step, if **no new bets** arrived since the last check, recall `pm_lazy_recall_step_percent` (bp, governance) of the current allocation back to the pool.
+
+**B. Active Market Penalty:** `factor = (1 − 5% ) ^ active_market_count` — a hardcoded recursive 5% reduction per concurrent active market from the same oracle.
+
+**C. Fault Stamps:** On bad market outcomes (no-contest, missed deadline, zero volume, dispute loss, no response, auto-close), the oracle gets a fault stamp that auto-expires after a fixed clean-operation window; each active stamp further reduces its allocation. (Penalty size + expiry are hardcoded.)
+
+---
+
+## 16a. Opt-In Leverage (Lazy-Pool-Funded)
+
+Live since HF14; opt-in, governed by median kill-switch `pm_leverage_enabled` (default off).
+
+- **Open** (`pm_leverage_open`) — a bettor posts collateral; the Lazy Pool **loans** the margin from
+ `free_balance` (capped by `leverage_fund_used`; checked against `pm_leverage_fund_percent`,
+ `…_max_per_position_bp`, `…_max_position_ratio_percent`, `…_min_market_liquidity` at open time). No
+ token emission — the position is fully collateralized from the system's view. A leveraged open does
+ **not** create a `pm_bet`; the curve weight is held on the `pm_leverage_position_object`.
+- **Liquidation** — runs against **pre-bet reserves** so the pool recovers `min(cancel_value,
+ obligation) ≥ loan`: opposing-bet cascade (`pm_place_bet`) and settlement force-close are always
+ full-recovery (loan + interest → pool); the **only** bounded bad-debt path is a same-side
+ `pm_cancel_bet` (Case B). The cascade is **not** gated by `pm_leverage_enabled` (the flag blocks only
+ new opens), so disabling leverage never strips protection from open positions.
+- **Virtual ops** — `pm_leverage_resolve` (force-close at settlement, with outcome + leverage),
+ `pm_leverage_liquidate` (mid-market, `reason` 0 opposing / 1 cancel).
+- **Governance weight** — Lazy-Pool depositors keep their PM-dispute and DAO-committee vote weight
+ (pool NAV → vesting-shares via `get_vesting_share_price`, HF14-gated).
+
+API: `get_account_leverage_positions`, `get_market_leverage_positions`, `get_lazy_pool`.
+
+## 16b. Batch / Commit-Reveal Betting (anti-MEV)
+
+Live since HF14 for **binary** markets (multi forces `allow_instant_bet` until LMSR batch lands);
+opt-in per market (`allow_batch` / `allow_instant_bet`), median kill-switch `pm_commit_reveal_enabled`.
+
+- `pm_place_bet` with `mode = 1` queues a **batch** bet; `pm_commit_bet` (commitment hash + escrow) →
+ `pm_reveal_bet` runs the **commit-reveal** flow. Unrevealed commitments forfeit
+ `pm_commit_no_reveal_penalty_percent` (bp) via `pm_commit_forfeit`.
+- At each epoch boundary (`pm_batch_epoch_blocks`, reveal window `pm_reveal_window_blocks`) queued bets
+ settle at a **uniform price** via the `pm_batch_settle` cron — only the net residual moves the AMM, so
+ intra-batch ordering carries no advantage and the `Σ reserve ≥ L` invariant is preserved.
+
+## 17. On-Chain Object Model
+
+All state lives in **chainbase objects** registered as core indexes at HF14 — there is no SQL database. Field-level definitions live in the operation/object headers and
+are queryable read-only via the [`prediction_market_api` plugin](../plugins/prediction-market-api). The
+reputation counters the prototype kept on a `users` table are now fields on `pm_oracle_object`.
+
+| Object (index) | Holds | Looked up by |
+|---|---|---|
+| `pm_oracle_object` | oracle registration, insurance, the 14 reputation counters, fault stamps, ban (`banned_until` + `banned_by`) | owner |
+| `pm_market_object` | market config, CPMM reserves (`reserve_a/b`, `k`), `*_fee_percent` (bp), `status` / `payout_status`, timers, `dispute_mode`, `a_bets_sum` / `b_bets_sum`, oracle resolution statement (`decision_url` / `decision_reason`) | id / creator / oracle / result_expiration |
+| `pm_outcome_object` | per-outcome LMSR `q`, `bets_sum`, `bets_count` (multi markets) | market + outcome |
+| `pm_bet_object` | a bet — account, `side` / `outcome_index`, `amount`, curve `weight`, `time_penalty`, `status`, `mode` | market / account |
+| `pm_liquidity_object` | an LP position — principal, deposit time, time-weight; `provider` empty ⇒ lazy-pool LP | market |
+| `pm_commit_object` | a commit-reveal commitment hash + escrow (batch / commit-reveal) | market / account |
+| `pm_dispute_object` | a dispute — disputer, `proposed_outcome`, fee escrow, timers, `status`, `dispute_mode`, oracle rebuttal (`oracle_response` / `oracle_response_time`) | market |
+| `pm_dispute_vote_object` | one committee ballot — voter, `vote_outcome`, `vote_percent` (revisable until close) | market + voter |
+| `pm_lazy_pool_object` | the singleton pool — `free_balance` / `allocated_balance` / `earned_balance`, `reward_per_share`, `leverage_fund_used`, `total_shares` | singleton (id 0) |
+| `pm_lazy_deposit_object` | a depositor — shares, reward snapshot, unlock time | account |
+| `pm_lazy_allocation_object` | the pool's silent LP allocation to one market + graduated-recall state (`bets_sum_at_check`, `check_step`, `recalled_amount`) | market |
+| `pm_leverage_position_object` | an open leveraged position — collateral, loan, obligation, curve weight, `status` | account / market + status |
+| `pm_creator_ban_object` | a banned creator — `banned_until`, `ban_count`, `banned_by` | ban account |
+
+Reputation metrics are computed on read (`compute_oracle_reliability_score()` — §14), not stored. All
+percentage fields are basis points (`*_percent`, bp). Lazy-pool per-market allocations and
+graduated-recall state live on `pm_lazy_allocation_object`; oracle fault stamps and reputation counters
+live on `pm_oracle_object`.
+
+**Plugin-only (non-consensus):** `pm_market_meta_object` — off-chain-parsed market metadata
+(category / tags / banned jurisdictions) for discovery and jurisdiction filtering; it is built by the
+[`prediction_market_api`](../plugins/prediction-market-api) plugin from each market's opaque `metadata`
+string and never participates in consensus.
+
+See [Prediction Market operations](../protocol/operations/prediction-markets) for the full field
+definitions of these objects.
diff --git a/docs/prediction-markets/whitepaper.md b/docs/prediction-markets/whitepaper.md
new file mode 100644
index 0000000000..4f713b88b0
--- /dev/null
+++ b/docs/prediction-markets/whitepaper.md
@@ -0,0 +1,672 @@
+# Onix Protocol: LP-Guaranteed Prediction Markets on VIZ DLT
+
+**An Industry Whitepaper**
+
+*Anatoly Piskunov (On1x)*
+*Version 2.0 — June 2026 (on-chain / HF14)*
+
+---
+
+> **On-chain status (HF14).** This paper was first written against the centralized prototype. The
+> protocol now runs as **first-class consensus operations (`pm_*`) on VIZ DLT**, verified in
+> `consensus_sim`. Live since HF14: both market types (CPMM binary + LMSR multi), the parimutuel
+> zero-sum settlement, the **Lazy Pool**, an opt-in **leverage** subsystem, opt-in **batch /
+> commit-reveal betting** (binary), bonded oracles, and a two-mode dispute system (committee /
+> account). All percentage parameters are **basis points (bp): 10000 = 100.00%** (the prototype used
+> permille). Sections below are annotated where the live design differs from the original prototype text.
+
+## Abstract
+
+Prediction markets aggregate dispersed information into prices, producing probability estimates that consistently outperform polls, expert panels, and statistical models. Yet adoption remains constrained by a single structural problem: **liquidity providers lose money.**
+
+Uniswap v3 LPs suffer impermanent loss. LMSR market makers risk their entire subsidy. CLOB market makers face adverse selection. Every existing model asks capital providers to accept downside risk in exchange for uncertain yield — and the data shows most of them lose.
+
+The **Onix Protocol** eliminates LP risk entirely. It is a prediction market architecture where LP principal is **structurally guaranteed** — not by insurance, not by hedging, but by the payout mechanics themselves. Winners are paid exclusively from losers' forfeited stakes. LP capital provides market depth but is never used to settle bets.
+
+This paper describes the Onix Protocol's two market types — **Onix Binary** (Constant Product Market Maker) and **Onix Multi** (LMSR pricing with parimutuel settlement) — along with the market architecture, oracle and dispute resolution system, lazy liquidity pool, opt-in leverage, governance model, and its implementation on VIZ DLT as consensus-level operations.
+
+---
+
+## 1. The Problem: LP Risk in Prediction Markets
+
+Every prediction market needs liquidity. Without it, prices are meaningless — a bet that moves the market by 20% reveals the bettor's capital, not the crowd's wisdom. The fundamental question is: **who provides that liquidity, and what do they risk?**
+
+### 1.1 The Current Landscape
+
+| Platform | LP Model | LP Risk | Yield Source |
+|----------|----------|---------|-------------|
+| **Uniswap v3** | Concentrated AMM | Impermanent loss (often >5% annualized; >50% of v3 LPs underperform buy-and-hold) | Trading fees |
+| **Aave / Compound** | Lending pool | Smart contract risk, liquidation cascades | Borrower interest |
+| **Curve** | Stableswap AMM | Low IL for pegged assets, smart contract risk | Fees + CRV emissions |
+| **Standard LMSR** | Market maker subsidy | Loss up to `b × ln(N)` — the entire subsidy | Bid-ask spread |
+| **Polymarket (CLOB)** | Active market making | Inventory risk, adverse selection | Bid-ask spread |
+| **Kalshi** | No LP concept | N/A (exchange model) | N/A |
+
+The pattern is clear: providing liquidity to prediction markets requires either active management skill (CLOB), tolerance for capital loss (LMSR), or acceptance of impermanent loss (AMM). None of these are suitable for retail participants.
+
+### 1.2 Why This Matters
+
+Prediction markets work best when they are deep and liquid. Deep markets produce accurate prices, attract informed traders, and generate the information value that makes prediction markets useful as a public good. But depth requires capital, and capital requires compensation for risk.
+
+The result is a chicken-and-egg problem:
+- Thin markets → high slippage → poor UX → few bettors → low fees → no LP incentive → thin markets
+
+Breaking this cycle requires removing the risk from the LP side of the equation. If providing liquidity is risk-free, the barrier to entry drops to zero, and the flywheel can start spinning.
+
+---
+
+## 2. The Onix Protocol
+
+### 2.1 Design Principles
+
+The Onix Protocol is built on three architectural invariants:
+
+1. **LP principal is structurally safe.** This is not a risk-mitigation strategy — it is a property of the payout architecture. LP capital and bet settlement draw from physically separate pools.
+
+2. **Losers fund winners.** All payouts (winner profits, oracle fees, creator fees, LP fees) are sourced exclusively from losers' forfeited stakes. Fees are computed at resolution as `floor(losers_sum × fee_bp / 10000)` (bp: 10000 = 100.00%), never deducted at bet time.
+
+3. **Dual market types, single guarantee.** Binary markets (Onix Binary) and multi-outcome markets (Onix Multi) use different pricing formulas but share the same settlement model and the same LP guarantee.
+
+### 2.2 Onix Binary (CPMM + Parimutuel Settlement)
+
+Onix Binary uses the Constant Product Market Maker formula — the same `x * y = k` invariant used by Uniswap — as the **pricing engine** for binary outcomes, with **parimutuel settlement** (losers fund winners pro-rata by weight), the same settlement model as Onix Multi.
+
+**Mechanics:**
+
+A market maintains two reserves, `reserve_a` and `reserve_b`, with constant product `k`:
+
+```
+k = reserve_a × reserve_b
+```
+
+When a user bets `amount` on outcome A (in the implementation, side 0 → `reserve_a`), the stake enters
+that side's reserve and tokens are drawn from the **opposing** reserve:
+
+```
+new_reserve_a = reserve_a + amount
+new_reserve_b = floor(k / new_reserve_a)
+tokens_received = reserve_b − new_reserve_b
+```
+
+The `tokens_received` (called `weight`) is the user's **relative claim** on the winners' pool if outcome A wins (settlement is parimutuel — see below, identical to Onix Multi). Implied probability rises for the side that is bet (more money on A → `reserve_a` grows → `P(A)` grows):
+
+```
+P(A) = reserve_a / (reserve_a + reserve_b)
+P(B) = reserve_b / (reserve_a + reserve_b)
+```
+
+**Settlement and proof of LP safety (parimutuel):**
+
+At resolution, winners receive their stake back plus a proportional share of the losers' pool, by weight (identical to Onix Multi):
+
+```
+winners_pool = losers_sum − fees
+payout = bet_amount + (weight / total_winning_weight) × winners_pool − time_penalty_on_profit
+```
+
+LP principal `L` is returned unconditionally, and the guarantee is exact:
+
+```
+Money OUT = L + winning_bets + winners_pool + fees = L + winning_bets + losing_bets = L + all_bets = Money IN
+```
+
+Total payout is capped at `losers_sum` regardless of weights, so LP capital is never used to settle bets. The CPMM is the **pricing engine** (probability + weight); it does not gate payout. (The AM-GM relation `reserve_a + reserve_b ≥ 2√k = L` still holds for the pricing curve but is no longer relied upon for solvency.)
+
+**Worked example:**
+
+```
+Setup: 200 VIZ liquidity → reserve_a = 100, reserve_b = 100, k = 10,000
+Fees (bp): oracle 50 (0.5%), creator 50 (0.5%), liquidity 100 (1%)
+
+Alice bets 50 VIZ on A → receives weight 33.33 (price moves from 50% to 69%)
+Bob bets 80 VIZ on B → receives weight 81.82
+
+Resolution: A wins
+ Losers (Bob): 80 VIZ forfeited → losers_sum = 80
+ oracle_fee = floor(80 × 50/10000) = 0.4 VIZ
+ creator_fee = floor(80 × 50/10000) = 0.4 VIZ
+ liq_fee = floor(80 × 100/10000) = 0.8 VIZ
+ winners_pool = 80 − 1.6 = 78.4 VIZ
+
+ Alice (only winner, weight 33.33 of 33.33):
+ payout = 50 (stake) + 78.4 × (33.33/33.33) = 128.4 VIZ (minus any time penalty on profit)
+ LP return: 200 VIZ principal + share of 0.8 VIZ fee pool
+```
+
+### 2.3 Onix Multi (LMSR + Parimutuel Settlement)
+
+Onix Multi is the protocol's innovation for markets with 3–10 outcomes. It combines Hanson's Logarithmic Market Scoring Rule (LMSR, 2003) for real-time pricing with parimutuel settlement for LP safety.
+
+**Pricing (LMSR softmax):**
+
+For a market with outcomes {1, 2, ..., N}, each with quantity parameter `q_i`:
+
+```
+price(i) = exp(q_i / b) / Σ_j exp(q_j / b)
+```
+
+This is the softmax function — prices always sum to exactly 1.0 by construction. No arbitrage mechanism or split/merge operation is needed.
+
+The cost to buy Δ tokens on outcome i:
+
+```
+C(q) = b × ln(Σ_j exp(q_j / b))
+
+cost = C(q + Δ·e_i) − C(q)
+```
+
+The parameter `b` controls price sensitivity (higher b = less price impact per bet). It is funded by the LP subsidy: `b = S / ln(N)` where S is the total subsidy.
+
+**The innovation — parimutuel settlement:**
+
+In **standard LMSR**, the market maker is the counterparty to all bets. If the crowd correctly predicts the outcome, the market maker loses up to `b × ln(N)` — potentially the entire subsidy. This is why LMSR has seen limited adoption outside corporate prediction markets (Microsoft, Inkling) where the operator absorbs the loss.
+
+**Onix Multi changes the payout source.** At resolution:
+
+```
+1. Oracle declares the winning outcome
+2. Losers forfeit 100% → losers_sum
+3. Fees deducted from losers_sum (bp; 10000 = 100.00%):
+ oracle_fee = floor(losers_sum × oracle_fee_bp / 10000)
+ creator_fee = floor(losers_sum × creator_fee_bp / 10000)
+ liq_fee = floor(losers_sum × liquidity_fee_bp / 10000)
+ winners_pool = losers_sum − fees
+4. Winners receive:
+ payout = bet_amount + (tokens / total_winning_tokens × winners_pool) − time_penalty
+5. LP subsidy returned unconditionally
+```
+
+Winners are paid by losers, not by the LP. The subsidy is architecturally separate from the settlement flow.
+
+**Proof of LP principal guarantee:**
+
+1. The LP deposits `S` VIZ as subsidy, which funds market depth.
+2. During betting, users pay VIZ → receive outcome tokens. The VIZ accumulates as the betting pool.
+3. At resolution, losers' forfeited stakes fund winner payouts and fees. The subsidy `S` was never in the payout pool.
+4. The subsidy is returned to the LP unconditionally, regardless of outcome.
+
+**Comparison:**
+
+| Dimension | Standard LMSR | Onix Multi |
+|-----------|---------------|------------|
+| LP role | Counterparty to all bets | Depth deposit (not counterparty) |
+| LP max loss | `b × ln(N)` (entire subsidy) | **Zero** |
+| Winner payout | 1 token = 1 unit of currency | Token = proportional claim on losers' pool |
+| CTF split/merge needed? | Yes (enforce price sum = 1) | No (softmax guarantees it) |
+
+**Worked example (3-outcome election):**
+
+```
+Setup: b = 1000, outcomes = [A, B, C], subsidy = 1000 VIZ
+Initial: price(A) = price(B) = price(C) = 33.3%
+
+Alice bets 50 VIZ on A → ~47 tokens (price: 33% → ~38%)
+Bob bets 100 VIZ on B → ~88 tokens
+Carol bets 30 VIZ on C → ~29 tokens
+
+Resolution: A wins
+ Losers: Bob (100) + Carol (30) = 130 VIZ
+ Fees (200 bp = 2% total): 2.6 VIZ
+ winners_pool = 127.4 VIZ
+
+ Alice: 50 + (47/47 × 127.4) = 177.4 VIZ
+ LP: 1000 VIZ returned in full + share of liquidity fees
+```
+
+### 2.4 Edge Cases
+
+| Scenario | Outcome |
+|----------|---------|
+| All bets on the winner | `losers_sum = 0` → every bettor gets back exactly their bet amount. LP subsidy returned. Zero-sum. |
+| No bets on the winner | Entire losers' pool is undistributed → LP bonus. LP profits maximally. |
+| Zero-volume market | LP subsidy returned in full. No fees, no payouts. |
+| Single bettor wins | That bettor receives `bet_amount + winners_pool`. LP subsidy returned. |
+
+---
+
+## 3. Market Architecture
+
+### 3.1 Market Lifecycle
+
+```mermaid
+stateDiagram-v2
+ direction LR
+ state "Waiting (0)" as Waiting
+ state "Active (1)" as Active
+ state "Closed (2)" as Closed
+ state "Resolved (3)" as Resolved
+ state "Deleted (-1)" as Deleted
+ state "Paid out" as Paid
+ [*] --> Waiting
+ Waiting --> Active: oracle accepts
+ Waiting --> Deleted: oracle rejects
+ Active --> Closed: betting expires
+ Active --> Resolved: early resolution (if allowed)
+ Closed --> Resolved: oracle resolves
+ Resolved --> Paid: grace period (12h)
+ Deleted --> [*]
+ Paid --> [*]
+```
+
+Markets are created by a market creator, reviewed and accepted by an oracle (who stakes insurance), open for betting, resolved with an outcome, and paid out after a dispute grace period.
+
+### 3.2 Fee Model (Losers-Only Fee Extraction)
+
+A distinctive feature of the Onix Protocol is that **no fees are deducted at bet time**. The full bet amount enters the market reserves. Fees are computed only at resolution, exclusively from the losing side's forfeited stakes:
+
+```mermaid
+flowchart TD
+ LS["losers_sum (100% of losing bets)"]
+ LS --> OF["oracle_fee = floor(losers_sum × oracle_fee_bp / 10000)"]
+ LS --> CF["creator_fee = floor(losers_sum × creator_fee_bp / 10000)"]
+ LS --> LF["liquidity_fee = floor(losers_sum × liquidity_fee_bp / 10000)"]
+ LS --> WP["winners_pool = losers_sum − all fees"]
+```
+
+This provides a structural guarantee: fees and winner payouts draw from completely separate funding sources. Fee extraction can never compete with winner obligations.
+
+**Oracle fee terms are frozen at acceptance (offer→quote).** The creator publishes a *maximum* the oracle may charge (`oracle_fee_percent` ceiling in bp + `oracle_fixed_fee` ceiling); when the oracle accepts it quotes its actual terms (≤ the creator's ceiling and ≤ the median governance cap `pm_max_oracle_fee_percent`), which are frozen onto the market and a `pm_market_accepted` virtual op is emitted. A self-oracle freezes its terms at creation. The **oracle fixed fee** (per-market) is paid from the losers' pool remainder (never minted). A **market creation fee** goes to the DAO fund as anti-spam protection.
+
+### 3.3 Time-Weighted LP Distribution
+
+LP fee shares are distributed proportionally to `amount × max(1, seconds_to_expiration)`:
+
+```
+weight_i = amount_i × max(1, sec_to_expiration_i)
+fee_share_i = floor(total_fee_pool × weight_i / Σ weight_j)
+```
+
+Early LPs earn dramatically more per unit of capital than late LPs. In a 48-hour market, an LP who deposits at hour 1 earns ~2,400x more per VIZ than one who deposits at hour 47.
+
+Each deposit is tracked as an independent position — multiple deposits by the same user are weighted and paid separately. LP principal is always returned in full, regardless of market outcome.
+
+### 3.4 Time Penalty for Late Bets
+
+To discourage last-minute betting (which carries less uncertainty risk), a configurable time penalty applies to bets placed near expiration:
+
+```
+if time_to_expiration < penalty_window:
+ ratio = 1 − (time_to_expiration / penalty_window)
+ penalty_ratio = ratio² // quadratic (default)
+ time_penalty = floor(penalty_ratio × max_penalty)
+```
+
+The penalty applies **only to profit**, never principal. A winning bettor always receives at least their original stake. The quadratic curve is gentle early in the penalty window and steep late, rewarding "somewhat late" over "extremely late."
+
+### 3.5 Position Transfers
+
+Positions are transferable between accounts via a native protocol operation:
+
+```
+pm_transfer_position { bet_id, to_user, amount, memo }
+```
+
+No slippage, no market impact — pure record reassignment. The `memo` field supports both plaintext and encrypted modes (ECIES via VIZ account memo keys), enabling P2P deals, OTC trading, and private annotations.
+
+This is the only composability feature from the Conditional Tokens Framework (Polymarket/Gnosis) that provides real user value. CTF split/merge is architecturally unnecessary — Onix pricing formulas guarantee price coherence by construction.
+
+---
+
+## 4. Oracle and Dispute Resolution
+
+### 4.1 Bonded Oracle Model
+
+Oracles in the Onix Protocol are not trusted by default — they are **bonded**. Each oracle must:
+
+- Register with a one-time fee (default 10 VIZ)
+- Deposit insurance (minimum 5,000 VIZ)
+- Accept markets explicitly (staking their insurance on each acceptance)
+- Resolve markets with an outcome and supporting evidence (decision URL)
+
+The insurance bond creates accountability: oracles who misresolve, miss deadlines, or lose disputes have their insurance slashed. The bond must exceed the oracle's potential manipulation profit for the economic security model to hold.
+
+Oracle revenue comes from two sources:
+1. **Fixed fee** (per market) — compensates for staking insurance and providing resolution
+2. **Percentage fee** (from losers' pool at resolution) — scales with market volume
+
+### 4.2 Dispute Arbitration
+
+Any bettor can challenge a resolution within a grace period by paying a dispute fee. During disputes, all payouts are frozen.
+
+Resolution runs in one of **two per-market modes**, chosen at creation:
+
+- **Committee mode (`dispute_mode = 0`, default)** — the *whole SHARES electorate* decides by
+ **stake-weighted vote** (`pm_dispute_vote`), tallied deterministically by the `pm_dispute_finalize`
+ cron at `voting_end_time`. It is an **open public hearing**: the live tally is queryable and votes are
+ **not** hidden behind commit-reveal (a deliberate, permanent choice — the DAO resolves disputes as
+ transparently as possible). Because new evidence surfaces during the hearing, **a ballot is revisable**
+ until close (a repeat vote overwrites the prior one). A voter's weight is its `effective_vesting_shares`
+ **plus its Lazy-Pool stake converted to vesting-shares**, so DAO members who park VIZ in the pool keep
+ their governance weight.
+- **Account mode (`dispute_mode = 1`)** — a single named `dispute_resolver` (recommended multisig)
+ issues the verdict (`pm_dispute_resolve`).
+
+The verdict logic in either mode:
+
+**If the oracle was wrong (overturned):**
+- The correct outcome is applied and payouts recalculated.
+- The disputer gets their fee back **plus a reward carve-out** drawn from the slashed insurance, sized as `dispute_fee × pm_dispute_reward_multiplier` (bp; e.g. 30000 = ×3), capped by the slash.
+- **The remainder of the slash is added to the winners' pool** (via `forfeit_pool`) — it goes to the winning bettors, **not** to a resolver or the DAO. Neither the committee voters nor the account-mode resolver receive any reward (committee voting is an unpaid governance duty).
+- The oracle's insurance is slashed (scaled by consensus strength in committee mode, or by the resolver's `penalty_amount` in account mode), with optional ban.
+
+**If the oracle was right (upheld):**
+- The disputer **forfeits the whole dispute fee to the oracle** (compensation for the bad-faith challenge).
+- Original payouts proceed unchanged.
+
+**Denial-of-resolution prevention:** If the resolver fails to act within 14 days, disputes auto-close: all bets and LP are refunded, the oracle is penalized, and the disputer's fee is returned. This guarantees funds are never frozen indefinitely.
+
+### 4.3 Oracle Reputation Scoring
+
+The protocol tracks 14 on-chain metrics per oracle and computes a reliability score (0–100):
+
+```
+reliability_score = clamp(0, 100,
+ 50 (base)
+ − 0.40 × dispute_loss_rate × 100
+ − 0.10 × excess_no_contest × 100
+ − 0.20 × deadline_miss_rate × 100
+ − 0.15 × (1 − dispute_response_rate) × 100
+ + volume_bonus (0–25)
+ + experience_bonus × freshness_multiplier (0–25)
+ − 15 × bans_received
+)
+```
+
+Key design choices:
+- **Rates, not counts** — 1 dispute lost out of 100 (1%) scores better than 1 out of 2 (50%)
+- **Neutral start at 50** — new oracles must earn reputation, not start at 100
+- **Freshness decay** — inactive oracles lose their experience bonus over time
+- **Volume tiers** — high-volume oracles receive bonus points for proven track record
+
+The reliability score combines with a risk factor (insurance-to-bets ratio) to produce a **composite trust score** — the primary metric shown to users.
+
+### 4.4 No-Contest and 3-Outcome Resolution
+
+An oracle who cannot verify an outcome can voluntarily declare **no-contest**, triggering refunds at a reduced penalty (50% of dispute fee from insurance — much cheaper than losing a dispute). This creates an incentive gradient:
+
+| Scenario | Oracle Cost | Ban Risk |
+|----------|------------|----------|
+| Voluntary no-contest | 500 VIZ | None |
+| Dispute loss | 1,000+ VIZ + extra penalty | Permanent or temporary |
+| Missed deadline | 250 VIZ (auto-penalty) | None (but reputation damage) |
+
+If users believe the oracle abused no-contest, they can dispute it. The resolver then chooses from **three** possible correct outcomes: A wins, B wins, or confirm no-contest. This prevents oracles from using no-contest to avoid paying out winning bettors.
+
+---
+
+## 5. Lazy Liquidity Pool
+
+### 5.1 The Capital Deployment Problem
+
+Individual LP provision requires active market selection. Most users won't manually evaluate and deposit into specific markets. The result: most markets launch with only the creator's initial liquidity, producing thin order books and high slippage.
+
+### 5.2 Automated Pool-to-Market Allocation
+
+The Lazy Liquidity Pool solves this by accepting deposits and **automatically allocating** a percentage of the pool's free balance to every new market when it activates:
+
+```
+alloc_amount = free_balance × allocation_percent / 100
+```
+
+Allocations are computed from the current free balance (not the original total), creating geometric decay — the pool can never be fully depleted:
+
+```
+After 50 markets (2% allocation each): ~357 VIZ free from original 1,000
+After 100 markets: ~133 VIZ still free
+```
+
+A maximum total allocation cap (default 70%) provides additional safety.
+
+### 5.3 Reward Distribution (one shared accumulator)
+
+The problem: when a market resolves with pool profit, that profit must be split among **all** current
+depositors in proportion to their shares — but iterating every depositor on every market would be O(N)
+and unbounded. The pool avoids that with **one global running total**, `reward_per_share` ("rps"):
+
+```
+// When a market resolves with pool LP profit, the per-share value of the pool rises once:
+pool.reward_per_share += profit × PRECISION / total_shares
+
+// A depositor's earnings = their shares × how much rps has risen since they last touched the pool:
+live_reward = pending + shares × (pool.reward_per_share − user.snapshot) / PRECISION
+```
+
+In plain terms: every depositor "owns" a slice of each rise in `reward_per_share`, and their reward is
+just `shares × (current rps − the rps recorded when they last deposited/withdrew)`. A depositor's own
+record is touched **only when they act** (deposit or withdraw); until then their entitlement accrues
+silently in the global number. So distributing profit to thousands of depositors is **O(1)** — a single
+addition — and no funds are paid until claimed. This is the well-known accumulator pattern from
+[SushiSwap's MasterChef contract](https://github.com/sushiswap/masterchef/blob/master/contracts/MasterChef.sol)
+(and Compound's cToken index) — `PRECISION` (1e9) keeps the integer division exact.
+
+### 5.4 Opportunity-Cost Protection
+
+The pool auto-allocates to every market, creating an attack vector: a malicious oracle could create long-duration zero-volume markets to lock pool capital. Three mechanisms address this:
+
+**Graduated Early Recall:** The market's duration is divided into 10 steps. At each step, if betting volume is below a threshold (1% of allocation), 10% of the current allocation is recalled to the pool. A completely idle 30-day market loses ~60% of its allocation.
+
+**Active Market Penalty:** Each additional active market from the same oracle reduces that oracle's allocation by 5% (recursive). An oracle with 10 active markets receives ~60% of the base allocation per market, incentivizing quality over quantity.
+
+**Fault Penalty Stamps:** Bad outcomes (missed deadlines, disputes lost, zero-volume resolutions) generate penalty stamps that further reduce future allocations. Stamps auto-expire after 10 days of clean operation.
+
+### 5.5 Opt-In Leverage (Lazy-Pool-Funded)
+
+The Lazy Pool serves **two roles from one `free_balance`**: silent market-LP allocations *and* funding for
+an **opt-in leverage** subsystem. A bettor can open a leveraged position (`pm_leverage_open`) where margin
+is a **loan from the pool** — no token emission, the position stays fully collateralized from the system's
+view. The binary "jump risk" that breaks CLOB liquidation engines is handled by **liquidating against
+pre-bet reserves**: an opposing-bet or settlement force-close recovers `min(cancel_value, obligation) ≥
+loan`, so the pool gets its loan plus interest back; the only bounded bad-debt path is a same-side
+`pm_cancel_bet`. A median kill-switch (`pm_leverage_enabled`, default off) blocks *new* opens, but the
+protective liquidation cascade is deliberately **not** gated by it — toggling leverage off never strips
+protection from open positions. The pool earns leverage interest in addition to LP yield, accounted via the same shared `reward_per_share` accumulator as in §5.3. Settlement of pool loans emits `pm_leverage_resolve` / `pm_leverage_liquidate`.
+
+---
+
+## 6. Governance
+
+### 6.1 Delegate-Voted Chain Parameters
+
+VIZ uses Delegated Proof of Stake (DPoS) consensus where elected delegates (validators) govern chain parameters through a median-vote mechanism:
+
+1. Each delegate publishes preferred values for all parameters
+2. The network computes the **median** of all active delegates' votes
+3. Parameters change automatically when the median shifts — no hard fork, no deployment
+
+All prediction-market parameters (fees, penalties, insurance requirements, dispute windows, lazy-pool
+settings, leverage knobs, batch/commit-reveal timing) are delegate-voted. All percentage parameters are
+in **basis points (bp), 10000 = 100.00%**:
+
+| Examples | Governance |
+|----------|-----------|
+| `pm_dispute_fee`, `pm_max_oracle_fee_percent` (bp) | Delegate median vote |
+| `pm_dispute_grace_sec`, `pm_dispute_vote_period_sec` | Delegate median vote |
+| `pm_dispute_approve_min_percent`, `pm_dispute_reward_multiplier` (bp) | Delegate median vote |
+| `pm_lazy_*` allocation/recall, `pm_leverage_*` (enabled, fund %, max position) | Delegate median vote |
+| `pm_commit_reveal_enabled`, `pm_batch_epoch_blocks`, `pm_reveal_window_blocks` | Delegate median vote |
+
+Hard forks are only needed for structural changes (new operation types, formula changes), not for economic tuning. Kill-switches (`pm_leverage_enabled`, `pm_commit_reveal_enabled`) let governance disable a whole subsystem by median vote without a fork.
+
+### 6.2 Jurisdictional Client Model
+
+VIZ DLT is infrastructure, not an operator — analogous to how Bitcoin is a ledger, not a money transmitter. The protocol is neutral and permissionless. Legal obligations attach to **client applications**, not to the consensus algorithm.
+
+Any jurisdiction can build a compliant client on VIZ DLT:
+
+| Client Component | Implementation |
+|-----------------|---------------|
+| Pre-approved oracles | Client whitelist of licensed, KYC-verified oracles |
+| Pre-approved resolvers | Government-approved dispute resolution bodies |
+| KYC/AML | Client-level identity verification |
+| Fee routing as tax revenue | `dao_fund_account_id` → state treasury account |
+| Market restrictions | Client filters by allowed categories |
+| Betting limits | Client-enforced per-user caps |
+
+The same protocol operations (`pm_place_bet`, `pm_resolve`, `pm_dispute`) work identically for permissionless and regulated clients. The difference is entirely at the client layer.
+
+---
+
+## 7. Competitive Landscape
+
+### 7.1 Platform Comparison
+
+| Dimension | Onix (Forecaster) | Polymarket | Kalshi | Standard LMSR |
+|-----------|-------------------|------------|--------|---------------|
+| **Pricing** | CPMM (binary) / LMSR softmax (multi) | CLOB | CLOB | LMSR |
+| **LP Risk** | **Zero** (structural guarantee) | Inventory risk | N/A | Up to `b × ln(N)` |
+| **LP Knowledge** | Low (deposit and earn) | High (manage orders) | N/A | Medium |
+| **Fee Model** | % of losers' pool at resolution | Bid-ask spread | Exchange fees (1-7%) | Spread |
+| **Oracle** | Per-market bonded + committee dispute | UMA Optimistic Oracle | Kalshi (CFTC-regulated) | Operator |
+| **Late Bet Penalty** | Quadratic, configurable | None | None | None |
+| **Position Transfer** | Native protocol operation + encrypted memo | CTF (ERC-1155) | None | None |
+| **Governance** | Delegate-voted parameters | Team multisig | CFTC process | Operator |
+| **Infrastructure** | VIZ DLT (consensus-level) | Polygon (smart contracts) | Proprietary servers | Various |
+
+### 7.2 Why CTF Split/Merge Is Unnecessary
+
+Polymarket uses the Gnosis Conditional Tokens Framework (CTF) where positions are ERC-1155 tokens that can be split and merged to enforce price coherence (prices sum to $1).
+
+Under the Onix Protocol, this mechanism is architecturally unnecessary:
+
+- **Onix Binary (CPMM):** `price(A) + price(B) = reserve_b/(reserve_a+reserve_b) + reserve_a/(reserve_a+reserve_b) = 1` — by definition
+- **Onix Multi (LMSR softmax):** `Σ price(i) = Σ exp(q_i/b) / Σ exp(q_j/b) = 1` — by definition of softmax
+
+No arbitrage mechanism needed. Price coherence is a mathematical property of the formulas, not an external enforcement layer.
+
+### 7.3 The Flywheel
+
+```
+Risk-free LP → lower barrier for retail LPs
+ → more liquidity deposited
+ → deeper markets, less slippage
+ → better UX for bettors
+ → more volume
+ → more fees for LPs
+ → attracts even more LPs
+```
+
+"Passive yield without impermanent loss" is the value proposition that Uniswap, Balancer, and Curve cannot offer. For the crypto-native audience, this is a compelling narrative: earn yield by providing liquidity to prediction markets, with zero risk to principal.
+
+---
+
+## 8. VIZ DLT: From Prototype to Protocol
+
+### 8.1 Current State
+
+The protocol began as a Telegram WebApp with a centralized backend (all market logic server-side) — a working prototype with known limits: no sybil resistance beyond Telegram accounts, no censorship resistance, no composability. **That migration is now done:** the full market logic runs **on VIZ DLT as consensus-validated `pm_*` operations** (HF14), exercised end-to-end in `consensus_sim`. The remainder of this section describes that on-chain architecture, now realized.
+
+### 8.2 Migration Architecture
+
+VIZ DLT is a Distributed Ledger Technology with ~3-second block times, DPoS consensus, named accounts (Graphene-style), and no general-purpose smart contracts. Prediction market operations will be implemented as **first-class consensus-validated operations** — not smart contracts, not `custom_json` payloads.
+
+| Layer | Examples | Consensus-Validated? |
+|-------|---------|---------------------|
+| **Protocol operations** | `pm_create_market`, `pm_oracle_accept_market`, `pm_place_bet`, `pm_commit_bet`/`pm_reveal_bet`, `pm_resolve_market`, `pm_dispute_create`/`pm_dispute_vote`/`pm_dispute_resolve`, `pm_lazy_deposit`/`pm_lazy_withdraw`, `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert` | Yes — every node validates |
+| **Virtual operations** | `pm_payout` (per bettor), `pm_auto_payout`, `pm_market_accepted`, `pm_dispute_finalize`, `pm_dispute_auto_close`, `pm_oracle_missed_penalty`, `pm_lazy_recall`, `pm_batch_settle`, `pm_commit_forfeit`, `pm_leverage_resolve`/`pm_leverage_liquidate` | Yes — deterministic, generated at block time |
+| **metadata / custom_json** | Dispute comments, market descriptions, UI metadata | No — display/indexing only |
+
+Every financial action (placing bets, adding liquidity, resolving markets, seizing insurance) is validated by every validator. Invalid operations are rejected before block inclusion. No Solidity, no gas estimation, no bytecode deployment.
+
+The frontend is a **fully headless web client** — no backend server, no database, no sessions. Private keys stored in the browser (encrypted), transactions signed locally and broadcast to public VIZ nodes. No Telegram dependency; the core app is platform-independent.
+
+### 8.3 Delivered Since the Prototype, and Remaining Roadmap
+
+**Delivered on-chain (HF14):**
+
+| Feature | Status |
+|---------|--------|
+| Commit-reveal + batch betting (binary, opt-in, median kill-switch) | ✅ Live |
+| Opt-in leverage (Lazy-Pool-funded, pre-bet-reserve liquidation) | ✅ Live |
+| Lazy Pool (auto-allocation, graduated recall, MasterChef accounting) | ✅ Live |
+| Per-bettor / leverage settlement virtual ops + plugin API | ✅ Live |
+| Lazy-pool stake as governance weight (PM disputes + DAO requests) | ✅ Live |
+
+**Remaining roadmap:**
+
+| Priority | Feature | Impact |
+|----------|---------|--------|
+| High | Shared liquidity pools (category-level AMMs) | Solves liquidity fragmentation at the architecture level |
+| High | Automated data oracles (exogenous feeds) | Eliminates manipulation for objective markets |
+| Medium | Tiered dispute windows (small vs large markets) | Better UX calibration |
+| Medium | LMSR batch settlement (extend batch/commit-reveal to multi) | Multi markets currently force instant betting |
+| — | Commit-reveal **dispute** voting | **Deliberately rejected** — disputes stay public hearings (see §4.2) |
+
+---
+
+## 9. What Onix Does NOT Claim
+
+Honest disclosure of tradeoffs and limitations:
+
+- **LP profit is not guaranteed.** If a market has zero losing bets, there are no fees to distribute. LP gets principal back but earns nothing.
+
+- **Platform risk exists.** Bugs, exploits, and governance attacks are separate from the market maker model. The LP guarantee is structural (payout architecture), not insured (no external guarantee fund).
+
+- **Onix Multi tokens are not fixed-value instruments.** In standard LMSR, 1 winning token = 1 unit of currency. In Onix Multi, tokens are proportional claims on the losers' pool. If all bettors pick the winner, everyone breaks even.
+
+- **LP yield depends on volume, not depth.** A market with 100,000 VIZ subsidy and one with 1,000 VIZ subsidy earn the same absolute fee if both have identical betting volume and fee rates. The subsidy provides depth, not yield.
+
+- **DPoS governance has known tradeoffs.** Fewer validators than PoW/PoS, delegate concentration risks, token-weighted voting. These are inherent to the DPoS model (shared by EOS, Hive, Tron), not VIZ-specific.
+
+- **VIZ token liquidity is currently low.** Economic guarantees (insurance bonds, dispute fees) scale with token price. The protocol assumes that utility drives demand over time — the same bet every protocol-native token project makes.
+
+---
+
+## 10. Conclusion
+
+The Onix Protocol addresses the fundamental barrier to prediction market adoption: LP risk. By structurally separating LP capital from bet settlement — in both binary (CPMM) and multi-outcome (LMSR + parimutuel) markets — Onix makes liquidity provision risk-free and accessible to retail participants.
+
+The key innovations:
+
+1. **LP principal guarantee** as an architectural invariant, not insurance
+2. **Losers-fund-winners** settlement eliminating fee competition with winner payouts
+3. **LMSR pricing with parimutuel settlement** (Onix Multi) — combining proven price discovery with LP safety
+4. **Time-weighted LP distribution** rewarding early capital commitment
+5. **Quadratic time penalty** on profit (never principal) for late bets
+6. **Lazy Liquidity Pool** with automated allocation, graduated recall, and MasterChef accounting — also funding the opt-in **leverage** subsystem (pool-funded margin, pre-bet-reserve liquidation, never bad debt outside a bounded cancel-bet path)
+7. **Bonded oracle model** with reputation scoring, offer→quote fee freezing, and a two-mode dispute system — committee disputes are **public hearings** with revisable, lazy-pool-weighted votes
+8. **Opt-in anti-MEV** — batch / commit-reveal betting (binary) with a median kill-switch
+9. **Consensus-level implementation** on VIZ DLT — no smart contracts, no gas, no external keepers; strictly **zero-sum** (the protocol never mints a token)
+
+The bet is straightforward: if zero-risk LP attracts capital, capital creates depth, depth improves prices, and prices attract bettors, then the Onix Protocol solves the prediction market liquidity problem. The protocol mechanics are mathematically verifiable. The economic hypothesis will be tested by the market.
+
+---
+
+## 11. Author and Disclosure
+
+### Author
+
+**Anatoly Piskunov** (On1x) — Russian IT innovator, Web3/DLT developer, and creator of the VIZ blockchain. His work spans distributed ledger technology, decentralized social protocols, and economic models for digital communities.
+
+Key contributions include: VIZ Blockchain (Fair DPoS, social capital primitives), the Onix Protocol (LP-guaranteed prediction markets), Voice Protocol (censorship-resistant messaging), and extensive publications on blockchain economics and Web3 architecture.
+
+Full list of publications and projects: [https://on1x.com](https://on1x.com)
+
+### Disclosure
+
+The author of Forecaster and the Onix Protocol is also the creator of VIZ DLT. The migration roadmap proposes moving the platform to a blockchain the author designed and built.
+
+This is disclosed upfront. It is also the norm: Polymarket depends on Polygon Labs' infrastructure, Kalshi runs on its own servers, Augur designed the REP token it runs on. Every platform argues for its own infrastructure. The question is not whether the author has an interest — they always do — but whether the technical claims are falsifiable. Every formula, proof, and mechanism in this paper is mathematically verifiable and the codebase is open-source.
+
+---
+
+## 12. References
+
+1. Hanson, R. (2003). *Combinatorial Information Market Design.* Information Systems Frontiers, 5(1), 107–119. — The Logarithmic Market Scoring Rule (LMSR).
+
+2. Adams, H., Zinsmeister, N., Robinson, D. (2020). *Uniswap v2 Core.* — Constant Product Market Maker (`x * y = k`).
+
+3. Adams, H., et al. (2021). *Uniswap v3 Core.* — Concentrated liquidity and impermanent loss analysis.
+
+4. Gnosis. *Conditional Tokens Framework (CTF) Documentation.* https://docs.gnosis.io/conditionaltokens/ — ERC-1155 prediction market positions.
+
+5. UMA Protocol. *Optimistic Oracle Documentation.* — Dispute escalation mechanism used by Polymarket.
+
+6. Leshner, R., Hayes, G. (2019). *Compound: The Money Market Protocol.* — cToken accumulator pattern (basis for reward_per_share).
+
+7. SushiSwap. *MasterChef Contract.* — Lazy accounting pattern for reward distribution.
+
+8. Piskunov, A. (2019). *VIZ blockchain system: technical description.* — VIZ DLT architecture, DPoS consensus, named accounts.
+
+9. Piskunov, A. (2019). *What is Fair DPoS.* — Governance innovation in delegated proof of stake.
+
+10. Piskunov, A. (2023). *VIZ as a Digital Representative Self-Governing State.* — Framework for blockchain systems as digital polities.
diff --git a/docs/prediction-markets/workflows.md b/docs/prediction-markets/workflows.md
new file mode 100644
index 0000000000..d5532fba45
--- /dev/null
+++ b/docs/prediction-markets/workflows.md
@@ -0,0 +1,752 @@
+---
+title: Prediction Markets — workflows & interaction diagrams
+description: One canonical Onix binary market traced through every participant, with the zero-sum master ledger for normal and disputed resolution, and the verified operation / virtual-operation status.
+---
+
+# Workflows & interaction diagrams
+
+One **canonical scenario** traced through every participant. Each role sends specific **signed
+operations**, is touched by specific **virtual operations**, and ends with a **tokens sent / received**
+table for two outcomes:
+
+- **Normal resolve** — oracle resolves, grace passes, `pm_auto_payout` settles. No dispute.
+- **Disputed resolve** — oracle resolves **A**, a dispute **overturns to B**, then settlement runs.
+
+All amounts are abstract **VIZ** units. All percents are **bp** (10000 = 100.00%). Settlement is strictly
+**zero-sum** — no tokens are ever minted; `current_supply` is untouched:
+
+```
+Σ winner_payout + oracle_take + creator_take + lp_bonus + LP_principal
+ == Σ all bet amounts + LP_principal + forfeit_pool (+ insurance slash, in a dispute)
+```
+
+## Canonical market **M** (binary CPMM, A vs B)
+
+| Item | Value |
+|------|-------|
+| Engine | binary CPMM (`x·y=k`), `weight = tokens_out` |
+| Seed liquidity (marketmaker) | **2000** → reserves A=1000 / B=1000 |
+| `oracle_fee_percent` (oracle quote) | **1000** (10%) |
+| `creator_fee_percent` | **500** (5%) |
+| `liquidity_fee_percent` | **500** (5%) |
+| `oracle_fixed_fee` (oracle quote) | **10** |
+| `dispute_penalty_percent` | **+10000** (slash up to 100% of insurance ×consensus) |
+
+Illustrative chain props: `pm_market_creation_fee` 5, `pm_oracle_registration_fee` 10,
+`pm_min_oracle_insurance` 5000, `pm_dispute_fee` 1000, `pm_dispute_reward_multiplier` 30000 (**3×**),
+`pm_no_contest_penalty_percent` 5000 (50% of dispute fee), `pm_oracle_penalty_percent` 500 (5% of
+insurance on a missed deadline), `pm_lazy_emergency_penalty_percent` 5000 (50% of profit),
+`pm_leverage_pool_profit_percent` **R = 10%**, `pm_lazy_alloc_percent` 2000 (20%).
+
+### Roster
+
+| Actor | Role | Stake / action |
+|-------|------|----------------|
+| **maker** | creator + first LP | seeds 2000 liquidity |
+| **orac** | external oracle | insurance 5000; quotes fee 10% + fixed 10 |
+| **LP1** | in-market liquidity provider | adds 1000 liquidity |
+| **A** | bettor — winner | 100 on **A**, early; weight 100 |
+| **C** | bettor — late winner | 100 on **A** at T+85%; weight 100; time-penalty **50%** |
+| **B** | bettor — loser | 200 on **B**; weight 200 |
+| **D** | leverage **×10** winner | collateral 10 + loan 90 (market **L**) |
+| **E** | leverage **×5** liquidated | collateral 20 + loan 80 (market **L**) |
+| **LZ1** | lazy-pool depositor | deposits 1000 |
+| **disp** | disputer | escrows dispute fee 1000 |
+
+> Curve weights (100 / 100 / 200) are written explicitly to keep the parimutuel arithmetic readable;
+> a real CPMM hands out slightly less weight as reserves shift.
+
+## Interaction diagrams
+
+**Market lifecycle.**
+
+```mermaid
+flowchart LR
+ W["Waiting (0)"] -->|oracle accepts| A["Active (1)"]
+ W -->|oracle rejects| X["Deleted (-1)"]
+ A -->|betting_expiration| C["Closed (2)"]
+ A -->|early resolution| R["Resolved (3)"]
+ C -->|oracle resolves| R
+ R -->|grace, no dispute| P["Paid out"]
+ R -->|dispute filed| D["Disputed"]
+ D -->|finalize / resolver| P
+```
+
+**Settlement — NORMAL resolve (A wins).** Losers fund winners; LP principal is untouched (zero-sum).
+
+```mermaid
+flowchart TD
+ B["B loses 200 (losers_pool)"] --> POOL{"split 200"}
+ POOL -->|oracle_fee 20 + fixed 10| OR["oracle +30"]
+ POOL -->|creator_fee 10| CR["creator +10"]
+ POOL -->|liq_fee 10 + penalty 37| LPS["LPs +47"]
+ POOL -->|winners_pool 150 → profit 75| A["A → payout 175"]
+ POOL -->|profit 75 − time-penalty 37| C["C → payout 138"]
+ MK["maker + LP1 principal 3000"] -.returned in full.-> MK
+```
+
+**Dispute — oracle said A, overturned to B.** The punishment is the insurance slash (separate money).
+
+```mermaid
+sequenceDiagram
+ participant O as Oracle
+ participant D as Disputer
+ participant V as Committee / Resolver
+ O->>O: resolve A
+ D->>V: pm_dispute_create (escrow dispute_fee)
+ O-->>V: mandatory response (deadline)
+ V->>V: pm_dispute_vote / pm_dispute_resolve → overturn to B
+ V-->>O: insurance slashed (5000)
+ V-->>D: fee back + reward (2000 from slash)
+ V->>V: settlement re-runs → B wins
+```
+
+## Master ledger — NORMAL resolve (A wins)
+
+`losers_sum = 200` (B). Fees off the losers' pool:
+`oracle_fee = 200×10% = 20`, `creator_fee = 200×5% = 10`, `liq_fee = 200×5% = 10`, `oracle_fixed = 10`.
+`winners_pool = 200 − 20 − 10 − 10 − 10 = 150`. `Σ winning weight = 200` (A 100 + C 100).
+
+- **A**: profit `150×100/200 = 75`, penalty 0 → **payout 175**.
+- **C**: profit 75, time-penalty `75×50% = 37` (→ LP) → **payout 138**.
+- **LP bonus** = `liq_fee 10 + penalties 37 = 47`, split by time in market: **maker ~31 / LP1 ~16**.
+- **oracle_take** = `oracle_fee 20 + fixed 10 = 30`. **creator_take** = `creator_fee 10`.
+
+| Actor | sends | receives | net (this market) |
+|-------|-------|----------|-------------------|
+| maker | 2000 liquidity + 5 creation-fee | 2000 principal + 10 creator-fee + 31 LP-bonus | **+36** |
+| orac | (10 reg-fee, 5000 insurance locked) | 30 oracle-take | **+30** |
+| LP1 | 1000 liquidity | 1000 principal + 16 LP-bonus | **+16** |
+| A | 100 | 175 | **+75** |
+| C | 100 | 138 | **+38** |
+| B | 200 | 0 | **−200** |
+
+**Zero-sum:** in `= bets 400 + LP principal 3000 = 3400`; out `= 175+138+0 + 30 + 10 + 47 + 3000 = 3400`. ✔
+The 5 creation-fee + 10 reg-fee leave to the **DAO fund** (not part of the market pool).
+
+## Master ledger — DISPUTED resolve (oracle said A → overturned to B)
+
+`disp` escrows `dispute_fee 1000`. The verdict overturns to **B**; the oracle is slashed.
+With `dispute_penalty_percent = 10000` and consensus strength **100%**: `slash = 5000×100%×100% = 5000`.
+Reward carve-out: `reward_target = fee×3 = 3000` → `bonus = 3000 − 1000 = 2000` (≤ slash). Disputer gets
+`fee 1000 + bonus 2000 = 3000`. Remainder `slash − bonus = 3000 → forfeit_pool`.
+
+Now **B wins**. `losers_sum = 200` (A 100 + C 100). Fees 20/10/10 + fixed 10.
+`winners_pool = 200 − 50 + forfeit 3000 = 3150`. `Σ winning weight = 200` (B).
+- **B**: profit `3150×200/200 = 3150` → **payout 3350**.
+- **oracle_take** still `30` (the *market* fee is paid from the frozen config even when overturned — the
+ punishment is the **insurance slash**, separate money). **creator_take** 10. **LP bonus** = liq 10.
+
+| Actor | sends | receives | net (this market) |
+|-------|-------|----------|-------------------|
+| maker | 2000 + 5 | 2000 principal + 10 creator-fee + ~6 LP-bonus | **+11** |
+| orac | insurance −**5000** slashed | 30 oracle-take | **−4970** |
+| LP1 | 1000 | 1000 principal + ~4 LP-bonus | **+4** |
+| A | 100 | 0 | **−100** |
+| C | 100 | 0 | **−100** |
+| B | 200 | 3350 | **+3150** |
+| disp | 1000 dispute-fee | 3000 (fee back + 2000 reward) | **+2000** |
+
+**Zero-sum:** in `= bets 400 + LP principal 3000 + dispute_fee 1000 + insurance slash 5000 = 9400`;
+out `= B 3350 + oracle 30 + creator 10 + lp_bonus 10 + LP principal 3000 + disputer 3000 = 9400`. ✔
+The slash 5000 splits into disputer bonus 2000 + forfeit 3000 (→ B via the winners' pool).
+
+## Implementation status (verified against the code)
+
+**Regular operations — all 21 present** in the `operation` variant (`operations.hpp`), validated +
+evaluated in `pm_evaluator.cpp`:
+`pm_oracle_register`, `pm_oracle_update`, `pm_create_market`, `pm_oracle_accept_market`, `pm_place_bet`,
+`pm_commit_bet`, `pm_reveal_bet`, `pm_cancel_bet`, `pm_add_liquidity`, `pm_withdraw_liquidity`,
+`pm_resolve_market`, `pm_no_contest`, `pm_dispute_create`, `pm_dispute_vote`, `pm_dispute_resolve`,
+`pm_transfer_position`, `pm_lazy_deposit`, `pm_lazy_withdraw`, `pm_leverage_open`, `pm_leverage_close`,
+`pm_leverage_convert`. ✔
+
+**Virtual operations** — emitted by `database::process_pm_markets()` / the evaluators:
+
+| Virtual op | Fires? | Trigger (code) |
+|------------|--------|----------------|
+| `pm_market_accepted` | ✔ | on `pm_oracle_accept_market` (accept) **and** self-oracle `pm_create_market` |
+| `pm_payout` | ✔ | **per active bet** at settle — carries `account`, `market_id`, `bet_id`, `side`/`outcome_index`, `amount` (stake), `payout` (**0 on a loss**) |
+| `pm_auto_payout` | ✔ | **once per market** at settle — a summary marker (`bets_sum`) alongside the per-bet `pm_payout`s |
+| `pm_commit_forfeit` | ✔ | unrevealed commit past `reveal_deadline` |
+| `pm_dispute_finalize` | ✔ | committee `voting_end_time` |
+| `pm_dispute_auto_close` | ✔ | `auto_close_time` (anti-freeze) |
+| `pm_oracle_missed_penalty` | ✔ | oracle missed `result_expiration` |
+| `pm_lazy_recall` | ✔ | idle-allocation graduated recall step |
+| `pm_batch_settle` | ✔ | epoch boundary |
+| `pm_leverage_liquidate` | ✔ | mid-market liquidation: reason **0** opposing-bet, **1** cancel-bet (`cascade_liquidate`) |
+| `pm_leverage_resolve` | ✔ | **settlement** of a leveraged position: carries `market_id`, `outcome_index`, `won` (solvent ⇒ positive), `pool_received`/`bettor_received`, and `leverage` (= `total_bet/collateral`) |
+
+> A leveraged position is force-closed at its `cancel_value` at settlement: the pool takes
+> `min(cv, obligation)`, the bettor gets the rest. **`pm_leverage_resolve`** marks that close (positive
+> if `cv ≥ obligation`, else collateral lost); **`pm_leverage_liquidate`** is only for the *mid-market*
+> opposing-bet / cancel-bet cascades.
+
+See the [Plugin API](../plugins/prediction-market-api) for the read methods that surface each of these
+(`get_account_leverage_positions`, `get_market_leverage_positions`, `get_creator_ban`, `get_dispute_votes`, …);
+per-bettor results (`pm_payout`) and leverage settlements (`pm_leverage_resolve`) also appear in
+`account_history`.
+
+## Roles in the canonical scenario
+
+Every participant traced through market **M** (and the leverage sub-market **L**): its interaction
+diagram, the **signed** operations it sends, the **virtual** operations that touch it, a
+**tokens sent / received** ledger for both outcomes, and a code-verification pointer. Each per-actor
+ledger is a slice of the two [master ledgers](#master-ledger--normal-resolve-a-wins) above.
+
+### Market maker (creator + first LP)
+
+The maker creates market M, seeds the **2000** liquidity (becoming the first `pm_liquidity_object`), and
+proposes the oracle's **offer ceiling**. It does **not** resolve (that's the oracle).
+
+```mermaid
+flowchart LR
+ maker -->|pm_create_market| M[(pm_market_object status=0)]
+ maker -->|seed 2000| LP0[(pm_liquidity_object provider=maker)]
+ M -. fee 5 .-> DAO[(committee_fund)]
+ orac -->|pm_oracle_accept_market| M2[(M status=1)]
+ M2 -. VIRTUAL .-> VA[[pm_market_accepted]]
+ M2 ==>|pm_auto_payout| RET[principal 2000 + creator_fee + LP bonus]
+ RET --> maker
+```
+
+- **Sends:** `pm_create_market` (sets the `oracle_fee_percent`/`oracle_fixed_fee` **offer ceiling** plus
+ its own `creator_fee_percent` 5% and `liquidity_fee_percent` 5%; pays `pm_market_creation_fee` 5 → DAO,
+ locks `liquidity` 2000); optional `pm_add_liquidity` / `pm_withdraw_liquidity` (principal-safe, locked
+ from `betting_expiration` to resolution).
+- **Touched by:** `pm_market_accepted` (oracle accepts, or self-oracle at creation); `pm_auto_payout`
+ (returns LP principal + time-weighted LP-bonus share).
+
+| outcome | sends | receives | net |
+|---------|-------|----------|-----|
+| normal (A) | 2000 liquidity + 5 creation-fee (→DAO) | 2000 principal + **creator_fee 10** + **LP-bonus ~31** | **+36** |
+| disputed (→B) | 2000 + 5 | 2000 principal + creator_fee 10 + LP-bonus ~6 | **+11** |
+
+The creator fee is **still paid** from the frozen market config on an overturn — the dispute punishes the
+**oracle** (insurance slash), not the maker. LP principal is returned unconditionally.
+
+- **Self-oracle variant:** `oracle == creator` → active at creation, `pm_market_accepted` fires with
+ `self_oracle=true`, and the maker also earns the `oracle_take`.
+- **Verify:** `pm_create_market_evaluator`; LP via `settle_liquidity`; `committee_fund += pm_market_creation_fee`.
+ **Observe:** `get_market`, `list_markets_by_creator`, `get_market_liquidity` (`earned_fee`), `get_market_meta`.
+
+### Oracle (register → accept/quote → resolve)
+
+External oracle **orac** bonds insurance, **quotes** its fee at accept (≤ the maker's offer and ≤
+`pm_max_oracle_fee_percent`), and resolves. Its market fee is paid from the losers' pool; its bond is at
+risk only on a missed deadline or a lost dispute.
+
+```mermaid
+flowchart LR
+ orac -->|pm_oracle_register insurance 5000| O[(pm_oracle_object)]
+ orac -. reg-fee 10 .-> DAO[(committee_fund)]
+ orac -->|pm_oracle_accept_market quote fee 10% + fixed 10| M[(M status=1)]
+ M -. VIRTUAL .-> VA[[pm_market_accepted]]
+ orac -->|pm_resolve_market A| M3[(M status=3)]
+ M3 ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|oracle_take 30| orac
+```
+
+- **Sends:** `pm_oracle_register` (locks insurance 5000, pays reg-fee 10 → DAO, sets advisory list-price);
+ `pm_oracle_accept_market` (**quotes** fee 10% + fixed 10, each ≤ the creator's offer and ≤ the median
+ cap; freezes them onto M); `pm_resolve_market` (sets `winning_outcome`, opens the grace window);
+ optional `pm_oracle_update` / `pm_no_contest`. The standing list-price can also pre-authorise markets to
+ go live at creation via **auto-accept** — see the oracle ops doc.
+- **Touched by:** `pm_market_accepted`; `pm_auto_payout` (credits `oracle_take`);
+ `pm_oracle_missed_penalty` (never resolves → slashes `pm_oracle_penalty_percent` of insurance → DAO,
+ refunds all bets).
+
+| outcome | sends | receives | net |
+|---------|-------|----------|-----|
+| normal (A) | insurance 5000 (locked) + reg-fee 10 (→DAO) | **oracle_take 30** = fee 20 + fixed 10 | **+30** |
+| disputed (→B) | insurance −**5000 slashed** | oracle_take 30 | **−4970** |
+
+Even when overturned the oracle keeps the small **market fee** (frozen config); the punishment is the
+**insurance slash**, split into the disputer's reward and the winners' `forfeit_pool`. Quoting **below**
+the offer is allowed (price = reputation); quoting **above** is rejected.
+
+- **Verify:** `pm_oracle_register_evaluator`, `pm_oracle_accept_market_evaluator` (≤ offer, ≤ cap, freeze),
+ `process_pm_markets` missed-deadline scan. **Observe:** `get_oracle` (insurance, counters, reliability
+ score), `list_oracles`, `get_market` (frozen terms).
+
+### Oracle — upheld in a dispute (dispute winner)
+
+The oracle resolved **A**; a disputer challenged it but the verdict **upholds** A. The oracle keeps its
+market fee **and** collects the forfeited `dispute_fee`; insurance untouched, `disputes_won++`.
+
+```mermaid
+flowchart LR
+ orac -->|pm_resolve_market A| M[(market resolved A)]
+ disp -->|pm_dispute_create| D[(dispute)]
+ D ==>|uphold A| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|dispute_fee 1000| orac
+ FIN --> AUTO[[pm_auto_payout settles A]]
+ AUTO -->|oracle_take 30| orac
+```
+
+| outcome | sends | receives | net |
+|---------|-------|----------|-----|
+| disputed, upheld | insurance 5000 (**not** slashed) | oracle_take 30 + **dispute_fee 1000** | **+1030** |
+| normal (no dispute) | insurance 5000 (locked) | oracle_take 30 | **+30** |
+
+The challenge backfires and **pays the oracle**. A *good-faith* market (`dispute_penalty_percent < 0`)
+can even hand the oracle a fee bonus when the outcome is changed — recognising an honest mistake.
+**Verify:** uphold branch of `pm_dispute_finalize` / `pm_dispute_resolve`. **Observe:** `get_oracle`
+(`disputes_won`, higher score), `get_dispute`.
+
+### Oracle — overturned + slashed (dispute loser)
+
+The oracle resolved **A**; the dispute **overturns to B** and insurance is **slashed**. It still collects
+the tiny frozen market fee (fee and punishment are separate money) but loses a large slice of bond and
+reputation.
+
+```mermaid
+flowchart LR
+ orac -->|pm_resolve_market A| M[(resolved A)]
+ disp -->|pm_dispute_create proposed=B| D[(dispute)]
+ D ==>|overturn to B| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|slash 5000| INS[oracle.insurance ↓]
+ INS --> SPLIT[bonus 2000 → disputer 3000 → forfeit_pool → B]
+ FIN --> AUTO[[pm_auto_payout settles B]]
+ AUTO -->|oracle_take 30| orac
+```
+
+`slash = insurance 5000 × dispute_penalty_percent (100%) × consensus_strength (100%) = 5000`,
+**redistributed** not burned: `bonus 2000 →` disputer, `3000 → forfeit_pool →` the new winners (B). Net
+**−4970** vs **+30** undisputed. Slash scales with **consensus strength** (`winning_rshares /
+max_rshares`); `dispute_penalty_percent < 0` (good-faith) → **no** slash. A slashed oracle is often
+**banned** too (next role). **Verify:** overturn branch of `pm_dispute_finalize` / `pm_dispute_resolve`;
+fee still from `mkt.oracle_fee_percent`. **Observe:** `get_oracle` (`total_insurance_slashed`,
+`banned_until`), `get_dispute`.
+
+### Banned oracle (and banned creator)
+
+A ban is a **status**, not a transfer: `pm_oracle_object.banned_until` (and, for creators, a
+`pm_creator_ban_object`) blocks the actor from **new** markets until the timestamp passes. It usually
+rides along with an overturn slash, but moves no tokens by itself.
+
+```mermaid
+flowchart LR
+ resolver -->|pm_dispute_resolve ban_oracle| O[(pm_oracle_object banned_until = T)]
+ orac -->|pm_create_market / accept| CHK{now < banned_until?}
+ CHK -->|yes| REJ[REJECTED: 'Oracle is banned']
+ CHK -->|no, expired| OK[allowed again]
+ resolver -->|ban_creator| CB[(pm_creator_ban_object)]
+ maker -->|pm_create_market| CHK2{banned?}
+ CHK2 -->|yes| REJ2[REJECTED: 'Creator is banned']
+```
+
+- **Who sets it:** account mode → `pm_dispute_resolve` (`ban_oracle`/`ban_creator` + `…_until`); committee
+ mode → `pm_dispute_finalize` scales a ban by consensus on overturn. `banned_until =
+ time_point_sec::maximum()` ⇒ **permanent**.
+- **Tokens:** the ban itself is **0** (pure status); the accompanying slash is the overturn case above.
+ Insurance stays locked, refundable after the ban lifts and no active markets remain.
+- Bans survive snapshots and are keyed by account — re-registration cannot wipe one. **Verify:**
+ `pm_create_market_evaluator` (`"Oracle is banned"` / `"Creator is banned"`). **Observe:** `get_oracle`
+ (`banned_until`, `bans_received`), **`get_creator_ban(account)`**.
+
+### Bettor A — early winner
+
+**A** stakes **100 on side A early** (no time penalty) and wins when M resolves to A. Payout = stake +
+weight-proportional share of the winners' pool.
+
+```mermaid
+flowchart LR
+ A -->|pm_place_bet side=A 100| BET[(pm_bet_object weight 100)]
+ BET --> M[(market M reserves shift)]
+ M ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|payout 175| A
+```
+
+| outcome | sends | receives | net |
+|---------|-------|----------|-----|
+| normal (A) | 100 | **175** | **+75** |
+| disputed (→B) | 100 | **0** | **−100** |
+
+`profit = winners_pool 150 × weight 100 / Σweight 200 = 75`; no penalty → payout `100 + 75`. An overturn
+makes A the **losing** side. Winnings come **only** from losers' stakes (+ forfeit pool), never emission.
+**Sends:** `pm_place_bet` (`side=0`, instant); optional `pm_transfer_position` / `pm_cancel_bet`.
+**Verify:** `pm_place_bet_evaluator`, `settle_market`. **Observe:** `get_account_positions`
+(`expected_payout`), `get_market_weight_sums`; realized `pm_payout` in `account_history`.
+
+### Bettor B — loser
+
+**B** stakes **200 on side B**. When M resolves to **A**, B's stake funds the winners and B gets nothing.
+In the disputed path B becomes the winner.
+
+```mermaid
+flowchart LR
+ B -->|pm_place_bet side=B 200| BET[(pm_bet_object status active)]
+ BET --> M[(market M)]
+ M ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|status=resolved, payout 0| BET
+```
+
+| outcome | sends | receives | net |
+|---------|-------|----------|-----|
+| normal (A) | 200 | **0** | **−200** |
+| disputed (→B) | 200 | **3350** | **+3150** |
+
+B's 200 **is** the `losers_sum` (pays the 40 fees + 150 winners' pool + LP bonus) — the parimutuel
+"losers fund winners" rule. On overturn B wins and the oracle's forfeit 3000 is injected into B's pool
+(`payout = 200 + 3150`). A losing bet is still recorded by a `pm_payout` with **payout=0**. **Verify:**
+`settle_market` loser branch. **Observe:** `get_account_positions`, `get_market_bets`, `get_dispute`.
+
+### Bettor C — late winner (time penalty)
+
+**C** stakes **100 on side A** but **late** (T+85% of the betting window), so a **time penalty** docks
+its *profit only* (not principal). Same weight as A, but earns less; the docked amount flows to the LPs.
+
+```mermaid
+flowchart LR
+ C -->|pm_place_bet side=A 100 at T+85%| BET[(pm_bet_object weight 100 time_penalty 50%)]
+ BET --> M[(market M)]
+ M ==>|grace passes| VP[[pm_auto_payout]]
+ VP -->|payout 138| C
+ VP -. penalty 37 .-> LPb[LP bonus]
+```
+
+| outcome | sends | receives | net |
+|---------|-------|----------|-----|
+| normal (A) | 100 | **138** | **+38** |
+| disputed (→B) | 100 | **0** | **−100** |
+
+`profit = 75`; `penalty = 75 × 50% = 37` (→ LPs); `payout = 100 + 75 − 37 = 138` — **−37** vs A's +75 for
+the same weight. The node stamps `time_penalty` at placement from the market's penalty curve
+(`time_penalty_type/value`, `penalty_curve_type`). It discourages last-second sniping and subsidises
+liquidity, not the protocol. **Verify:** `pm_place_bet_evaluator` (curve eval), `compute_settlement`.
+**Observe:** `get_account_positions` (`time_penalty`), `get_market_bets`.
+
+### Bettor D — leverage ×10 winner
+
+**D** opens a **×10** position: **10 collateral + 90 loan** from the [lazy pool](#the-lazy-liquidity-pool-system-object)
+= **100** on side A, in isolated leverage market **L** (`pm_leverage_enabled=true`, `R = 10%`). When A
+wins, D keeps the upside on the full 100 after repaying loan + interest.
+`pool_profit = loan 90 × R 10% = 9`; `obligation = 90 × 1.10 = 99`.
+
+```mermaid
+flowchart LR
+ D -->|pm_leverage_open collateral 10 + loan 90| POS[(pm_leverage_position total_bet 100, obligation 99)]
+ POOL[(lazy pool)] -.loan 90.-> POS
+ POS --> L[(market L, side A)]
+ L ==>|settle: force_close at cancel_value| VR[[pm_leverage_resolve won=true, leverage=10]]
+ VR -->|min(cv,obligation) 99| POOL
+ VR -->|cv 200 − 99 = 101| D
+```
+
+| outcome | sends | receives | net |
+|---------|-------|----------|-----|
+| normal (A) | collateral **10** | cancel_value 200 − obligation 99 = **101** | **+91** |
+| disputed (→B) | collateral 10 | 0 | **−10** |
+
+A profitable position closes at its `cancel_value`; the pool reclaims `obligation 99` (loan 90 + **9
+interest**), D keeps the rest on just 10 of its own → **+91** (pool **+9**). Leverage settles by
+liquidation, **never** via `pm_auto_payout`. Zero-sum (L): in `10 + 90 + 100 = 200`; out `101 + 99 =
+200`. **Sends:** `pm_leverage_open`; optional `pm_leverage_close` (only if `cv ≥ obligation`) /
+`pm_leverage_convert`. **Touched by:** `pm_leverage_resolve` (settlement force-close,
+`reason=expiration`). **Verify:** `force_close_positions` → `liquidate_position(reason=2)`. **Observe:**
+**`get_account_leverage_positions`** / **`get_market_leverage_positions`**, `get_lazy_pool`.
+
+### Bettor E — leverage ×5 liquidated
+
+**E** opens a **×5** position: **20 collateral + 80 loan** = **100** on side B (market **L**, `R = 10%`).
+Before resolution an **opposing bet** moves the curve against B; the **cascade liquidation** force-closes
+it. **E loses its collateral, but the pool is always made whole.** `obligation = 80 × 1.10 = 88`.
+
+```mermaid
+flowchart LR
+ E -->|pm_leverage_open collateral 20 + loan 80| POS[(pm_leverage_position obligation 88)]
+ POOL[(lazy pool)] -.loan 80.-> POS
+ X -->|pm_place_bet side=A| L[(market L)]
+ L ==>|cascade at PRE-bet reserves cv 88 ≤ threshold| VL[[pm_leverage_liquidate reason=opposing_bet]]
+ VL -->|pool_received 88 = loan 80 + profit 8| POOL
+ VL -->|bettor_received 0| E
+```
+
+E is liquidated **before** resolution, so the final A/B result (disputed or not) doesn't change it:
+
+| actor | sends | receives | net |
+|-------|-------|----------|-----|
+| **E** | collateral **20** | **0** | **−20** |
+| **pool** | loan 80 | **88** (loan 80 + R% 8) | **+8** |
+
+Opposing-bet liquidations run at **pre-bet** reserves where `cancel_value ≥ loan`, so `pool_received =
+min(cv, obligation)` returns at least the loan — the pool **never** loses.
+
+> **The only path that can go negative** is a same-side **`pm_cancel_bet` (Case B)**: a cancel reverses a
+> *prior, large* same-side bet (beyond the per-bet slippage cap) and, for fairness to the cancel-bettor,
+> executes **first** at their submitted price — so the cascade can land `cancel_value < loan`:
+> `shortfall = obligation − cancel_value`, `lazy_pool.free_balance −= shortfall`. This **bad debt** is
+> **bounded** (`≤ cancel_value_before × SL%`) and **rare** (the pool's R% on every other position offsets
+> it). Covered by `leverage_cancel_bet_cascade_bad_debt`.
+
+Pool protection is structural (`max_per_position`, `max_position_ratio`, `safety_margin`, the slippage
+cap, the `expiration_buffer`). `pm_leverage_enabled=false` blocks **new** opens only — the liquidation
+cascade is **not** gated by the flag, so governance can never strip the pool's protection mid-flight
+(`leverage_disabled_keeps_liquidation_protection`). **Verify:** `pm_place_bet` →
+`cascade_liquidate(reason=0)`; `pm_cancel_bet` → `cascade_liquidate(reason=1)`; `liquidate_position`.
+**Observe:** **`get_account_leverage_positions`** (`status=1`, `pool_received`, `bettor_received`),
+`get_lazy_pool`.
+
+### Liquidity provider in the market (LP1)
+
+**LP1** adds **1000** liquidity to active M (after the maker's seed). Principal is **always** returned;
+on top it earns a **time-weighted** slice of the LP bonus (liquidity fee + time penalties + dust).
+Distinct from a [lazy-pool provider](#liquidity-provider-in-the-lazy-pool-lz1), who deposits once and is
+auto-allocated across many markets.
+
+```mermaid
+flowchart LR
+ LP1 -->|pm_add_liquidity 1000| L1[(pm_liquidity_object provider=LP1)]
+ L1 --> M[(market M reserves)]
+ M ==>|settle| SL[[settle_liquidity]]
+ SL -->|principal 1000 + bonus ~16| LP1
+ LP1 -->|pm_withdraw_liquidity after resolution| OUT[principal-safe exit]
+```
+
+| outcome | sends | receives | net |
+|---------|-------|----------|-----|
+| normal (A) | 1000 | **1000 principal + ~16 bonus** | **+16** |
+| disputed (→B) | 1000 | 1000 principal + ~4 bonus | **+4** |
+
+LP bonus pool = `liq_fee 10 + time-penalties 37 = 47`, split by `principal × seconds-in-market` (earlier
+maker ~31, later LP1 ~16). The **principal guarantee** is architectural — the seed is returned before any
+winner is paid; an LP can only forgo bonus, never lose principal. Withdrawing is locked from
+`betting_expiration` until resolution. **Verify:** `pm_add_liquidity_evaluator` (records `deposit_time`),
+`settle_liquidity` → `distribute_lp`. **Observe:** `get_market_liquidity` (`earned_fee`),
+`get_market_weight_sums`.
+
+### The Lazy Liquidity Pool (system object)
+
+A **singleton** `pm_lazy_pool_object` — not an account. Depositors fund it once; the pool
+**auto-allocates** a slice to each accepted market as a silent LP (`pm_liquidity_object` with empty
+`provider`), **funds leverage loans**, and **recalls** idle allocations. It earns LP yield + leverage
+interest, accounted MasterChef-style (one global `reward_per_share`, O(1) — see the whitepaper).
+Fields: `total_shares`, `free_balance`, `allocated_balance`, `earned_balance`, `reward_per_share`,
+`leverage_fund_used`.
+
+```mermaid
+flowchart TD
+ LZ1 -->|pm_lazy_deposit 1000| POOL[(pm_lazy_pool free 1000 / shares 1000)]
+ POOL ==>|on market accept alloc 20% = 200| ALLOC[(pm_lazy_allocation + pm_liquidity provider=∅)]
+ ALLOC -->|market settles| YLD[route_pool_lp_return principal 200 + yield 20]
+ YLD --> POOL
+ POOL -->|leverage loan 90| Dpos[D position]
+ Dpos -->|close/resolve: 90 + interest 9| POOL
+ POOL -. idle market .-> VR[[pm_lazy_recall]]
+ VR -->|step back to free| POOL
+```
+
+| pool money flow | effect |
+|-----------------|--------|
+| `pm_lazy_deposit` | `free_balance += amount`, mint shares |
+| auto allocation (on accept) | `free → allocated` (silent LP) |
+| market settles | `route_pool_lp_return`: principal + yield → `free`; yield → `earned` & `reward_per_share` |
+| leverage open (D/E) | `free −= loan`, `leverage_fund_used += loan` |
+| leverage close / resolve / opposing-bet liquidation | `min(cv, obligation) → free`; `cv ≥ loan` ⇒ **never a loss** |
+| cancel-bet liquidation (Case B only) | recovers `cv` which **may be < loan** → bounded **bad debt** |
+| `pm_lazy_recall` (idle market) | one 10% step of an idle allocation → `free` |
+| `pm_lazy_withdraw` | burn shares → principal + pending; emergency penalty stays in pool |
+
+Over the canonical scenario the pool nets **+37 earned** (market M yield +20, leverage D interest +9,
+leverage E opposing-bet recovery +8). As a market LP its principal is returned unconditionally; only the
+*bonus* yield varies with a dispute.
+
+> The pool serves **both** roles from a single `free_balance`: market-LP allocations
+> (`maybe_allocate_lazy`) and leverage loans (`leverage_fund_used` caps the latter). All leverage knobs
+> are checked **at `pm_leverage_open` time** against the current median, so later property swings only
+> affect *new* opens, never loans already out.
+
+VIZ in the pool is **liquid**, not vested → **no** validator-scheduling or committee-request weight.
+**Exception (HF14):** for **PM committee disputes** a depositor's pool stake **is** counted — converted
+to vesting-shares via `get_vesting_share_price()` and added to their `pm_dispute_vote` weight (see
+[committee resolver](#resolver--committee-stake-weighted-dispute_mode--0)). **Verify:**
+`apply_hardfork(CHAIN_HARDFORK_14)` (singleton), `maybe_allocate_lazy`, `route_pool_lp_return`.
+**Observe:** `get_lazy_pool`.
+
+### Liquidity provider in the lazy pool (LZ1)
+
+**LZ1** deposits **1000** into the pool **once** and lets it spread across markets + leverage loans. It
+earns a share of the pool's aggregate yield (`reward_per_share`), not any single market's outcome. Two
+exits: **planned** (after the lock) and **emergency** (before the lock, with a penalty on *profit*).
+
+```mermaid
+flowchart LR
+ LZ1 -->|pm_lazy_deposit 1000| DEP[(pm_lazy_deposit_object shares 1000, unlock=+7d)]
+ DEP --> POOL[(lazy pool)]
+ POOL -. yield accrues .-> RPS[reward_per_share ↑]
+ LZ1 -->|pm_lazy_withdraw| OUT{planned or emergency?}
+ OUT -->|planned, t≥unlock| P[principal 1000 + pending 29]
+ OUT -->|emergency, t|pm_dispute_create proposed=B escrow fee 1000| D[(pm_dispute_object status open)]
+ D --> VOTE{committee vote or account resolve}
+ VOTE ==>|overturn to B| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|fee 1000 + bonus 2000| disp
+ FIN -.slash 5000 from oracle.-> SPLIT[bonus 2000 → disp 3000 → forfeit_pool → winners]
+```
+
+| outcome | sends | receives | net |
+|---------|-------|----------|-----|
+| disputed, overturned (the "win") | dispute_fee **1000** | fee 1000 back + **bonus 2000** | **+2000** |
+
+`reward_target = fee × pm_dispute_reward_multiplier (3×) = 3000` → `bonus = 3000 − 1000 = 2000`, **capped
+at the actual slash**; the remainder (3000) → `forfeit_pool` → the new winners. disp risked 1000, walks
+away **+2000**. (Committee mode: disp does **not** vote on its own — the SHARES electorate does.)
+**Verify:** `pm_dispute_create_evaluator`, overturn branch of `pm_dispute_finalize`/`pm_dispute_resolve`.
+**Observe:** `get_dispute`, `get_dispute_votes`.
+
+### Disputer — fee forfeited (oracle upheld)
+
+**disp** disputes the oracle's **A** but the verdict **upholds the oracle**. The escrowed fee is
+**forfeited to the oracle** as compensation, and the market settles as originally resolved (A wins).
+
+```mermaid
+flowchart LR
+ disp -->|pm_dispute_create proposed=B escrow fee 1000| D[(pm_dispute_object)]
+ D --> VOTE{committee vote or account resolve}
+ VOTE ==>|uphold oracle A| FIN[[pm_dispute_finalize / resolve]]
+ FIN -->|dispute_fee 1000| orac[oracle compensation]
+ FIN -->|market settles as A| AUTO[[pm_auto_payout]]
+```
+
+| outcome | sends | receives | net |
+|---------|-------|----------|-----|
+| disputed, upheld (the "loss") | dispute_fee **1000** | **0** | **−1000** |
+
+The fee is the disputer's skin-in-the-game: a wrong/frivolous dispute pays the oracle. This asymmetry
+(lose the fee if wrong, win a multiple if right) keeps the channel honest. A dispute that is never
+**decided** (oracle silent / no quorum) is force-closed and the fee **returned** (net 0) — see
+[dispute auto-close](#dispute-forced-to-end-anti-freeze-auto-close), which is different from losing on
+the merits. **Verify:** uphold branch of `pm_dispute_finalize`/`pm_dispute_resolve`. **Observe:**
+`get_dispute`, `get_oracle` (gains the fee, `disputes_won++`).
+
+### Resolver — committee (stake-weighted, dispute_mode = 0)
+
+The *whole SHARES electorate* decides by **stake-weighted vote**; no single resolver account. The verdict
+is tallied deterministically by `pm_dispute_finalize` at `voting_end_time`.
+
+**Voting weight** = live **`effective_vesting_shares`** (`vesting − delegated + received`) **plus
+lazy-pool stake converted to vesting-shares**, since many DAO members park VIZ in the pool (where it is
+liquid):
+
+```
+pool_claim_viz = pool_NAV × deposit.shares / pool.total_shares
+pool_weight = pool_claim_viz × get_vesting_share_price()
+voter_weight = effective_vesting_shares + pool_weight
+```
+
+The participation quorum denominator is likewise `total_vesting_shares + (pool_NAV → vesting-shares)`. The
+7-day deposit lock prevents deposit-vote-withdraw gaming.
+
+```mermaid
+flowchart LR
+ V1[voter · eff_vshares] -->|pm_dispute_vote outcome,percent| D[(pm_dispute_votes)]
+ V2[voter · eff_vshares] -->|pm_dispute_vote| D
+ D ==>|voting_end_time| FIN[[pm_dispute_finalize]]
+ FIN -->|argmax rshares, threshold check| VERDICT{uphold / overturn}
+ VERDICT -->|consensus_strength scales slash & bans| OUT[settle]
+```
+
+- **Sends (voters):** `pm_dispute_vote` — **auth `regular`**. `vote_outcome = -1` upholds, else proposes
+ the correct outcome; `vote_percent ∈ [-10000, 10000]`. A voter may **revise** their ballot any number
+ of times while voting is open — a repeat vote **overwrites** the prior (latest wins, no "Already voted").
+- **No commit-reveal — deliberate, will NOT change.** A committee dispute is an **open public hearing**:
+ the running tally is visible (`get_dispute_votes`) and votes are not hidden. The DAO's value
+ proposition is resolving disputes as truthfully and transparently as possible; new evidence surfaces
+ during voting and voters are *expected* to update; and voters are **not paid** for matching the
+ majority, so the usual anti-herding (beauty-contest) rationale for commit-reveal does not apply.
+
+| actor | sends | receives |
+|-------|-------|----------|
+| each voter | 0 | **0** — voting is a governance duty, not a paid action |
+
+Voters never receive tokens; influence is pure stake weight. Economic flows land on the disputer, oracle,
+and bettors per the [disputed master ledger](#master-ledger--disputed-resolve-oracle-said-a--overturned-to-b).
+Niche markets may fail the threshold → fall through to
+[dispute auto-close](#dispute-forced-to-end-anti-freeze-auto-close). **Verify:** `pm_dispute_vote_evaluator`
+(modify-or-create on `by_market_voter`); `pm_dispute_finalize` (`lazy_vote_weight`, `get_vesting_share_price`,
+quorum, argmax, `consensus_strength`). **Observe:** `get_dispute_votes` (live tally + finalize projection:
+`quorum_percent_bp`, `expected_uphold`, `expected_outcome`, `expected_consensus_strength_bp`). Tests:
+`committee_dispute_lazy_pool_voting_weight`, `committee_dispute_flips_outcome`.
+
+### Resolver — single account (centralized, dispute_mode = 1)
+
+The market names one `dispute_resolver` account (e.g. a regulator multisig) that decides alone — **no
+stake weight, no DAO vote**. Set at creation, it must differ from both `oracle` and `creator` (anti
+self-judging). Same op set as committee mode; only *who decides* differs.
+
+```mermaid
+flowchart LR
+ disp -->|pm_dispute_create| D[(dispute, mode=1)]
+ resolver -->|pm_dispute_resolve correct_outcome=B penalty_amount, ban flags| FIN[[verdict]]
+ FIN -->|slash penalty_amount| orac[oracle.insurance ↓]
+ FIN -->|fee + reward| disp
+ FIN --> AUTO[[pm_auto_payout settles B]]
+```
+
+- **Sends:** `pm_dispute_resolve` — **auth `active` of the named `dispute_resolver`** only:
+ `correct_outcome`, `penalty_amount` (insurance to slash — a fixed amount, **not** stake-scaled),
+ `ban_oracle`/`ban_creator` (+ `…_until`).
+
+| actor | sends | receives |
+|-------|-------|----------|
+| resolver | 0 | **0** — a neutral arbiter |
+
+The post-verdict canon is identical to committee mode; only the slash size differs (resolver-set
+`penalty_amount`, no `consensus_strength` scaling, since there is a single decider). KYC/whitelisting of
+the resolver is a **client-layer** concern. **Verify:** `pm_dispute_resolve_evaluator` (only the named
+resolver, `dispute_mode==1`). **Observe:** `get_dispute`, `get_oracle`, **`get_creator_ban(account)`**.
+
+### Dispute forced to end (anti-freeze auto-close)
+
+A dispute that is **never decided** — oracle silent and (committee) no quorum — cannot freeze the market
+forever. At `auto_close_time` the `pm_dispute_auto_close` processor force-ends it: **everyone is
+refunded**, the disputer's fee is **returned**, and the unresponsive oracle is penalised. No winner is
+picked.
+
+```mermaid
+flowchart LR
+ disp -->|pm_dispute_create escrow fee 1000| D[(dispute, status open)]
+ D -. oracle silent / no quorum .-> WAIT[auto_close_time reached]
+ WAIT ==>|VIRTUAL| AC[[pm_dispute_auto_close]]
+ AC -->|refund all bets| bettors
+ AC -->|fee 1000 back| disp
+ AC -->|insurance slash → DAO| orac
+```
+
+| actor | sends | receives | net |
+|-------|-------|----------|-----|
+| A / B / C | bet | full refund | **0** |
+| maker / LP1 | liquidity | principal back | **0** (no bonus) |
+| disp | dispute_fee 1000 | **1000 back** | **0** |
+| orac | insurance −slash → DAO | — | **− slash** |
+
+This is **not** "the disputer lost": a returned fee (net 0) differs from a forfeited fee
+([disputer-loser](#disputer--fee-forfeited-oracle-upheld), net −1000). Nobody profits; the market is
+voided to break the freeze, the cost falls on the oracle that didn't respond. The same void-and-refund
+shape covers `pm_oracle_missed_penalty` and `pm_no_contest`. Tune `pm_dispute_auto_close_sec` (14 d) vs
+`pm_dispute_vote_period_sec` (3 d) so honest disputes resolve first. **Verify:** `process_pm_markets`
+auto-close scan (`refund_all_bets` + `return_liquidity` + fee credit; `disputes_auto_closed++`).
+**Observe:** `get_dispute` (status → auto-closed), `get_market`, `get_oracle`.
diff --git a/docs/protocol/operations/overview.md b/docs/protocol/operations/overview.md
index 6b695bc64d..41fa9eeffd 100644
--- a/docs/protocol/operations/overview.md
+++ b/docs/protocol/operations/overview.md
@@ -52,6 +52,32 @@ These are user-initiated operations that can be broadcast to the network.
| 58 | `use_invite_balance_operation` | active | [Invites](./invites.md) |
| 60 | `fixed_award_operation` | regular | [Awards](./awards.md) |
| 61 | `target_account_sale_operation` | master | [Account Market](./account-market.md) |
+| 64 | `set_reward_sharing_operation` | active | [Validators](./validators.md) |
+| 66 | `pm_oracle_register_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 67 | `pm_oracle_update_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 68 | `pm_create_market_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 69 | `pm_oracle_accept_market_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 70 | `pm_place_bet_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 71 | `pm_commit_bet_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 72 | `pm_reveal_bet_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 73 | `pm_cancel_bet_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 74 | `pm_add_liquidity_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 75 | `pm_withdraw_liquidity_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 76 | `pm_resolve_market_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 77 | `pm_no_contest_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 78 | `pm_dispute_create_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 79 | `pm_dispute_vote_operation` | regular | [Prediction Markets](./prediction-markets.md) |
+| 80 | `pm_dispute_resolve_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 81 | `pm_transfer_position_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 82 | `pm_lazy_deposit_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 83 | `pm_lazy_withdraw_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 91 | `pm_leverage_open_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 92 | `pm_leverage_close_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 93 | `pm_leverage_convert_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 98 | `pm_dispute_oracle_respond_operation` | active | [Prediction Markets](./prediction-markets.md) |
+| 99 | `pm_unban_operation` | active | [Prediction Markets](./prediction-markets.md) |
+
+> IDs are the fixed index in the chain's single `operation` variant (append-only). Gaps in this table are **virtual** operations (below) interleaved by ID — e.g. 62–63, 65, 84–90, 94–97, 100.
---
@@ -83,6 +109,19 @@ Virtual operations are generated by the blockchain itself during block processin
| 59 | `expire_escrow_ratification_operation` | Escrow deadline missed | [Virtual Ops](../virtual-operations.md) |
| 62 | `bid_operation` | Auction bid placed | [Virtual Ops](../virtual-operations.md) |
| 63 | `outbid_operation` | Auction outbid | [Virtual Ops](../virtual-operations.md) |
+| 65 | `stakeholder_reward_operation` | Reward-sharing payout to a stakeholder | [Validators](./validators.md) |
+| 84 | `pm_batch_settle_operation` | Batch epoch boundary settled | [Prediction Markets](./prediction-markets.md) |
+| 85 | `pm_commit_forfeit_operation` | Commit-reveal escrow forfeited (unrevealed) | [Prediction Markets](./prediction-markets.md) |
+| 86 | `pm_auto_payout_operation` | Market settled (per-market payout marker) | [Prediction Markets](./prediction-markets.md) |
+| 87 | `pm_dispute_finalize_operation` | Committee vote tallied | [Prediction Markets](./prediction-markets.md) |
+| 88 | `pm_dispute_auto_close_operation` | Dispute anti-freeze auto-close | [Prediction Markets](./prediction-markets.md) |
+| 89 | `pm_oracle_missed_penalty_operation` | Oracle missed resolution deadline | [Prediction Markets](./prediction-markets.md) |
+| 90 | `pm_lazy_recall_operation` | Lazy-pool graduated recall step | [Prediction Markets](./prediction-markets.md) |
+| 94 | `pm_leverage_liquidate_operation` | Leveraged position liquidated | [Prediction Markets](./prediction-markets.md) |
+| 95 | `pm_leverage_resolve_operation` | Leveraged position settled at resolution | [Prediction Markets](./prediction-markets.md) |
+| 96 | `pm_market_accepted_operation` | Market went live (oracle accepted / self / auto) | [Prediction Markets](./prediction-markets.md) |
+| 97 | `pm_payout_operation` | Per-bettor parimutuel payout | [Prediction Markets](./prediction-markets.md) |
+| 100 | `pm_ban_expired_operation` | Temporary oracle/creator ban lapsed | [Prediction Markets](./prediction-markets.md) |
---
diff --git a/docs/protocol/operations/prediction-markets.md b/docs/protocol/operations/prediction-markets.md
index bf977a9d18..33e93e533f 100644
--- a/docs/protocol/operations/prediction-markets.md
+++ b/docs/protocol/operations/prediction-markets.md
@@ -106,6 +106,8 @@ Creates a market; the creator seeds the first liquidity and becomes the first LP
Oracle accepts (`status → active`) or rejects (liquidity refunded to creator; `status → deleted`) a pending market. On accept the oracle **quotes its actual terms** via `oracle_fee_percent` + `oracle_fixed_fee` — each must be `≤` the creator's offer on the market and `oracle_fee_percent ≤ pm_max_oracle_fee_percent`. The quote is **frozen onto the market** and a `pm_market_accepted` virtual op is emitted (so history parsers see the launch + terms). Settlement later reads only these frozen fields — never the live median.
+The oracle must act within `pm_oracle_accept_window_sec` (default 1h) of creation. If it does neither by the market's `accept_deadline`, the per-block cron voids the market (`status → deleted`), refunds the creator's seed liquidity (**not** the non-refundable creation fee), and emits `pm_market_expired` (see Virtual operations).
+
| Field | Type | Description |
|-------|------|-------------|
| `market_id` | `int64` | Pending market |
@@ -155,7 +157,14 @@ Withdraws liquidity (principal-safe). Locked from `betting_expiration` until res
### `pm_resolve_market_operation` (ID 76)
**Auth:** `active` of `oracle`
-Oracle resolves to `winning_outcome`. Opens the dispute grace window (`result_expiration + pm_dispute_grace_sec`); after it elapses `pm_auto_payout` settles.
+Oracle resolves to `winning_outcome`. Opens the dispute grace window (`result_expiration + pm_dispute_grace_sec`); after it elapses `pm_auto_payout` settles. The oracle's resolution statement is **stored on the market** (like an oracle's `rules_url`) so a client can read it directly via `get_market` without scanning history.
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `market_id` | `int64` | Target market |
+| `winning_outcome` | `int16_t` | Winning outcome index |
+| `decision_url` | `string` | Evidence link, `≤ MAX_PM_DECISION_URL_LEN`; stored on the market |
+| `decision_reason` | `string` | Free-text justification, `≤ MAX_PM_DISPUTE_REASON_LEN`; stored on the market (`decision_reason`) |
### `pm_no_contest_operation` (ID 77)
**Auth:** `active` of `oracle`
@@ -177,7 +186,39 @@ A committee dispute is an **open public hearing** — there is **no commit-revea
### `pm_dispute_resolve_operation` (ID 80)
**Auth:** `active` of `resolver`
-Account-mode verdict by the market's configured `dispute_resolver`. May slash `penalty_amount` of insurance and ban the oracle/creator until the given times.
+Account-mode verdict by the market's configured `dispute_resolver`. May slash `penalty_amount` of insurance and ban the oracle/creator until the given times (`ban_*_until = time_point_sec::maximum()` = permanent).
+
+> **Bans are a compliance/regulator feature, exclusive to account mode.** When a market routes its disputes to an account-mode `dispute_resolver` (e.g. a regulator or a licensed arbitrator), that resolver can sanction **both the oracle and the market creator** — temporarily or permanently — in the same verdict, on top of the insurance slash: it lets a regulator serving as resolver bar a bad-faith oracle or a repeat-offender creator from the platform. **Committee/DAO mode (`dispute_mode == 0`) has no ban power by design** — it is a transparent public hearing that only slashes insurance and adjusts reputation (`pm_dispute_finalize`), never bans. A ban set here records the issuing `resolver` in the target's `banned_by`, so only that resolver may lift it early via `pm_unban`; otherwise it lapses at `banned_until` (the cron emits `pm_ban_expired`).
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `market_id` | `int64` | Disputed market |
+| `correct_outcome` | `int16_t` | Final correct outcome (`-1` = void/no-contest) |
+| `penalty_amount` | `asset` (VIZ) | Oracle insurance to slash |
+| `ban_oracle` / `ban_oracle_until` | `bool` / `time_point_sec` | Ban the oracle until the given time |
+| `ban_creator` / `ban_creator_until` | `bool` / `time_point_sec` | Ban the creator from creating markets until the given time |
+
+### `pm_dispute_oracle_respond_operation` (ID 98)
+**Auth:** `active` of `oracle`
+
+The market's oracle posts a **public rebuttal** onto an open dispute. Because a dispute is a public hearing, the text is stored on the dispute object (`oracle_response` / `oracle_response_time`, readable via `get_dispute`) so every voter/resolver can weigh it. Allowed only while the dispute is open and `now ≤ oracle_response_deadline`; re-posting overwrites the previous response.
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `market_id` | `int64` | Disputed market |
+| `response` | `string` | Rebuttal text, non-empty, `≤ MAX_PM_DISPUTE_REASON_LEN` |
+
+### `pm_unban_operation` (ID 99)
+**Auth:** `active` of `resolver`
+
+Lifts a ban imposed by an account-mode `pm_dispute_resolve` **early**. Only the account recorded in the target's `banned_by` (the resolver that set the ban) may lift it; at least one of `unban_oracle` / `unban_creator` must be set, and the corresponding ban must currently be active. Sets `banned_until` to a past time and clears `banned_by`. (Bans not lifted here simply expire at `banned_until` — the cron then emits `pm_ban_expired`.)
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `resolver` | `account_name_type` | The account that imposed the ban (must equal the target's `banned_by`) |
+| `target` | `account_name_type` | The banned oracle / creator |
+| `unban_oracle` | `bool` | Clear the oracle ban (`pm_oracle_object.banned_until`) |
+| `unban_creator` | `bool` | Clear the creator ban (`pm_creator_ban_object.banned_until`) |
### `pm_transfer_position_operation` (ID 81)
**Auth:** `active` of `from`
@@ -213,9 +254,12 @@ Emitted by the PM consensus logic — either by a signed operation's evaluator (
| 95 | `pm_leverage_resolve_operation` | Settlement — leveraged position force-closed: `outcome_index`, `won`, `pool_received`/`bettor_received`, `leverage` |
| 96 | `pm_market_accepted_operation` | Evaluator — market went live: oracle accepted, self-oracle, or auto-accept; frozen oracle terms + `self_oracle` |
| 97 | `pm_payout_operation` | Settlement — per active bet: `amount` (stake), `side`/`outcome_index`, `payout` (**0 on a loss**) |
+| 100 | `pm_ban_expired_operation` | A temporary oracle/creator ban lapsed at `banned_until`: the cron cleared it (fields `account`, `oracle`, `creator`). Early manual lifts use the signed `pm_unban` instead |
+| 101 | `pm_market_expired_operation` | A pending market's `accept_deadline` passed: the oracle never accepted/rejected within `pm_oracle_accept_window_sec` — market voided, seed refunded (creation fee kept). Fields `oracle`, `creator`, `market_id`, `refunded_liquidity` |
> IDs 91–93 are the *regular* ops `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert` (see the
-> spec). Per-bettor results are `pm_payout`; the per-market `pm_auto_payout` remains a settlement marker.
+> spec); IDs 98–99 are the *regular* ops `pm_dispute_oracle_respond`/`pm_unban` (above). Per-bettor
+> results are `pm_payout`; the per-market `pm_auto_payout` remains a settlement marker.
---
diff --git a/docs/protocol/operations/validators.md b/docs/protocol/operations/validators.md
index 5089314540..599325782a 100644
--- a/docs/protocol/operations/validators.md
+++ b/docs/protocol/operations/validators.md
@@ -163,4 +163,39 @@ Delegates all validator voting to a proxy account. All existing direct votes are
---
+## `set_reward_sharing_operation` (ID 64)
+
+**Auth:** `active` of `owner`
+
+**HF13 validator reward sharing.** A validator opts to forward a fraction of its block reward to its **stakeholders** — the accounts that voted for it — proportionally to time-weighted vote weight. `sharing_rate` is that fraction in basis points; the shared pool accumulates and is distributed at each epoch end via the `stakeholder_reward` virtual op.
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `owner` | `account_name_type` | Validator setting its sharing rate |
+| `sharing_rate` | `uint16_t` | Fraction of block reward forwarded to stakeholders, in basis points (0 = none, 10000 = 100%) |
+
+```json
+[64, {
+ "owner": "alice",
+ "sharing_rate": 2500
+}]
+```
+
+- `sharing_rate` is capped at 10000 (100%).
+- Distribution is by **time-weighted** vote weight, so recently-added votes earn a smaller share until they mature.
+
+---
+
+## `stakeholder_reward_operation` (ID 65) — virtual
+
+Emitted at each distribution epoch when a validator with a non-zero `sharing_rate` pays out a stakeholder's share of the shared block reward. Virtual (never signed); appears in `account_history`.
+
+| Field | Type | Description |
+|-------|------|-------------|
+| `validator` | `account_name_type` | The validator that shared the reward |
+| `stakeholder` | `account_name_type` | The voter receiving a share |
+| `shares` | `asset` (SHARES) | Amount credited to the stakeholder |
+
+---
+
See also: [Data Types](../data-types.md), [Operations Overview](./overview.md), [Chain Properties](../../governance/chain-properties.md).
diff --git a/docs/protocol/virtual-operations.md b/docs/protocol/virtual-operations.md
index 6e38c7b3d7..b0ac23d5b7 100644
--- a/docs/protocol/virtual-operations.md
+++ b/docs/protocol/virtual-operations.md
@@ -351,4 +351,32 @@ All locked funds return to `from`.
---
+## Prediction Markets (HF14)
+
+Emitted by the PM consensus logic — **not** a wall-clock cron. Two sources:
+- A **signed operation's evaluator**, at the instant it applies — `pm_market_accepted` (on accept / self-oracle / auto-accept) and `pm_leverage_liquidate` (on an opposing- or cancel-bet that pushes a leveraged position under threshold).
+- The **deadline processor `process_pm_markets()`**, run each block: it settles markets that have reached an **expiration / deadline / dispute-grace / epoch boundary** (bounded at `pm_processing_cap_per_block`, oldest-deadline-first).
+
+See [Prediction Market Operations](./operations/prediction-markets.md). (IDs 91–93 are the *regular* ops `pm_leverage_open`/`pm_leverage_close`/`pm_leverage_convert`, and IDs 98–99 the *regular* ops `pm_dispute_oracle_respond`/`pm_unban` — see that page.)
+
+| ID | Operation | Trigger |
+|----|-----------|---------|
+| 84 | `pm_batch_settle_operation` | Epoch boundary reached: queued bets executed on the epoch-open snapshot |
+| 85 | `pm_commit_forfeit_operation` | `reveal_deadline` passed unrevealed: penalty → `forfeit_pool`, rest refunded |
+| 86 | `pm_auto_payout_operation` | Dispute grace elapsed (per market): parimutuel settlement + LP principal returned |
+| 87 | `pm_dispute_finalize_operation` | Committee `voting_end_time` reached: tally decides; oracle penalty; re-resolved/upheld |
+| 88 | `pm_dispute_auto_close_operation` | `auto_close_time` reached, oracle never responded: anti-freeze refund, insurance slashed → DAO |
+| 89 | `pm_oracle_missed_penalty_operation` | `result_expiration` passed unresolved: insurance slashed → DAO, all bets refunded |
+| 90 | `pm_lazy_recall_operation` | An idle lazy-pool allocation reached a recall step: one graduated step returned to the pool |
+| 94 | `pm_leverage_liquidate_operation` | Evaluator — mid-market leverage liquidation (opposing-bet `0` / cancel-bet `1` cascade) |
+| 95 | `pm_leverage_resolve_operation` | Settlement — leveraged position force-closed at `cancel_value`: `outcome_index`, `won`, `pool_received`/`bettor_received`, `leverage` |
+| 96 | `pm_market_accepted_operation` | Evaluator — market went live: oracle accepted, self-oracle, or auto-accept; frozen oracle terms + `self_oracle` |
+| 97 | `pm_payout_operation` | Settlement — per active bet: `amount` (stake), `side`/`outcome_index`, `payout` (**0 on a loss**); alongside the per-market `pm_auto_payout` |
+| 100 | `pm_ban_expired_operation` | A temporary oracle/creator ban lapsed at `banned_until`: the cron cleared it (`account`, `oracle`, `creator`). Early manual lifts use the signed `pm_unban` instead |
+| 101 | `pm_market_expired_operation` | `accept_deadline` passed on a pending market: the named oracle never accepted/rejected within `pm_oracle_accept_window_sec` — market voided (`status -1`), seed liquidity refunded (`refunded_liquidity`), creation fee **not** refunded (`oracle`, `creator`, `market_id`, `refunded_liquidity`) |
+
+All PM money movement is strictly zero-sum (no emission); settlement conserves `Σ out == Σ bets + LP principal + forfeit_pool`.
+
+---
+
See also: [Operations Overview](./operations/overview.md), [Awards](./operations/awards.md), [Committee](./operations/committee.md).
diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt
index b4c8d3aa18..2826039421 100644
--- a/libraries/chain/CMakeLists.txt
+++ b/libraries/chain/CMakeLists.txt
@@ -60,7 +60,15 @@ if(BUILD_SHARED_LIBRARIES)
committee_evaluator.cpp
invite_evaluator.cpp
paid_subscription_evaluator.cpp
-
+ pm/lmsr_q96.cpp
+ pm/parimutuel.cpp
+ pm/leverage.cpp
+ pm_evaluator.cpp
+
+ include/graphene/chain/pm/lmsr_q96.hpp
+ include/graphene/chain/pm/parimutuel.hpp
+ include/graphene/chain/pm/leverage.hpp
+ include/graphene/chain/pm_evaluator.hpp
include/graphene/chain/account_object.hpp
include/graphene/chain/block_log.hpp
include/graphene/chain/dlt_block_log.hpp
@@ -90,6 +98,7 @@ if(BUILD_SHARED_LIBRARIES)
include/graphene/chain/validator_objects.hpp
include/graphene/chain/committee_objects.hpp
include/graphene/chain/paid_subscription_objects.hpp
+ include/graphene/chain/pm_objects.hpp
${hardfork_hpp_file}
)
@@ -114,7 +123,15 @@ else()
committee_evaluator.cpp
invite_evaluator.cpp
paid_subscription_evaluator.cpp
-
+ pm/lmsr_q96.cpp
+ pm/parimutuel.cpp
+ pm/leverage.cpp
+ pm_evaluator.cpp
+
+ include/graphene/chain/pm/lmsr_q96.hpp
+ include/graphene/chain/pm/parimutuel.hpp
+ include/graphene/chain/pm/leverage.hpp
+ include/graphene/chain/pm_evaluator.hpp
include/graphene/chain/account_object.hpp
include/graphene/chain/block_log.hpp
include/graphene/chain/dlt_block_log.hpp
@@ -145,6 +162,7 @@ else()
include/graphene/chain/committee_objects.hpp
include/graphene/chain/invite_objects.hpp
include/graphene/chain/paid_subscription_objects.hpp
+ include/graphene/chain/pm_objects.hpp
${hardfork_hpp_file}
)
@@ -152,7 +170,7 @@ endif()
add_dependencies(graphene_chain graphene_protocol graphene_utilities build_hardfork_hpp)
target_link_libraries(graphene_chain graphene_protocol graphene_utilities fc chainbase appbase ${PATCH_MERGE_LIB})
-target_include_directories(graphene_chain PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../../")
+target_include_directories(graphene_chain PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../../")
if(WITH_COVERAGE)
target_compile_options(graphene_chain PRIVATE --coverage)
@@ -161,6 +179,10 @@ endif()
if(MSVC)
set_source_files_properties(database.cpp PROPERTIES COMPILE_FLAGS "/bigobj")
+ # HF14 LMSR is pure integer math, but forbid contraction/fast-math defensively (consensus determinism).
+ set_source_files_properties(pm/lmsr_q96.cpp PROPERTIES COMPILE_FLAGS "/fp:precise")
+else()
+ set_source_files_properties(pm/lmsr_q96.cpp PROPERTIES COMPILE_FLAGS "-fno-fast-math -ffp-contract=off")
endif(MSVC)
install(TARGETS
diff --git a/libraries/chain/chain_properties_evaluators.cpp b/libraries/chain/chain_properties_evaluators.cpp
index a71eeb848c..0e3e130ff5 100644
--- a/libraries/chain/chain_properties_evaluators.cpp
+++ b/libraries/chain/chain_properties_evaluators.cpp
@@ -84,6 +84,11 @@ namespace graphene { namespace chain {
_wprops = p;
}
+ result_type operator()(const chain_properties_pm& p) const {
+ FC_ASSERT( _db.has_hardfork(CHAIN_HARDFORK_14), "chain_properties_pm" );
+ _wprops = p;
+ }
+
template
result_type operator()(Props&& p) const {
_wprops = p;
diff --git a/libraries/chain/database.cpp b/libraries/chain/database.cpp
index f09fc14ba0..a7d2bd738b 100644
--- a/libraries/chain/database.cpp
+++ b/libraries/chain/database.cpp
@@ -20,6 +20,8 @@
#include
#include
#include
+#include
+#include
#include
#include
@@ -3146,6 +3148,32 @@ namespace graphene { namespace chain {
void database::committee_processing() {
const auto &props = get_dynamic_global_properties();
const validator_schedule_object &consensus = get_validator_schedule_object();
+
+ // HF14 governance bridge: VIZ parked in the PM lazy pool keeps its DAO-committee voting
+ // weight — a voter's pool claim (NAV × their shares / total shares) is converted to
+ // vesting-shares with the SAME price as create_vesting and added to their vote weight.
+ // GATED on the PM hardfork: pre-HF14 the pool does not exist and this contributes 0.
+ const bool pm_active = has_hardfork(CHAIN_HARDFORK_14);
+ const auto vsh_price = props.get_vesting_share_price();
+ const pm_lazy_pool_object* lpool = pm_active
+ ? find(pm_lazy_pool_id_type(0)) : nullptr;
+ const int64_t lp_nav = (lpool && lpool->total_shares.value > 0)
+ ? lpool->free_balance.value + lpool->allocated_balance.value + lpool->leverage_fund_used.value : 0;
+ const int64_t lp_total_shares = (lpool ? lpool->total_shares.value : 0);
+ const int64_t lp_nav_shares = (lp_nav > 0)
+ ? (asset(share_type(lp_nav), TOKEN_SYMBOL) * vsh_price).amount.value : 0;
+ auto lazy_vote_weight = [&](const account_name_type& acct) -> int64_t {
+ if (lp_nav <= 0 || lp_total_shares <= 0) return 0;
+ const auto& ldidx = get_index().indices().get();
+ auto d = ldidx.find(acct);
+ if (d == ldidx.end() || d->shares.value <= 0) return 0;
+ int64_t viz = (int64_t)(fc::uint128_t((uint64_t)lp_nav)
+ * fc::uint128_t((uint64_t)d->shares.value)
+ / fc::uint128_t((uint64_t)lp_total_shares)).lo;
+ if (viz <= 0) return 0;
+ return (asset(share_type(viz), TOKEN_SYMBOL) * vsh_price).amount.value;
+ };
+
const auto &idx0 = get_index().indices().get();
auto itr0 = idx0.lower_bound(0);
while (itr0 != idx0.end() &&
@@ -3164,10 +3192,12 @@ namespace graphene { namespace chain {
const auto &cur_vote = *vote_itr;
++vote_itr;
const auto &voter_account = get_account(cur_vote.voter);
- max_rshares+=voter_account.effective_vesting_shares().amount.value;
- actual_rshares+=voter_account.effective_vesting_shares().amount.value*cur_vote.vote_percent/CHAIN_100_PERCENT;
+ int64_t vw = voter_account.effective_vesting_shares().amount.value
+ + lazy_vote_weight(cur_vote.voter); // + lazy-pool stake (HF14)
+ max_rshares+=vw;
+ actual_rshares+=vw*cur_vote.vote_percent/CHAIN_100_PERCENT;
}
- approve_min_shares=props.total_vesting_shares.amount * consensus.median_props.committee_request_approve_min_percent / CHAIN_100_PERCENT;
+ approve_min_shares=(props.total_vesting_shares.amount + share_type(lp_nav_shares)) * consensus.median_props.committee_request_approve_min_percent / CHAIN_100_PERCENT;
if(has_hardfork(CHAIN_HARDFORK_2)){
if(approve_min_shares > max_rshares){
modify(cur_request, [&](committee_request_object &c) {
@@ -3654,7 +3684,7 @@ namespace graphene { namespace chain {
return;
}
- chain_properties_hf13 median_props;
+ chain_properties_pm median_props;
auto median = active.size() / 2;
auto calc_median = [&](auto&& param) {
@@ -3703,6 +3733,54 @@ namespace graphene { namespace chain {
if(has_hardfork(CHAIN_HARDFORK_13)){
calc_median(&chain_properties_hf13::distribution_epoch_length);
}
+ if(has_hardfork(CHAIN_HARDFORK_14)){
+ calc_median(&chain_properties_pm::pm_oracle_registration_fee);
+ calc_median(&chain_properties_pm::pm_min_oracle_insurance);
+ calc_median(&chain_properties_pm::pm_market_creation_fee);
+ calc_median(&chain_properties_pm::pm_min_liquidity);
+ calc_median(&chain_properties_pm::pm_max_outcomes);
+ calc_median(&chain_properties_pm::pm_max_market_duration);
+ calc_median(&chain_properties_pm::pm_max_oracle_fee_percent);
+ calc_median(&chain_properties_pm::pm_oracle_accept_window_sec);
+ calc_median(&chain_properties_pm::pm_listing_min_coverage_percent);
+ calc_median(&chain_properties_pm::pm_betting_min_coverage_percent);
+ calc_median(&chain_properties_pm::pm_default_time_penalty_percent);
+ calc_median(&chain_properties_pm::pm_max_time_penalty);
+ calc_median(&chain_properties_pm::pm_dispute_fee);
+ calc_median(&chain_properties_pm::pm_dispute_grace_sec);
+ calc_median(&chain_properties_pm::pm_oracle_dispute_response_sec);
+ calc_median(&chain_properties_pm::pm_dispute_auto_close_sec);
+ calc_median(&chain_properties_pm::pm_dispute_vote_period_sec);
+ calc_median(&chain_properties_pm::pm_dispute_approve_min_percent);
+ calc_median(&chain_properties_pm::pm_oracle_penalty_percent);
+ calc_median(&chain_properties_pm::pm_no_contest_penalty_percent);
+ calc_median(&chain_properties_pm::pm_dispute_reward_multiplier);
+ calc_median(&chain_properties_pm::pm_batch_epoch_blocks);
+ calc_median(&chain_properties_pm::pm_reveal_window_blocks);
+ calc_median(&chain_properties_pm::pm_commit_no_reveal_penalty_percent);
+ calc_median(&chain_properties_pm::pm_min_batch_bet);
+ calc_median(&chain_properties_pm::pm_commit_reveal_enabled);
+ calc_median(&chain_properties_pm::pm_processing_cap_per_block);
+ calc_median(&chain_properties_pm::pm_lazy_pool_enabled);
+ calc_median(&chain_properties_pm::pm_lazy_alloc_percent);
+ calc_median(&chain_properties_pm::pm_lazy_max_total_alloc_percent);
+ calc_median(&chain_properties_pm::pm_lazy_lock_sec);
+ calc_median(&chain_properties_pm::pm_lazy_recall_step_percent);
+ calc_median(&chain_properties_pm::pm_lazy_emergency_penalty_percent);
+ calc_median(&chain_properties_pm::pm_lazy_min_liquidity_fee_percent);
+ calc_median(&chain_properties_pm::pm_leverage_enabled);
+ calc_median(&chain_properties_pm::pm_leverage_fund_percent);
+ calc_median(&chain_properties_pm::pm_leverage_max_per_position_bp);
+ calc_median(&chain_properties_pm::pm_leverage_pool_profit_percent);
+ calc_median(&chain_properties_pm::pm_leverage_safety_margin_percent);
+ calc_median(&chain_properties_pm::pm_leverage_max_slippage_percent);
+ calc_median(&chain_properties_pm::pm_leverage_min_market_liquidity);
+ calc_median(&chain_properties_pm::pm_leverage_max_position_ratio_percent);
+ calc_median(&chain_properties_pm::pm_leverage_expiration_buffer_sec);
+ calc_median(&chain_properties_pm::pm_leverage_m_factor_percent);
+ calc_median(&chain_properties_pm::pm_leverage_funding_rate_ppm_per_day);
+ calc_median(&chain_properties_pm::pm_conversion_profit_cost_percent);
+ }
modify(wso, [&](validator_schedule_object &_wso) {
_wso.median_props = median_props;
@@ -5008,6 +5086,30 @@ namespace graphene { namespace chain {
_my->_evaluator_registry.register_evaluator();
_my->_evaluator_registry.register_evaluator();
_my->_evaluator_registry.register_evaluator();
+
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
+ _my->_evaluator_registry.register_evaluator();
}
void database::set_custom_operation_interpreter(const std::string &id, std::shared_ptr registry) {
@@ -5057,6 +5159,21 @@ namespace graphene { namespace chain {
add_core_index(*this);
add_core_index(*this);
+ // HF14 Prediction Markets (Onix) — consensus objects, registered as core indexes.
+ add_core_index(*this);
+ add_core_index(*this);
+ add_core_index(*this);
+ add_core_index(*this);
+ add_core_index(*this);
+ add_core_index(*this);
+ add_core_index(*this);
+ add_core_index(*this);
+ add_core_index(*this);
+ add_core_index(*this);
+ add_core_index(*this);
+ add_core_index(*this);
+ add_core_index(*this);
+
_plugin_index_signal();
}
@@ -5671,6 +5788,7 @@ namespace graphene { namespace chain {
committee_processing();
paid_subscribe_processing();
+ process_pm_markets();
process_hardforks();
check_block_post_validation_chain();
@@ -7053,6 +7171,9 @@ namespace graphene { namespace chain {
_hardfork_times[CHAIN_HARDFORK_13] = fc::time_point_sec(CHAIN_HARDFORK_13_TIME);
_hardfork_versions[CHAIN_HARDFORK_13] = CHAIN_HARDFORK_13_VERSION;
+ _hardfork_times[CHAIN_HARDFORK_14] = fc::time_point_sec(CHAIN_HARDFORK_14_TIME);
+ _hardfork_versions[CHAIN_HARDFORK_14] = CHAIN_HARDFORK_14_VERSION;
+
const auto &hardforks = get_hardfork_property_object();
FC_ASSERT(
hardforks.last_hardfork <= CHAIN_NUM_HARDFORKS,
@@ -7969,6 +8090,12 @@ namespace graphene { namespace chain {
// pending_stakeholder_reward on validator_object default to 0 (replay
// initialises them), no extra migration needed.
break;
+ case CHAIN_HARDFORK_14:
+ // Prediction Markets: instantiate the lazy-liquidity pool singleton
+ // (id 0) so pm_lazy_deposit and the allocation/leverage paths can rely
+ // on get. All balances start at zero (empty pool).
+ create([](pm_lazy_pool_object&) {});
+ break;
default:
break;
}
diff --git a/libraries/chain/hardfork.d/0-preamble.hf b/libraries/chain/hardfork.d/0-preamble.hf
index 848514a323..be8af26aac 100644
--- a/libraries/chain/hardfork.d/0-preamble.hf
+++ b/libraries/chain/hardfork.d/0-preamble.hf
@@ -52,4 +52,4 @@ FC_REFLECT((graphene::chain::hardfork_property_object),
CHAINBASE_SET_INDEX_TYPE( graphene::chain::hardfork_property_object, graphene::chain::hardfork_property_index)
#define CHAIN_STARTUP_HARDFORKS 0
-#define CHAIN_NUM_HARDFORKS 13
+#define CHAIN_NUM_HARDFORKS 14
diff --git a/libraries/chain/hardfork.d/14.hf b/libraries/chain/hardfork.d/14.hf
new file mode 100644
index 0000000000..c6fac6d4ea
--- /dev/null
+++ b/libraries/chain/hardfork.d/14.hf
@@ -0,0 +1,14 @@
+// 14 Hardfork — Prediction Markets (Onix): binary CPMM + multi LMSR, oracles,
+// disputes, batch/commit-reveal, lazy liquidity pool. See plan Prediction_Market_HF14.
+#ifndef CHAIN_HARDFORK_14
+#define CHAIN_HARDFORK_14 14
+// NOTE: placeholder activation time — MUST be finalized before release (see plan risk register:
+// pm_processing_cap_per_block must be benchmarked under realistic load first).
+#ifdef BUILD_TESTNET
+// Testnet activates earlier so the full PM feature set can be exercised ahead of mainnet.
+#define CHAIN_HARDFORK_14_TIME 1780000000
+#else
+#define CHAIN_HARDFORK_14_TIME 1782849600
+#endif
+#define CHAIN_HARDFORK_14_VERSION hardfork_version( version(4, 0, 0) )
+#endif
diff --git a/libraries/chain/include/graphene/chain/chain_object_types.hpp b/libraries/chain/include/graphene/chain/chain_object_types.hpp
index a58b48fc44..9b7feee747 100644
--- a/libraries/chain/include/graphene/chain/chain_object_types.hpp
+++ b/libraries/chain/include/graphene/chain/chain_object_types.hpp
@@ -73,7 +73,22 @@ namespace graphene { namespace chain {
paid_subscription_object_type,
paid_subscribe_object_type,
validator_penalty_expire_object_type,
- validator_confirmation_object_type
+ validator_confirmation_object_type,
+
+ // HF14 Prediction Markets (Onix) — APPEND ONLY, never reorder existing ids.
+ pm_oracle_object_type,
+ pm_market_object_type,
+ pm_outcome_object_type,
+ pm_bet_object_type,
+ pm_liquidity_object_type,
+ pm_commit_object_type,
+ pm_dispute_object_type,
+ pm_dispute_vote_object_type,
+ pm_lazy_pool_object_type,
+ pm_lazy_deposit_object_type,
+ pm_lazy_allocation_object_type,
+ pm_leverage_position_object_type,
+ pm_creator_ban_object_type
};
class dynamic_global_property_object;
@@ -110,6 +125,21 @@ namespace graphene { namespace chain {
class validator_penalty_expire_object;
class validator_confirmation_object;
+ // HF14 Prediction Markets
+ class pm_oracle_object;
+ class pm_market_object;
+ class pm_outcome_object;
+ class pm_bet_object;
+ class pm_liquidity_object;
+ class pm_commit_object;
+ class pm_dispute_object;
+ class pm_dispute_vote_object;
+ class pm_lazy_pool_object;
+ class pm_lazy_deposit_object;
+ class pm_lazy_allocation_object;
+ class pm_leverage_position_object;
+ class pm_creator_ban_object;
+
typedef object_id dynamic_global_property_id_type;
typedef object_id account_id_type;
typedef object_id account_authority_id_type;
@@ -143,6 +173,21 @@ namespace graphene { namespace chain {
typedef object_id validator_penalty_expire_object_id_type;
typedef object_id validator_confirmation_object_id_type;
+ // HF14 Prediction Markets
+ typedef object_id pm_oracle_id_type;
+ typedef object_id pm_market_id_type;
+ typedef object_id pm_outcome_id_type;
+ typedef object_id pm_bet_id_type;
+ typedef object_id pm_liquidity_id_type;
+ typedef object_id pm_commit_id_type;
+ typedef object_id pm_dispute_id_type;
+ typedef object_id pm_dispute_vote_id_type;
+ typedef object_id pm_lazy_pool_id_type;
+ typedef object_id pm_lazy_deposit_id_type;
+ typedef object_id pm_lazy_allocation_id_type;
+ typedef object_id pm_leverage_position_id_type;
+ typedef object_id pm_creator_ban_id_type;
+
} } //graphene::chain
namespace fc {
@@ -239,6 +284,19 @@ FC_REFLECT_ENUM(graphene::chain::object_type,
(paid_subscribe_object_type)
(validator_penalty_expire_object_type)
(validator_confirmation_object_type)
+ (pm_oracle_object_type)
+ (pm_market_object_type)
+ (pm_outcome_object_type)
+ (pm_bet_object_type)
+ (pm_liquidity_object_type)
+ (pm_commit_object_type)
+ (pm_dispute_object_type)
+ (pm_dispute_vote_object_type)
+ (pm_lazy_pool_object_type)
+ (pm_lazy_deposit_object_type)
+ (pm_lazy_allocation_object_type)
+ (pm_leverage_position_object_type)
+ (pm_creator_ban_object_type)
)
FC_REFLECT_TYPENAME((graphene::chain::shared_string))
diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp
index 326e2642e5..6ccfca08ba 100644
--- a/libraries/chain/include/graphene/chain/database.hpp
+++ b/libraries/chain/include/graphene/chain/database.hpp
@@ -478,6 +478,7 @@ namespace graphene { namespace chain {
void process_validator_epoch_distribution(); ///< HF13: distribute accumulated delegator TOKEN rewards
void committee_processing();
void paid_subscribe_processing();
+ void process_pm_markets(); ///< HF14: bounded per-block PM cron
void expire_award_shares_processing();
diff --git a/libraries/chain/include/graphene/chain/evaluator.hpp b/libraries/chain/include/graphene/chain/evaluator.hpp
index ac7ad104f3..9deb4c1bb6 100644
--- a/libraries/chain/include/graphene/chain/evaluator.hpp
+++ b/libraries/chain/include/graphene/chain/evaluator.hpp
@@ -11,6 +11,8 @@ namespace graphene {
template
class evaluator {
public:
+ virtual ~evaluator() = default; // polymorphic base: needed for delete via base ptr
+
virtual void apply(const OperationType &op) = 0;
virtual int get_type() const = 0;
diff --git a/libraries/chain/include/graphene/chain/pm/leverage.hpp b/libraries/chain/include/graphene/chain/pm/leverage.hpp
new file mode 100644
index 0000000000..6e24b95e95
--- /dev/null
+++ b/libraries/chain/include/graphene/chain/pm/leverage.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include
+#include
+
+// HF14 Prediction Markets — pure leverage (margin) math for binary CPMM markets,
+// frozen against leverage-risk-off-strategy.md §4. Deterministic integer math only;
+// `k = reserve_a × reserve_b` is 128-bit. Free of database types so it is unit-tested
+// in isolation; the evaluators / atomic-liquidation hook feed live reserves in.
+//
+// Reserve convention matches the chain's pm_place_bet (NOT the PHP prototype):
+// outcome 0 (A): bet adds to reserve_a; tokens = reserve_b − k/(reserve_a+amount)
+// outcome 1 (B): bet adds to reserve_b; tokens = reserve_a − k/(reserve_b+amount)
+//
+// Leverage ships behind the `pm_leverage_enabled` kill-switch and is CPMM-binary only
+// in HF14 (LMSR leverage is future work).
+
+namespace graphene { namespace chain { namespace pm { namespace leverage {
+
+ struct cpmm_fill {
+ int64_t tokens = 0; ///< weight received
+ int64_t new_reserve_a = 0;
+ int64_t new_reserve_b = 0;
+ };
+
+ // §4.1 — place a bet of `amount` on `outcome`; returns tokens + post-bet reserves.
+ cpmm_fill cpmm_buy(int64_t reserve_a, int64_t reserve_b, fc::uint128_t k,
+ int64_t amount, int outcome);
+
+ // §4.2 — exit price: VIZ returned by selling `tokens` of `outcome` back to the curve
+ // at current reserves (floored at 0).
+ int64_t cancel_value(int64_t reserve_a, int64_t reserve_b, fc::uint128_t k,
+ int64_t tokens, int outcome);
+
+ // §4.3 — cancel value of `tokens` on `outcome` AFTER an opposing bet of `m` on the
+ // other side moves the price against the position (floored at 0). Monotonically
+ // decreasing in `m`; the worst case uses m = worst_opposing_bet().
+ int64_t cancel_value_after_opposing(int64_t reserve_a, int64_t reserve_b, fc::uint128_t k,
+ int64_t tokens, int outcome, int64_t m);
+
+ // §4.3 — worst-case opposing bet size: min(reserve_a,reserve_b) × sl% /100 × m_factor% /100.
+ int64_t worst_opposing_bet(int64_t reserve_a, int64_t reserve_b,
+ uint16_t sl_percent, uint16_t m_factor_percent);
+
+ // §4.4 — pool obligation / liquidation threshold = loan × (1 + r_percent/100).
+ int64_t liquidation_threshold(int64_t loan, uint16_t r_percent);
+
+ // §4.6 Constraint 2 (API preview) — max loan L (50-iter binary search over [0, hi])
+ // such that, after placing (collateral+L) on `outcome`, the worst-case cancel value
+ // ≥ liquidation_threshold(L) × (1 + s_percent/100). Reserves are the PRE-bet market
+ // reserves. Returns the loan (0 if none qualifies).
+ int64_t max_leverage_loan(int64_t reserve_a, int64_t reserve_b, fc::uint128_t k,
+ int64_t collateral, int outcome, int64_t hi_loan,
+ uint16_t r_percent, uint16_t s_percent,
+ uint16_t sl_percent, uint16_t m_factor_percent);
+
+}}}} // graphene::chain::pm::leverage
diff --git a/libraries/chain/include/graphene/chain/pm/lmsr_q96.hpp b/libraries/chain/include/graphene/chain/pm/lmsr_q96.hpp
new file mode 100644
index 0000000000..ce4d05be47
--- /dev/null
+++ b/libraries/chain/include/graphene/chain/pm/lmsr_q96.hpp
@@ -0,0 +1,44 @@
+#pragma once
+
+#include
+#include
+#include
+
+// HF14 — Deterministic fixed-point LMSR (Q96). C++ consensus port of the normative
+// PHP/JS reference (.qoder/plans/pm-plan/reference/lmsr_fixed.php + market_math.js) and
+// lmsr-fixed-point-spec.md. MUST reproduce reference/lmsr_fixed_vectors.json bit-for-bit.
+//
+// No / exp / log / pow / sqrt — pure big-integer arithmetic. The translation unit
+// is compiled with -fno-fast-math -ffp-contract=off and CI greps the object for FP symbols.
+//
+// Rounding rules (frozen, spec §3.5):
+// mul_q / shifts : arithmetic right shift = floor toward -inf
+// div_q / term/n : truncation toward zero (C99 integer division)
+// exp range-reduce: round to nearest, ties to even (the single non-truncating step)
+
+namespace graphene { namespace chain { namespace lmsr {
+
+ using q96 = boost::multiprecision::int256_t;
+
+ // Public precisions (unchanged from the reference API).
+ constexpr int64_t LMSR_PRECISION = 1000; // 1 VIZ = 1000 milli-VIZ
+ constexpr int64_t LMSR_PRICE_PRECISION = 1000000; // price x10^6
+
+ // Public API — all int64 milli-VIZ in/out. On domain violation these FC_ASSERT (fail the op).
+ int64_t lmsr_cost(const std::vector& q, int64_t b);
+ std::vector lmsr_prices(const std::vector& q, int64_t b);
+ int64_t lmsr_price(const std::vector& q, int64_t b, int i);
+ int64_t lmsr_buy_cost(const std::vector& q, int64_t b, int i, int64_t delta);
+ int64_t lmsr_sell_return(const std::vector& q, int64_t b, int i, int64_t delta);
+ int64_t lmsr_tokens_for_amount(const std::vector& q, int64_t b, int i, int64_t amount);
+ int64_t lmsr_b_from_liquidity(int64_t liquidity, int n);
+
+ // Exposed for the vector-parity test (primitives + transcendentals).
+ namespace detail {
+ q96 mul_q(const q96& a, const q96& b);
+ q96 div_q(const q96& a, const q96& b);
+ q96 exp_q(const q96& x);
+ q96 ln_q(const q96& x);
+ }
+
+} } } // graphene::chain::lmsr
diff --git a/libraries/chain/include/graphene/chain/pm/parimutuel.hpp b/libraries/chain/include/graphene/chain/pm/parimutuel.hpp
new file mode 100644
index 0000000000..c0fa350541
--- /dev/null
+++ b/libraries/chain/include/graphene/chain/pm/parimutuel.hpp
@@ -0,0 +1,58 @@
+#pragma once
+
+#include
+#include
+
+// HF14 Prediction Markets — pure parimutuel settlement math (spec
+// parimutuel-settlement.md §2). Kept free of database/chainbase types so it can
+// be unit-tested in isolation; settle_market() in pm_evaluator.cpp is a thin
+// wrapper that feeds chain state in and applies the result via adjust_balance.
+//
+// Strictly ZERO-SUM (no token emission):
+// Σ winner_payout + oracle_take + creator_take + lp_bonus
+// == Σ winner.amount + losers_sum + forfeit_pool
+// LP principal is returned separately by the caller and is unaffected here.
+
+namespace graphene { namespace chain { namespace pm {
+
+ // One winning position fed into settlement (curve weight is the claim device).
+ struct winner_in {
+ int64_t amount = 0; ///< staked principal (mVIZ)
+ int64_t weight = 0; ///< curve weight (CPMM/LMSR tokens)
+ uint32_t time_penalty = 0; ///< 1e6-scaled late penalty applied to PROFIT only
+ };
+
+ struct settle_params {
+ int64_t losers_sum = 0; ///< Σ amount of losing bets
+ int64_t forfeit_pool = 0; ///< extra pot folded into winners_pool
+ int64_t oracle_fixed_fee = 0; ///< funded from the pool, capped ≥0
+ uint16_t oracle_fee_percent = 0;
+ uint16_t creator_fee_percent = 0;
+ uint16_t liquidity_fee_percent = 0;
+ };
+
+ struct settle_result {
+ std::vector winner_payout; ///< amount + profit − penalty, parallel to winners
+ int64_t oracle_take = 0; ///< oracle_fee + oracle_fixed_paid
+ int64_t creator_take = 0;
+ int64_t lp_bonus = 0; ///< liq_fee + Σpenalty + rounding dust + undistributed pool
+ };
+
+ /// Compute the parimutuel payout split. Winners are paid by weight out of the
+ /// losers' stakes; the remainder/penalties accrue to the LP bonus.
+ settle_result compute_settlement(const settle_params& p,
+ const std::vector& winners);
+
+ // One LP position for the time-weighted fee split.
+ struct lp_in {
+ int64_t principal = 0; ///< staked liquidity (mVIZ)
+ int64_t seconds_active = 0; ///< settle_time − deposit_time (clamped ≥0)
+ };
+
+ /// Split `bonus` across LPs weighted by principal × time-in-market (minute
+ /// granularity, +1 floor so simultaneous deposits fall back to principal-pro-rata,
+ /// and the product stays within 128 bits). Returns each LP's bonus share (parallel
+ /// to `lps`); the last LP absorbs the rounding remainder so Σ shares == bonus.
+ std::vector distribute_lp(const std::vector& lps, int64_t bonus);
+
+}}} // graphene::chain::pm
diff --git a/libraries/chain/include/graphene/chain/pm_evaluator.hpp b/libraries/chain/include/graphene/chain/pm_evaluator.hpp
new file mode 100644
index 0000000000..82e1b6ac64
--- /dev/null
+++ b/libraries/chain/include/graphene/chain/pm_evaluator.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include
+
+#define DEFINE_PM_EVALUATOR(X) \
+class pm_##X##_evaluator : public graphene::chain::evaluator_impl { \
+public: \
+ typedef graphene::protocol::pm_##X##_operation operation_type; \
+ pm_##X##_evaluator(database& db) \
+ : graphene::chain::evaluator_impl(db) {} \
+ void do_apply(const operation_type& o); \
+};
+
+namespace graphene { namespace chain {
+
+ DEFINE_PM_EVALUATOR(oracle_register)
+ DEFINE_PM_EVALUATOR(oracle_update)
+ DEFINE_PM_EVALUATOR(create_market)
+ DEFINE_PM_EVALUATOR(oracle_accept_market)
+ DEFINE_PM_EVALUATOR(place_bet)
+ DEFINE_PM_EVALUATOR(commit_bet)
+ DEFINE_PM_EVALUATOR(reveal_bet)
+ DEFINE_PM_EVALUATOR(cancel_bet)
+ DEFINE_PM_EVALUATOR(add_liquidity)
+ DEFINE_PM_EVALUATOR(withdraw_liquidity)
+ DEFINE_PM_EVALUATOR(resolve_market)
+ DEFINE_PM_EVALUATOR(no_contest)
+ DEFINE_PM_EVALUATOR(dispute_create)
+ DEFINE_PM_EVALUATOR(dispute_vote)
+ DEFINE_PM_EVALUATOR(dispute_resolve)
+ DEFINE_PM_EVALUATOR(transfer_position)
+ DEFINE_PM_EVALUATOR(lazy_deposit)
+ DEFINE_PM_EVALUATOR(lazy_withdraw)
+ DEFINE_PM_EVALUATOR(leverage_open)
+ DEFINE_PM_EVALUATOR(leverage_close)
+ DEFINE_PM_EVALUATOR(leverage_convert)
+ DEFINE_PM_EVALUATOR(dispute_oracle_respond)
+ DEFINE_PM_EVALUATOR(unban)
+
+}} // graphene::chain
diff --git a/libraries/chain/include/graphene/chain/pm_objects.hpp b/libraries/chain/include/graphene/chain/pm_objects.hpp
new file mode 100644
index 0000000000..35e59e92c4
--- /dev/null
+++ b/libraries/chain/include/graphene/chain/pm_objects.hpp
@@ -0,0 +1,753 @@
+#pragma once
+
+#include
+#include
+
+#include
+#include
+
+// HF14 Prediction Markets (Onix) — consensus ChainBase objects. All financial sums are
+// share_type. Variable-length strings are shared_string capped at MAX_PM_* (enforced on
+// insert in the evaluators — the cap is consensus-mechanical, see config.hpp). Time indexes
+// are (status, time, id) so the bounded per-block cron scans oldest-first deterministically.
+
+namespace graphene { namespace chain {
+
+ using protocol::string_less;
+
+ // ───────────────────────────── 1.1 pm_oracle_object ─────────────────────────────
+ class pm_oracle_object : public object {
+ public:
+ pm_oracle_object() = delete;
+ template
+ pm_oracle_object(Constructor&& c, allocator a) : rules_url(a) { c(*this); }
+
+ id_type id;
+ account_name_type owner;
+ share_type insurance;
+ uint16_t fee_percent = 0;
+ share_type fixed_fee;
+ shared_string rules_url;
+ time_point_sec active_since;
+ time_point_sec last_active_time;
+ time_point_sec banned_until; ///< 0 not banned; time_point_sec::maximum() = permanent
+
+ // 14 reputation counters (score computed on read in the API plugin).
+ uint32_t markets_accepted = 0;
+ uint32_t markets_resolved = 0;
+ uint32_t no_contest_count = 0;
+ uint32_t missed_count = 0;
+ uint32_t disputes_received = 0;
+ uint32_t disputes_lost = 0;
+ uint32_t disputes_won = 0;
+ uint32_t disputes_auto_closed = 0;
+ uint32_t dispute_responses_missed = 0;
+ share_type total_volume_resolved;
+ share_type total_insurance_slashed;
+ uint32_t avg_resolution_time = 0;
+ uint32_t penalty_stamps = 0;
+ uint32_t bans_received = 0;
+ time_point_sec last_penalty_stamp_time; ///< time of most recent stamp (10-day decay)
+ // Auto-accept policy (anti-collusion): if `auto_accept`, a new market naming this oracle is
+ // accepted at creation ONLY when it matches this policy — so a creator cannot slip in a
+ // sham dispute resolver. `auto_accept_creator` empty = any creator. `auto_accept_resolver`
+ // empty = only committee-mode markets (dispute_mode 0); set = only account-mode markets
+ // whose dispute_resolver equals it. The oracle's profile fee terms must also be within the
+ // creator's offered ceiling, else the market stays pending for manual review.
+ account_name_type auto_accept_creator;
+ account_name_type auto_accept_resolver;
+ bool auto_accept = false;
+ account_name_type banned_by; ///< resolver that set banned_until (empty if unset); may pm_unban
+ };
+
+ struct by_owner;
+ struct by_status;
+ struct by_insurance;
+ typedef multi_index_container<
+ pm_oracle_object,
+ indexed_by<
+ ordered_unique, member>,
+ ordered_unique, member, string_less>,
+ ordered_non_unique, member>,
+ ordered_non_unique, member>
+ >,
+ allocator
+ > pm_oracle_index;
+
+ // ───────────────────────────── 1.2 pm_market_object ─────────────────────────────
+ class pm_market_object : public object {
+ public:
+ pm_market_object() = delete;
+ template
+ pm_market_object(Constructor&& c, allocator a)
+ : url(a), decision_url(a), decision_reason(a) { c(*this); }
+
+ id_type id;
+ account_name_type creator;
+ account_name_type oracle;
+ uint8_t market_type = 0; ///< 0 binary (CPMM), 1 multi (LMSR)
+ uint8_t outcome_count = 2;
+ shared_string url;
+ // NB: the free-form client `metadata` is intentionally NOT stored in consensus state.
+ // It is opaque to consensus (never read here), so the prediction_market_api plugin
+ // ingests it off-chain from pm_create_market_operation into its own prunable index.
+ // This keeps chainbase lean and lets each node prune it (see --pmm-ttl-days).
+ int8_t status = 0; ///< -1 deleted, 0 waiting, 1 active, 2 closed, 3 resolved
+ uint8_t payout_status = 0; ///< 0 none, 1 pending, 2 paid, 3 disputed
+ time_point_sec created_time;
+ time_point_sec accept_deadline; ///< pending markets only: created_time + median
+ ///< pm_oracle_accept_window_sec. The cron voids the
+ ///< market (refund seed) if the oracle hasn't acted by
+ ///< then. 0 for markets active at creation (never scanned).
+ time_point_sec betting_expiration;
+ time_point_sec result_expiration;
+ time_point_sec finalized_time; ///< 0 while live; set to head-block time the moment the
+ ///< market becomes terminal (resolved+paid, void/no-contest,
+ ///< oracle-rejected, or accept-window expired). The cron GCs
+ ///< the whole market cluster pm_closed_market_retention_sec
+ ///< after this — deterministic, so every node prunes alike.
+ int16_t resolved_outcome = -1;
+
+ // Binary CPMM
+ share_type reserve_a;
+ share_type reserve_b;
+ fc::uint128_t k = 0; ///< reserve_a * reserve_b
+ share_type a_bets_sum;
+ share_type b_bets_sum;
+
+ // Multi LMSR
+ share_type lmsr_b;
+ share_type lmsr_subsidy;
+
+ // Common
+ share_type bets_sum;
+ share_type liquidity_sum;
+ uint16_t oracle_fee_percent = 0;
+ uint16_t creator_fee_percent = 0;
+ uint16_t liquidity_fee_percent = 0;
+ share_type oracle_fixed_fee;
+ share_type liquidity_fee_earned;
+ share_type forfeit_pool;
+ uint8_t time_penalty_type = 0;
+ uint32_t time_penalty_value = 0;
+ uint8_t penalty_curve_type = 0;
+ bool allow_early_resolution = false;
+ bool allow_cancellation = false;
+ bool allow_batch = false;
+ bool allow_instant_bet = true;
+ uint8_t endogeneity_tier = 2;
+ uint32_t current_epoch = 0;
+ uint8_t dispute_mode = 0; ///< 0 committee / 1 account
+ account_name_type dispute_resolver;
+ int16_t dispute_penalty_percent = 0; ///< −10000..+10000: oracle penalty on a
+ ///< successful dispute (>0 slash % of
+ ///< insurance ×consensus; <0 good-faith
+ ///< oracle bonus from fee; 0 none)
+ // Oracle's resolution statement, stored on-chain (like an oracle/validator's rules_url):
+ // set by pm_resolve_market (both) or pm_no_contest (reason → decision_reason). Empty until
+ // resolved. Readable directly via get_market — no history scan needed.
+ shared_string decision_url; ///< evidence link the oracle cited when resolving
+ shared_string decision_reason; ///< oracle's free-text justification (or NO-CONTEST reason)
+ };
+
+ struct by_creator;
+ struct by_oracle;
+ struct by_accept_deadline;
+ struct by_betting_expiration;
+ struct by_result_expiration;
+ struct by_payout_status;
+ struct by_finalized;
+ typedef multi_index_container<
+ pm_market_object,
+ indexed_by<
+ ordered_unique, member>,
+ ordered_non_unique, member, string_less>,
+ ordered_non_unique, member, string_less>,
+ ordered_non_unique, member>,
+ // Pending-acceptance sweep: (status, accept_deadline, id). The cron lower_bounds at
+ // status 0 and stops at the first accept_deadline > now, so voiding never-accepted
+ // markets stays bounded. Accepted (1)/rejected (-1) markets sit in other status buckets.
+ ordered_unique,
+ composite_key,
+ member,
+ member
+ >,
+ composite_key_compare, std::less, std::less>
+ >,
+ ordered_unique,
+ composite_key,
+ member,
+ member