Early-exit deferred claim (F1 / #300)
Status: design locked (owner 2026-08-08), implementation in progress on branch pm.
Problem
The market is a hybrid: a CPMM (binary) / LMSR (multi) curve for entry and early exit, and pari-mutuel settlement for positions held to resolution. Any round-trip through the curve (buy then sell before settlement) realizes a trading P&L against the curve depth — the LPs — exactly like Uniswap impermanent loss. But the design promises LPs principal protection (fee-only, no IL). Those two are in tension.
Two code paths exit against the curve on a binary market:
- Leverage (
liquidate_position/pm_leverage_close): always; force-closed at settlement; amplified by the pool loan. - Regular bet cancel (
cancel_bet, F2 curve-priced refund): during the betting window.
Both route residual = stake − curve_refund to forfeit_pool (signed). When an early exit is profitable (curve_refund > stake), forfeit_pool goes negative. At settlement winners_pool = losers_sum − fees + forfeit_pool; if leverage/early-exit profit outran the losing stakes, winners_pool < 0, is floored to 0, and the shortfall (uncovered, F1) is charged to LP principal — or minted when LP principal is exhausted. Reachable: proven with a gate-respecting CPMM simulation (one-sided pump, uncovered = 7316); Babin's replay corpus hit it in 1163/1988 pairs.
Root cause: curve-priced exit pays a bonding-curve value that is not bounded by the losing pool, while settlement pays pari-mutuel. The gap lands on the LP.
Model (locked)
Early exits no longer extract curve value from LPs. Instead an exit records an outcome-contingent deferred claim, funded at settlement from a bounded slice of the losing pool.
Recorded on exit
{ position_id, kind (bet|leverage), chosen_outcome, claim_amount, exit_time }.
Regular bet cancel
- Principal returned immediately, unconditionally:
refund = min(curve_refund, stake)(own money, not borrowed). A cancel can cut losses or break even but never realizes curve-profit at cancel time. - Profit tail
max(curve_refund − stake, 0)→ deferred claim on the chosen outcome. - Depth-normalized pricing (audit #1-C): the capped/tail split is re-priced at the bet's entry curve depth, not the current one.
pm_bet_object.entry_liquidityrecordsliquidity_sumat the moment the CPMM bet hit the curve (instantplace_betand batch fill). On cancel both reserves are scaled byentry_liquidity / liquidity_sum(mirror-of-buy on the scaled reserves). Because bets keepkinvariant while liquidity ops scalekbyf²andliquidity_sumbyf,sqrt(k_entry / k_now) == L_entry / L_nowexactly → deterministic, sqrt-free (no integer sqrt). The result is clamped to the realcurve_refundso normalization may only reduce the payout, never raise it. This kills the self-liquidity inflation vector (bet → ownadd_liquidityinflates depth → largercurve_refund→ cancel mints a bigger tail → withdraw returns the liquidity whole = cash-neutral guaranteed profit) without opening a shrink-side one. Falls back to legacy pricing whenentry_liquidityis absent (bets predating the field).
Leverage close / liquidate
- Collateral is NOT separately returned — it is first-loss margin for the pool. The pool recovers its obligation (
loan·(1+R) + funding) fromcvfirst; ifcv < obligation, the collateral covers the gap. - Residual
max(cv − obligation, 0)→ deferred claim on the chosen outcome. - Leverage is therefore a leveraged directional bet, not a volatility harvest: you profit only if your outcome wins and the bucket has room; a wrong outcome loses the collateral.
- NB two distinct predicates now: solvency (
cv ≥ obligation, governs loan recovery) vs outcome-win (governs the right to a claim). A position can be solvent yet on the losing outcome → pool made whole, claim = 0.
Settlement
bucket = pm_early_exit_reward_cap_percent × losers_sum / 10000(default 33%).- Collect deferred claims on the winning outcome only (losing-outcome claims → 0).
- Pay them FIFO by
exit_time(first out, first paid) until the bucket is drained; no per-position cap (owner 2026-08-08: FIFO ordering + total bucket is the bound). A claim that the remaining bucket cannot fully fund is paid partially; the rest is unpaid (haircut). - Any unused bucket returns to the winners' pool — held winning bets share it pari-mutuel.
Guarantees
paid_claims ≤ bucket = cap · losers_sum
winners_pool = losers_sum − fees − paid_claims + honest_forfeits
≥ (1 − cap) · losers_sum − fees ≥ 0 (cap < 100%)uncoveredis impossible by construction; no mint; LP principal never touched; the lazy pool bears no leverage IL.- A losing outcome never profits (owner requirement).
- Held winners receive
≥ (1 − cap)of the losing pool plus any unused bucket. - Early exit is a bounded, contingent discount (≤ cap, FIFO) vs holding to resolution (full pari-mutuel share) → no arbitrage against holding; a deliberate liquidity discount.
Chain parameter
pm_early_exit_reward_cap_percent (uint16, bp, default 3300 = 33% of losers_sum). Median-voted validator param; validate() bounds ≤ 10000. Added to chain_properties_pm + FC_REFLECT + calc_median (DONE, single-TU verified).
Implementation touchpoints (node)
- [x] chain param
pm_early_exit_reward_cap_percent(struct/validate/reflect/median). - [ ] object
pm_deferred_claim_object(+ index by market, by exit_time) — space 30, append at end ofobject_typeenum (snapshot-safe, likepm_lazy_withdraw_request). - [ ]
cancel_bet: returnmin(curve_refund, stake), record profit-tail claim; stop routing a negative residual toforfeit_pool. - [ ]
liquidate_position/pm_leverage_close: pool takes obligation, recordcv − obligationclaim, tag chosen outcome; drop immediatebettor_received; stop negativeforfeit_pool. - [ ] settlement (
settle_market): after force-close, computebucket, pay winning-outcome claims FIFO by exit_time, remainder → winners' pool; remove theuncovered/F1settle_liquiditycharge path (LP no longer absorbs it). - [ ] snapshot: include
pm_deferred_claim_objectin allowlist (+ import handler). - [x] virtual op
pm_early_exit_claim_paid(account, market, kind, outcome,claimed,paid) — appended at the end of theoperationvariant (op-ids stay stable), FC_REFLECT'd, emitted in the settlement distribution loop next to theadjust_balance, and routed to the early-exiter's account history (account_historyimpacted-accounts visitor).claimedvspaidexposes any bucket-exhaustion haircut.adjust_balancealone leaves no history trace — this closes that gap. - [x] read API:
get_deferred_claims(market, [from=0], [limit=100])— FIFO exit order viaby_claim_market; empty on a settled market (claims consumed). Plugin-only (clients call via rawApi/JSON-RPC), matchingget_lazy_withdraw_requests; no wallet wiring.
Client / lib / docs follow-ups
- viz-js-lib / viz-php-lib / viz-python-lib: new chain param in v5 chain_properties_pm (serialization lock-step, byte-verify), any new read method / vop.
- Forecaster: notices + operation descriptions (leverage = directional, early exit = bounded discount), show pending deferred claim on positions.
- WebVIZWallet: same operation-description updates if surfaced.
- Scientific article:
early-exit choicewith the math (regular vs leverage-from-lazy-pool, validator-set reward cap).
Rejected alternatives (why)
- Smear
uncoveredacross all LP / mint (status quo) — breaks LP principal promise. - Localize only to lazy pool — pool can be exhausted; still an approximation; cancels leak.
- Cap the win at exit — you don't know
losers_sumat exit time; deferral removes that. - Full-AMM settlement — abandons the pari-mutuel thesis of VIZ. The deferred outcome-contingent claim is the only option giving a hard LP guarantee while staying pari-mutuel.