viz-js-lib 0.12.7 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1138 @@
1
+ # Prediction Markets (Onix, HF14) — Library Integration Specification
2
+
3
+ > **Audience:** implementers of the JS / PHP / Python client libraries.
4
+ > **Goal:** everything a library needs to (a) build & sign prediction-market transactions and
5
+ > (b) read prediction-market state over JSON-RPC — operation names, plugin names, API method
6
+ > names, parameters, and the full field layout of every operation and returned object.
7
+ >
8
+ > This document is generated from the node source and is authoritative for field **names,
9
+ > order, and types**. It intentionally does **not** re-explain the economic mechanics
10
+ > (CPMM/LMSR/parimutuel, leverage math, dispute game theory) — for that see
11
+ > `docs/prediction-markets/specification.md`. Here we describe *the wire contract only*.
12
+
13
+ ---
14
+
15
+ ## 1. Conventions
16
+
17
+ ### 1.1 Data types
18
+
19
+ | Spec type | Wire (JSON) representation | Notes |
20
+ |---|---|---|
21
+ | `account_name_type` | string | 3–25 chars, VIZ account name |
22
+ | `asset` | string `"10.000 VIZ"` | VIZ, **3 decimals**, `TOKEN_SYMBOL = VIZ`. Serialized as `"<amount>.<3 decimals> VIZ"` |
23
+ | `share_type` | integer (int64) | raw amount, **already scaled by 1000** (i.e. `10.000 VIZ` = `10000`). Used inside read-only objects |
24
+ | `pm_object_id_type` | integer (int64) | plain numeric id of a pm object (market/bet/liquidity/…). NOT the `space.type.instance` string form |
25
+ | `<obj>_id_type` (in returned objects) | string `"<space>.<type>.<instance>"` OR integer, depending on serializer | ChainBase object id. In practice compare/pass the numeric instance. See §1.4 |
26
+ | `time_point_sec` | string ISO-8601 `"2026-07-02T12:00:00"` (UTC, no `Z`) | seconds precision |
27
+ | `fc::sha256` | hex string (64 chars) | |
28
+ | `fc::uint128_t` | integer or string | may exceed JS `Number.MAX_SAFE_INTEGER` — use big-int/string |
29
+ | `uint16_t` percent (bp) | integer | **basis points**, `10000 = 100.00%` unless noted otherwise |
30
+ | `extensions_type` | array `[]` | always present, always empty for HF14; include as `[]` when signing |
31
+
32
+ **Percent conventions differ by field — read the per-field notes.** Three scales appear:
33
+ - **bp (basis points):** `10000 = 100%`. Most fee/percent fields.
34
+ - **`time_penalty` on a bet:** `1e6 = 100%` (`pm_max_time_penalty = 1000000`).
35
+ - **`pm_leverage_*_percent` governance knobs:** plain percent (`10 = 10%`), see §9.
36
+
37
+ ### 1.2 Operation serialization
38
+
39
+ Operations are members of a single `static_variant` (tagged union). Two encodings:
40
+
41
+ - **JSON (what libraries build):** a 2-element array `["<op_name>", { ...fields }]`, where
42
+ `<op_name>` is the struct name **with the `_operation` suffix removed** — e.g.
43
+ `pm_place_bet_operation` → `"pm_place_bet"`.
44
+ - **Binary (for signing):** the variant **tag is a varint = the op-id** in §2/§3, followed by
45
+ the fields in the exact declared order. Field order in every table below **is** the binary
46
+ order. `extensions` is the last field of user ops (empty vector → single `0x00` byte).
47
+
48
+ A transaction's `operations` array holds these 2-element arrays. Signing (ref-block, expiration,
49
+ chain-id, canonical secp256k1) is identical to every other VIZ operation — unchanged by HF14.
50
+
51
+ ### 1.3 JSON-RPC calling convention
52
+
53
+ All reads go through the **`prediction_market_api`** plugin. Call shape (same as every VIZ API
54
+ plugin):
55
+
56
+ ```json
57
+ { "jsonrpc":"2.0", "id":1, "method":"call",
58
+ "params":["prediction_market_api", "<method>", [ <positional args...> ]] }
59
+ ```
60
+
61
+ Positional args are order-sensitive; trailing optional args may be omitted. Pagination is
62
+ uniformly `(…key…, from, limit)` with `limit ≤ 1000`.
63
+
64
+ ### 1.4 Object ids
65
+
66
+ Returned objects carry an `id`. The numeric **instance** part is what you pass back into ops as
67
+ `market_id`, `bet_id`, `liquidity_id`, `position_id`, `commit_id`. When reading you may receive
68
+ either the string `"space.type.instance"` or a bare integer depending on the serializer build;
69
+ always be able to parse the trailing integer. Ops take the bare integer (`pm_object_id_type`).
70
+
71
+ ---
72
+
73
+ ## 2. Plugins & node configuration
74
+
75
+ | Plugin (config `plugin =` name) | Role |
76
+ |---|---|
77
+ | `chain` | consensus DB (always on) |
78
+ | `json_rpc` | RPC transport (always on) |
79
+ | `prediction_market_api` | **all PM read methods** (§5). Requires `chain` + `json_rpc` |
80
+
81
+ `prediction_market_api` config option (`config.ini`):
82
+
83
+ | Option | Default | Meaning |
84
+ |---|---|---|
85
+ | `pmm-ttl-days` | `7` | Days to keep a market's non-consensus metadata + kline history after its dispute window closes, then pruned |
86
+
87
+ There is **no separate operations plugin** — PM operations are part of the core protocol
88
+ (`operation` variant); any node accepting transactions accepts them once HF14 is active.
89
+
90
+ ---
91
+
92
+ ## 3. User (signed) operations
93
+
94
+ 35 total PM operations exist; **23 are user operations** (submitted in transactions), 12 are
95
+ virtual (§4). Op-id = index in the global `operation` variant (fixed, append-only).
96
+
97
+ Legend for **Auth**: which authority must sign — `active` (spending auth) or `regular` (posting-level auth).
98
+
99
+ | # | op-id | JSON name | Auth | Purpose |
100
+ |---|---|---|---|---|
101
+ | 1 | 66 | `pm_oracle_register` | active(`owner`) | Register an oracle with a bonded insurance deposit |
102
+ | 2 | 67 | `pm_oracle_update` | active(`owner`) | Change insurance / fees / rules / auto-accept policy |
103
+ | 3 | 68 | `pm_create_market` | active(`creator`) | Create a market; creator seeds first liquidity |
104
+ | 4 | 69 | `pm_oracle_accept_market` | active(`oracle`) | Oracle accepts/rejects a pending market & quotes terms |
105
+ | 5 | 70 | `pm_place_bet` | active(`account`) | Place a bet (instant or batch) |
106
+ | 6 | 71 | `pm_commit_bet` | active(`account`) | Commit-reveal phase 1: hidden commitment |
107
+ | 7 | 72 | `pm_reveal_bet` | active(`account`) | Commit-reveal phase 2: reveal & enqueue |
108
+ | 8 | 73 | `pm_cancel_bet` | active(`account`) | Cancel an open/queued bet (needs `allow_cancellation`) |
109
+ | 9 | 74 | `pm_add_liquidity` | active(`provider`) | Add liquidity to a market |
110
+ | 10 | 75 | `pm_withdraw_liquidity` | active(`provider`) | Withdraw liquidity |
111
+ | 11 | 76 | `pm_resolve_market` | active(`oracle`) | Oracle resolves to a winning outcome |
112
+ | 12 | 77 | `pm_no_contest` | active(`oracle`) | Oracle voids the market (refund all) |
113
+ | 13 | 78 | `pm_dispute_create` | active(`disputer`) | File a dispute (escrows `pm_dispute_fee`) |
114
+ | 14 | 79 | `pm_dispute_vote` | **regular**(`voter`) | Committee-mode dispute vote |
115
+ | 15 | 80 | `pm_dispute_resolve` | active(`resolver`) | Account-mode dispute verdict by configured resolver |
116
+ | 16 | 81 | `pm_transfer_position` | active(`from`) | Transfer bet weight to another account |
117
+ | 17 | 82 | `pm_lazy_deposit` | active(`account`) | Deposit into the lazy liquidity pool |
118
+ | 18 | 83 | `pm_lazy_withdraw` | active(`account`) | Withdraw from the lazy pool |
119
+ | 19 | 91 | `pm_leverage_open` | active(`account`) | Open a leveraged CPMM position |
120
+ | 20 | 92 | `pm_leverage_close` | active(`account`) | Voluntarily close a leveraged position |
121
+ | 21 | 93 | `pm_leverage_convert` | active(`account`) | Convert a leveraged position to a normal bet |
122
+ | 22 | 98 | `pm_dispute_oracle_respond` | active(`oracle`) | Oracle posts a public rebuttal on an open dispute |
123
+ | 23 | 99 | `pm_unban` | active(`resolver`) | Lift an oracle/creator ban set by that resolver |
124
+
125
+ > Op-ids 84–90 and 94–97 are the **virtual** ops interleaved in the variant (§4). The user-op
126
+ > op-ids above are therefore not fully contiguous. Always verify the numeric tag against a live
127
+ > node if you hand-roll binary serialization; JSON name-based serialization is the safe path.
128
+
129
+ Below, each op lists its fields **in declared (binary) order**. All ops end with
130
+ `extensions: []` (omitted from the tables). Percent fields are bp unless noted.
131
+
132
+ ### 3.1 `pm_oracle_register`
133
+ | Field | Type | Description |
134
+ |---|---|---|
135
+ | `owner` | account | Oracle account; also the required signer |
136
+ | `insurance` | asset | VIZ bond locked from `owner`; must be `≥ pm_min_oracle_insurance` |
137
+ | `fee_percent` | uint16 (bp) | Oracle % of losers' pool; `≤ pm_max_oracle_fee_percent` |
138
+ | `fixed_fee` | asset | Per-market fixed fee (VIZ, `≥ 0`) |
139
+ | `rules_url` | string | Oracle rules/profile URL; `≤ 256` chars |
140
+ | `auto_accept_creator` | account | Auto-accept only markets from this creator; empty = any |
141
+ | `auto_accept_resolver` | account | Empty = auto-accept committee-mode markets only; set = only account-mode markets whose `dispute_resolver` equals this |
142
+ | `auto_accept` | bool | Enable the anti-collusion auto-accept policy above |
143
+
144
+ ### 3.2 `pm_oracle_update`
145
+ All change fields are **optional** — omit to leave unchanged.
146
+ | Field | Type | Description |
147
+ |---|---|---|
148
+ | `owner` | account | Oracle account (signer) |
149
+ | `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` |
150
+ | `fee_percent` | optional uint16 (bp) | New oracle fee % |
151
+ | `fixed_fee` | optional asset | New per-market fixed fee |
152
+ | `rules_url` | optional string | New rules URL |
153
+ | `auto_accept_creator` | optional account | Update auto-accept policy |
154
+ | `auto_accept_resolver` | optional account | Update auto-accept policy |
155
+ | `auto_accept` | optional bool | Enable/disable auto-accept |
156
+
157
+ ### 3.3 `pm_create_market`
158
+ Oracle terms here are the creator's **offer ceiling** (max the maker will pay). The oracle locks
159
+ its actual quote (`≤` these) at accept; a self-oracle freezes them as-is at creation.
160
+ | Field | Type | Description |
161
+ |---|---|---|
162
+ | `creator` | account | Market creator (signer); becomes first LP |
163
+ | `oracle` | account | Registered oracle, or `creator` for a self-oracle |
164
+ | `market_type` | uint8 | `0` binary (CPMM), `1` multi (LMSR) |
165
+ | `outcomes` | string[] | Outcome labels. Size **2** for binary; `3..pm_max_outcomes` for multi. Each label `≤ 64` chars |
166
+ | `url` | string | Resolution criteria/title; `≤ 256` chars |
167
+ | `oracle_fee_percent` | uint16 (bp) | Offered max oracle % of losers' pool |
168
+ | `oracle_fixed_fee` | asset | Offered max oracle fixed fee (VIZ, `≥ 0`) |
169
+ | `creator_fee_percent` | uint16 (bp) | Creator's cut of losers' pool |
170
+ | `liquidity_fee_percent` | uint16 (bp) | LP cut of losers' pool |
171
+ | `liquidity` | asset | VIZ seed liquidity; `≥ pm_min_liquidity` |
172
+ | `lmsr_b` | share_type | Multi only: client-computed LMSR `b`. Node checks `floor(liquidity / ln_q(N)) == b`. `0` for binary |
173
+ | `betting_expiration` | time_point_sec | Betting closes at this time |
174
+ | `result_expiration` | time_point_sec | `> betting_expiration`; `≤ now + pm_max_market_duration` |
175
+ | `time_penalty_type` | uint8 | Time-decay penalty model selector |
176
+ | `time_penalty_value` | uint32 | Penalty parameter (see market mechanics) |
177
+ | `penalty_curve_type` | uint8 | Penalty curve selector |
178
+ | `allow_early_resolution` | bool | Oracle may resolve before `betting_expiration` |
179
+ | `allow_cancellation` | bool | Bettors may cancel open bets |
180
+ | `allow_batch` | bool | Allow batch (commit-reveal / queued) betting |
181
+ | `allow_instant_bet` | bool | Allow instant bets. **Multi forces `true`** (no LMSR batch yet) |
182
+ | `endogeneity_tier` | uint8 | `1` econ-data / `2` sports / `3` political |
183
+ | `dispute_mode` | uint8 | `0` committee / `1` account |
184
+ | `dispute_resolver` | account | Required & must exist iff `dispute_mode==1`; must NOT equal `oracle` or `creator` |
185
+ | `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 |
186
+ | `metadata` | string | Free-form client JSON (no length cap). Consensus-opaque; parsed off-chain by the meta plugin (see §6) |
187
+
188
+ ### 3.4 `pm_oracle_accept_market`
189
+ Quote fields are ignored on reject and for self-oracle markets.
190
+ | Field | Type | Description |
191
+ |---|---|---|
192
+ | `oracle` | account | Oracle (signer) |
193
+ | `market_id` | int64 | Target market |
194
+ | `accept` | bool | `true` accept, `false` reject |
195
+ | `oracle_fee_percent` | uint16 (bp) | Oracle's quoted %; `≤` market offer & the median cap |
196
+ | `oracle_fixed_fee` | asset | Oracle's quoted fixed fee; `≤` market offer |
197
+
198
+ Emits virtual `pm_market_accepted` on accept.
199
+
200
+ ### 3.5 `pm_place_bet`
201
+ | Field | Type | Description |
202
+ |---|---|---|
203
+ | `account` | account | Bettor (signer) |
204
+ | `market_id` | int64 | Target market |
205
+ | `side` | int8 | Binary: `0`/`1`. Multi: `-1` |
206
+ | `outcome_index` | int16 | Multi: `0..N-1`. Binary: `-1` |
207
+ | `amount` | asset | Stake (VIZ, `> 0`) |
208
+ | `min_tokens` | share_type | Slippage floor on received weight (`0` = none) |
209
+ | `mode` | uint8 | `0` instant, `1` batch (queued to next epoch) |
210
+
211
+ ### 3.6 `pm_commit_bet`
212
+ | Field | Type | Description |
213
+ |---|---|---|
214
+ | `account` | account | Bettor (signer) |
215
+ | `market_id` | int64 | Target market |
216
+ | `commitment` | sha256 | SHA-256 of the exact binary preimage in §3.6.1 (consensus-critical) |
217
+ | `escrow_amount` | asset | VIZ locked; `≥ pm_min_batch_bet` |
218
+ | `no_reveal_fee_percent` | uint16 (bp) | **MUST equal** median(`pm_commit_no_reveal_penalty_percent`) — consensus-checked |
219
+
220
+ #### 3.6.1 Commitment preimage (byte-exact — must match consensus)
221
+
222
+ The node recomputes this hash in the `pm_reveal_bet` evaluator (`verify_commit` in
223
+ `libraries/chain/pm_evaluator.cpp`) and rejects the reveal on any mismatch — a wrong preimage
224
+ **forfeits the escrow**. The preimage is a **raw binary concatenation with no separators and no
225
+ length prefixes** (it is *not* the colon-delimited string some prototypes used). Fields, in order:
226
+
227
+ | # | Field | Bytes | Encoding |
228
+ |---|---|---|---|
229
+ | 1 | `market_id` | 8 | int64, **little-endian** (the numeric market instance) |
230
+ | 2 | `account` | 32 | ASCII account name left-aligned in a **32-byte** buffer, **zero-padded** (VIZ `fixed_string_32` storage) |
231
+ | 3 | `side` | 1 | int8 (binary: `0`/`1`; multi: `-1` = `0xFF`) |
232
+ | 4 | `outcome_index` | 2 | int16, **little-endian** (multi: `0..N-1`; binary: `-1` = `0xFFFF`) |
233
+ | 5 | `amount` | 8 | int64, little-endian — the **revealed** amount in **milli-VIZ** (`asset.amount`, i.e. VIZ×1000) |
234
+ | 6 | `min_tokens` | 8 | int64, little-endian (milli-VIZ / weight units) |
235
+ | 7 | `salt` | variable | raw UTF-8/byte content of the salt string (its own length, no terminator) |
236
+
237
+ Fixed portion = **59 bytes**; total = `59 + len(salt)`. `commitment = SHA-256(preimage)` (32-byte
238
+ digest). The same `side`, `outcome_index`, `amount`, `min_tokens`, `salt` must be re-supplied in
239
+ `pm_reveal_bet` (§3.7). Integers use **little-endian** because the node hashes raw host bytes on its
240
+ (x86-64, little-endian) consensus platform — libraries MUST emit little-endian regardless of host.
241
+
242
+ **Golden test vector** (verify your encoder against this before shipping):
243
+
244
+ ```
245
+ market_id = 5
246
+ account = "alice"
247
+ side = 0
248
+ outcome_index = -1
249
+ amount = 10000 # 10.000 VIZ
250
+ min_tokens = 0
251
+ salt = "cafe1234"
252
+
253
+ preimage (hex, 67 bytes):
254
+ 0500000000000000 616c696365000000000000000000000000000000000000000000000000000000 00 ffff 1027000000000000 0000000000000000 6361666531323334
255
+
256
+ commitment = SHA-256(preimage)
257
+ = acc2fbc9e024509a529584baba41dc2eabdb82c3c107dc041d37c94b24f4b3c0
258
+ ```
259
+
260
+ (Preimage shown space-grouped by field for readability; concatenate without spaces before hashing.)
261
+
262
+ ### 3.7 `pm_reveal_bet`
263
+ | Field | Type | Description |
264
+ |---|---|---|
265
+ | `account` | account | Bettor (signer) |
266
+ | `commit_id` | int64 | The `pm_commit` object being revealed |
267
+ | `side` | int8 | Same value that was committed |
268
+ | `outcome_index` | int16 | Same value that was committed |
269
+ | `amount` | asset | `≤ escrow_amount`; surplus refunded |
270
+ | `salt` | string | Entropy bound into the commitment hash |
271
+ | `min_tokens` | share_type | Slippage floor |
272
+
273
+ ### 3.8 `pm_cancel_bet`
274
+ | Field | Type | Description |
275
+ |---|---|---|
276
+ | `account` | account | Bettor (signer) |
277
+ | `bet_id` | int64 | Bet to cancel |
278
+ | `min_return` | share_type | Slippage floor on the refund |
279
+
280
+ ### 3.9 `pm_add_liquidity`
281
+ | Field | Type | Description |
282
+ |---|---|---|
283
+ | `provider` | account | LP (signer) |
284
+ | `market_id` | int64 | Target market |
285
+ | `amount` | asset | VIZ to add (`> 0`) |
286
+
287
+ ### 3.10 `pm_withdraw_liquidity`
288
+ | Field | Type | Description |
289
+ |---|---|---|
290
+ | `provider` | account | LP (signer) |
291
+ | `liquidity_id` | int64 | LP position to withdraw from |
292
+ | `amount` | asset | VIZ to withdraw; `0` = full position. Principal-safe; locked from `betting_expiration` until resolution |
293
+
294
+ ### 3.11 `pm_resolve_market`
295
+ | Field | Type | Description |
296
+ |---|---|---|
297
+ | `oracle` | account | Oracle (signer) |
298
+ | `market_id` | int64 | Target market |
299
+ | `winning_outcome` | int16 | Winning outcome index |
300
+ | `decision_url` | string | Evidence/decision URL; `≤ 256` chars. Stored on the market (`pm_market_object.decision_url`) |
301
+ | `decision_reason` | string | Free-text justification; `≤ 1024` chars. Stored on the market (`pm_market_object.decision_reason`), readable via `get_market` |
302
+
303
+ ### 3.12 `pm_no_contest`
304
+ | Field | Type | Description |
305
+ |---|---|---|
306
+ | `oracle` | account | Oracle (signer) |
307
+ | `market_id` | int64 | Target market |
308
+ | `reason` | string | `≤ 1024` chars. Stored on the market as `decision_reason` |
309
+
310
+ ### 3.13 `pm_dispute_create`
311
+ | Field | Type | Description |
312
+ |---|---|---|
313
+ | `disputer` | account | Disputer (signer); escrows `pm_dispute_fee` |
314
+ | `market_id` | int64 | Target market |
315
+ | `proposed_outcome` | int16 | Outcome the disputer claims is correct (`-1` = void/no-contest challenge) |
316
+ | `reason` | string | `≤ 1024` chars |
317
+
318
+ ### 3.14 `pm_dispute_vote` (committee mode)
319
+ Signed with **regular** authority (like `committee_vote_request`). Ballots are revisable until
320
+ voting closes (modify-or-create).
321
+ | Field | Type | Description |
322
+ |---|---|---|
323
+ | `voter` | account | Voter (regular-auth signer) |
324
+ | `market_id` | int64 | Disputed market |
325
+ | `vote_outcome` | int16 | Correct outcome, or `-1` to uphold the oracle |
326
+ | `vote_percent` | int16 | Conviction / penalty intensity `[-10000, 10000]` (bp). Sign encodes direction; see §5 `get_dispute_votes` for tally semantics |
327
+
328
+ ### 3.15 `pm_dispute_resolve` (account mode)
329
+ Verdict by the market's configured `dispute_resolver`.
330
+ | Field | Type | Description |
331
+ |---|---|---|
332
+ | `resolver` | account | Configured resolver (signer) |
333
+ | `market_id` | int64 | Disputed market |
334
+ | `correct_outcome` | int16 | Final correct outcome |
335
+ | `penalty_amount` | asset | Oracle insurance to slash |
336
+ | `ban_oracle` | bool | Ban the oracle |
337
+ | `ban_oracle_until` | time_point_sec | Oracle ban expiry (`time_point_sec::maximum()` = permanent) |
338
+ | `ban_creator` | bool | Ban the creator from creating markets |
339
+ | `ban_creator_until` | time_point_sec | Creator ban expiry (max = permanent) |
340
+
341
+ > **Bans are a compliance/regulator feature, exclusive to account mode.** When a market routes
342
+ > disputes to an account-mode `dispute_resolver` (e.g. a regulator or a licensed arbitrator), that
343
+ > resolver can sanction **both the oracle and the market creator** — temporarily or permanently
344
+ > (`*_until = maximum()`) — in the same verdict, on top of the insurance slash. This lets a regulator
345
+ > that serves as resolver enforce off-chain rules (bar a bad-faith oracle or a repeat-offender
346
+ > creator from the platform). **Committee/DAO mode (`dispute_mode = 0`) has no ban power by design**
347
+ > — it is a transparent public hearing that only slashes insurance and dings reputation
348
+ > (`pm_dispute_finalize`); it never bans. A ban set here records the issuing `resolver` in the
349
+ > target's `banned_by`, so only that resolver may lift it early via `pm_unban` (§3.23); otherwise it
350
+ > lapses at `banned_until` (cron emits `pm_ban_expired`, §4.12).
351
+
352
+ ### 3.16 `pm_transfer_position`
353
+ | Field | Type | Description |
354
+ |---|---|---|
355
+ | `from` | account | Current holder (signer) |
356
+ | `bet_id` | int64 | Bet whose weight is transferred |
357
+ | `to` | account | Recipient |
358
+ | `amount` | share_type | Weight to reassign; `0` = full. No market impact |
359
+ | `memo` | string | Plaintext, or `#`-prefixed ECIES (same as VIZ memos) |
360
+
361
+ ### 3.17 `pm_lazy_deposit`
362
+ | Field | Type | Description |
363
+ |---|---|---|
364
+ | `account` | account | Depositor (signer) |
365
+ | `amount` | asset | VIZ deposited into the lazy pool (`> 0`) |
366
+
367
+ ### 3.18 `pm_lazy_withdraw`
368
+ | Field | Type | Description |
369
+ |---|---|---|
370
+ | `account` | account | Depositor (signer) |
371
+ | `shares` | share_type | Pool shares to burn; `0` = all |
372
+ | `emergency` | bool | `true` = withdraw before lock ends, with penalty on locked-share profit |
373
+
374
+ ### 3.19 `pm_leverage_open`
375
+ Gated by `pm_leverage_enabled`.
376
+ | Field | Type | Description |
377
+ |---|---|---|
378
+ | `account` | account | Bettor (signer) |
379
+ | `market_id` | int64 | Target market (binary CPMM only) |
380
+ | `outcome_index` | int16 | Binary side `0`/`1` |
381
+ | `collateral` | asset | Bettor's own stake (VIZ) |
382
+ | `loan` | asset | Pool loan (VIZ) |
383
+ | `min_tokens` | share_type | Slippage floor (consensus-checked) |
384
+ | `max_slippage_percent` | uint16 (bp) | User-facing front-run guard |
385
+
386
+ ### 3.20 `pm_leverage_close`
387
+ | Field | Type | Description |
388
+ |---|---|---|
389
+ | `account` | account | Position owner (signer) |
390
+ | `position_id` | int64 | Leverage position to close (only if `cancel_value ≥ liquidation_threshold`) |
391
+ | `min_return` | share_type | Slippage floor on the bettor's return |
392
+
393
+ ### 3.21 `pm_leverage_convert`
394
+ | Field | Type | Description |
395
+ |---|---|---|
396
+ | `account` | account | Position owner (signer) |
397
+ | `position_id` | int64 | Leverage position to convert to a normal bet |
398
+ | `conversion_profit_cost` | uint16 (bp) | **MUST equal** median(`pm_conversion_profit_cost_percent`) |
399
+
400
+ ### 3.22 `pm_dispute_oracle_respond`
401
+ The market's oracle posts a public rebuttal onto the open dispute. Stored on the dispute object
402
+ (read via `get_dispute`); allowed only while the dispute is open and `now ≤ oracle_response_deadline`.
403
+ Re-posting overwrites the previous response.
404
+ | Field | Type | Description |
405
+ |---|---|---|
406
+ | `oracle` | account | Market oracle (signer) |
407
+ | `market_id` | int64 | Disputed market |
408
+ | `response` | string | Rebuttal text; non-empty, `≤ 1024` chars |
409
+
410
+ ### 3.23 `pm_unban`
411
+ Lifts a ban imposed by an account-mode `pm_dispute_resolve`. Only the resolver recorded in the
412
+ target's `banned_by` may lift it. At least one of `unban_oracle` / `unban_creator` must be true.
413
+ | Field | Type | Description |
414
+ |---|---|---|
415
+ | `resolver` | account | The resolver that imposed the ban (signer); must equal the target's `banned_by` |
416
+ | `target` | account | The banned oracle / creator account |
417
+ | `unban_oracle` | bool | Clear the oracle ban (`pm_oracle_object.banned_until`) |
418
+ | `unban_creator` | bool | Clear the creator ban (`pm_creator_ban_object.banned_until`) |
419
+
420
+ ---
421
+
422
+ ## 4. Virtual operations
423
+
424
+ Virtual ops are **never submitted** by clients — they are emitted deterministically by the
425
+ node's bounded per-block cron and appear in block/account history (via the account-history /
426
+ operation-history plugins, `get_ops_in_block`, etc.). Libraries need to **parse** them.
427
+ They have no `extensions` field.
428
+
429
+ | op-id | JSON name | Emitted when |
430
+ |---|---|---|
431
+ | 84 | `pm_batch_settle` | Epoch boundary: queued/revealed bets settle at a uniform price |
432
+ | 85 | `pm_commit_forfeit` | An unrevealed commitment forfeits its penalty into `forfeit_pool` |
433
+ | 86 | `pm_auto_payout` | Deferred market-level payout marker after the dispute grace elapses |
434
+ | 87 | `pm_dispute_finalize` | Committee-mode tally finalized at `voting_end_time` |
435
+ | 88 | `pm_dispute_auto_close` | Anti-freeze: dispute unresolved at `auto_close_time` → full refund |
436
+ | 89 | `pm_oracle_missed_penalty` | Oracle missed `result_expiration` → insurance slashed, refund all |
437
+ | 90 | `pm_lazy_recall` | Lazy-pool graduated recall step (idle allocation returned) |
438
+ | 94 | `pm_leverage_liquidate` | A leveraged position force-closed (opposing bet / cancel / expiration) |
439
+ | 95 | `pm_leverage_resolve` | A leveraged position settled at market resolution |
440
+ | 96 | `pm_market_accepted` | Oracle accepted (or self-oracle auto-accepted); terms frozen |
441
+ | 97 | `pm_payout` | Per-bettor parimutuel settlement (one per active bet inside settle) |
442
+ | 100 | `pm_ban_expired` | A temporary oracle/creator ban lapsed at `banned_until`; the cron cleared it |
443
+
444
+ ### 4.1 `pm_batch_settle`
445
+ `market_id` (int64), `epoch` (uint32), `settled_bets` (uint32).
446
+
447
+ ### 4.2 `pm_commit_forfeit`
448
+ `account`, `commit_id` (int64), `market_id` (int64), `penalty` (asset → `forfeit_pool`), `refund` (asset → account).
449
+
450
+ ### 4.3 `pm_auto_payout`
451
+ `account`, `market_id` (int64), `bet_id` (int64), `payout` (asset).
452
+
453
+ ### 4.4 `pm_dispute_finalize`
454
+ `market_id` (int64), `winning_outcome` (int16), `oracle_penalty` (asset).
455
+
456
+ ### 4.5 `pm_dispute_auto_close`
457
+ `market_id` (int64), `oracle_penalty` (asset).
458
+
459
+ ### 4.6 `pm_oracle_missed_penalty`
460
+ `oracle`, `market_id` (int64), `slashed` (asset).
461
+
462
+ ### 4.7 `pm_lazy_recall`
463
+ `market_id` (int64), `recalled` (asset).
464
+
465
+ ### 4.8 `pm_leverage_liquidate`
466
+ `account`, `position_id` (int64), `market_id` (int64), `cancel_value` (asset), `pool_received` (asset),
467
+ `bettor_received` (asset), `reason` (uint8: `0` opposing_bet, `1` cancel_bet, `2` expiration).
468
+
469
+ ### 4.9 `pm_leverage_resolve`
470
+ `account`, `position_id` (int64), `market_id` (int64), `won` (bool), `pool_received` (asset),
471
+ `bettor_received` (asset), `outcome_index` (int16), `leverage` (uint16 — integer multiple `total_bet/collateral`).
472
+ `won` = position was solvent; `bettor_received == 0` ⇒ collateral lost.
473
+
474
+ ### 4.10 `pm_market_accepted`
475
+ `oracle`, `creator`, `market_id` (int64), `oracle_fee_percent` (uint16 bp, frozen),
476
+ `oracle_fixed_fee` (asset, frozen), `self_oracle` (bool — `true` = auto-accepted at creation).
477
+
478
+ ### 4.11 `pm_payout`
479
+ `account`, `market_id` (int64), `bet_id` (int64), `side` (int8: binary `0`/`1`, multi `-1`),
480
+ `outcome_index` (int16: multi `0..N-1`, binary `-1`), `amount` (asset — the stake),
481
+ `payout` (asset — credited at settle; `0` on a loss).
482
+
483
+ ### 4.12 `pm_ban_expired`
484
+ `account`, `oracle` (bool), `creator` (bool). Emitted by the per-block cron when a **temporary**
485
+ oracle and/or creator ban reaches `banned_until` — the cron clears the ban and fires this so
486
+ history/indexers observe the lift. An *early manual* lift is the signed `pm_unban` op instead (§3.23),
487
+ so this vop fires only for automatic time-expiry. Permanent bans (`banned_until = maximum()`) never
488
+ expire and never emit it.
489
+
490
+ ---
491
+
492
+ ## 5. API methods — `prediction_market_api` plugin
493
+
494
+ Call as `call("prediction_market_api", "<method>", [args])`. `from`/`limit` are pagination
495
+ offsets; `limit ≤ 1000`. Returned object schemas are in §7.
496
+
497
+ ### Markets
498
+
499
+ **`get_market(market_id)`**
500
+ - `market_id` int64.
501
+ - Returns `pm_market_object` (§7.2). Throws if not found.
502
+
503
+ **`list_markets(status, from, limit, [show_risky=false])`**
504
+ - `status` int8 — filter by market status (`-1` deleted, `0` waiting, `1` active, `2` closed, `3` resolved).
505
+ - `from` uint32, `limit` uint32.
506
+ - `show_risky` bool (optional) — when `false` (default), markets whose oracle insurance covers
507
+ `< 2.5×` their betting volume are **hidden**. `true` reveals them.
508
+ - Returns `pm_market_object[]`.
509
+
510
+ **`list_markets_by_oracle(oracle, from, limit)`** → `pm_market_object[]` for `oracle` (account name).
511
+
512
+ **`list_markets_by_creator(creator, from, limit)`** → `pm_market_object[]` for `creator` (account name).
513
+
514
+ **`get_market_outcomes(market_id)`** → `pm_outcome_object[]` (§7.3), ordered by `outcome_index`.
515
+
516
+ **`get_market_weight_sums(market_id)`** → `pm_market_weight_sums_api_object` (§7.16). Per-outcome
517
+ aggregated staked amount and curve weight (computed live from active/resolved bets). For binary
518
+ markets the outcome labels are synthesized as `"A"`/`"B"`.
519
+
520
+ **`get_market_bets(market_id, from, limit)`** → `pm_bet_object[]` (§7.4), all bets on the market.
521
+
522
+ **`get_account_positions(account, from, limit)`** → `pm_position_api_object[]` (§7.15) — each
523
+ bet plus its `expected_payout` (parimutuel projection mirroring settlement) and the market status.
524
+
525
+ **`get_market_liquidity(market_id, from, limit)`** → `pm_liquidity_object[]` (§7.5).
526
+
527
+ **`get_market_full(market_id, [account])`** → `pm_market_full_api_object` (§7.24). One-call enriched
528
+ view: market + outcomes + weight sums + oracle (with reliability) + parsed metadata, plus — when
529
+ `account` is given — that account's bets / leverage positions / LP **on this market**. Saves the thin
530
+ client several round-trips when opening a market detail screen. Throws only if the market is missing.
531
+
532
+ ### Leverage
533
+
534
+ **`get_account_leverage_positions(account, from, limit)`** → `pm_leverage_position_object[]` (§7.12).
535
+
536
+ **`get_market_leverage_positions(market_id, from, limit)`** → `pm_leverage_position_object[]` (§7.12).
537
+
538
+ **`get_creator_ban(account)`** → `pm_creator_ban_object` (§7.13). Throws if the account is not banned.
539
+
540
+ **`get_leverage_quote(market_id, outcome_index, collateral)`** → `pm_leverage_quote_api_object` (§7.20).
541
+ Read-only projection of `pm_leverage_open` using the **same in-node margin math**: the max solvent
542
+ loan, the resulting max leverage, the pool/position caps, and up to 12 slider stops (each with
543
+ tokens, threshold, current & worst-case cancel value). `collateral` is a `share_type` integer
544
+ (milli-VIZ). When leverage is not possible, `available=false` and `failed_constraints[]` explains
545
+ why. Throws only if the market does not exist.
546
+
547
+ **`get_leverage_close_preview(position_id)`** → `pm_leverage_close_preview_api_object` (§7.21).
548
+ Mirrors `pm_leverage_close` at head-block reserves: cancel value, pool obligation, what the bettor
549
+ receives, and `closeable` (false ⇒ the protocol would liquidate instead).
550
+
551
+ **`get_leverage_convert_preview(position_id)`** → `pm_leverage_convert_preview_api_object` (§7.22).
552
+ Mirrors `pm_leverage_convert`: cancel value, current profit, the conversion fee at the current
553
+ median `pm_conversion_profit_cost_percent`, and the `total_user_payment` the convert op would debit.
554
+
555
+ > These three are **non-consensus quotes** — reserves move between the read and the broadcast, so
556
+ > treat the numbers as an estimate at the head block and always send the on-chain slippage guards
557
+ > (`min_tokens` / `min_return` / `conversion_profit_cost`).
558
+
559
+ ### Oracles
560
+
561
+ **`get_oracle(owner)`** → `pm_oracle_api_object` (§7.14) — the raw `pm_oracle_object` plus a
562
+ non-consensus `reliability_score` (bp `[0..10000]`). `owner` = account name. Throws if not found.
563
+
564
+ **`list_oracles(from, limit)`** → `pm_oracle_object[]` (§7.1), ordered by owner name.
565
+
566
+ ### Disputes
567
+
568
+ **`get_dispute(market_id)`** → `pm_dispute_object` (§7.7). Throws if no dispute exists.
569
+
570
+ **`get_dispute_votes(market_id)`** → `pm_dispute_votes_api_object` (§7.17). Returns the live
571
+ ballot list **plus** a stake-weighted projection of what the finalize cron would apply right now
572
+ (quorum status, expected outcome, consensus strength). Safe to call when no dispute exists
573
+ (returns defaults). Key semantics of the vote tally:
574
+ - A vote with `vote_outcome == -1` (or `vote_percent ≤ 0`) counts as **defending the oracle**.
575
+ - A vote with `vote_percent > 0` and a valid `vote_outcome` backs an **outcome change**, weighted
576
+ by `voter_shares × vote_percent / 10000`.
577
+ - Voter weight = `effective_vesting_shares + lazy-pool stake→shares`.
578
+
579
+ ### Lazy pool & chain properties
580
+
581
+ **`get_lazy_pool()`** (no args) → `pm_lazy_pool_object` (§7.9). Throws if the pool is uninitialized.
582
+
583
+ **`get_lazy_deposit(account)`** → `pm_lazy_deposit_object` (§7.10). Throws if the account has no deposit.
584
+
585
+ **`get_lazy_allocations(from, limit)`** → `pm_lazy_allocation_object[]` (§7.11). All lazy-pool
586
+ per-market allocation records (for a pool dashboard).
587
+
588
+ **`get_market_lazy_allocation(market_id)`** → `pm_lazy_allocation_object` (§7.11). The lazy-pool
589
+ allocation for one market. Throws if none.
590
+
591
+ > Oracle penalty stamps are already on `pm_oracle_object` (`penalty_stamps`, `last_penalty_stamp_time`,
592
+ > returned by `get_oracle`) — no separate method needed.
593
+
594
+ **`get_pm_chain_properties()`** (no args) → `chain_properties_pm` (§9) — the **median** (active,
595
+ consensus) values of every PM governance parameter.
596
+
597
+ ### Metadata & charts (non-consensus, plugin-indexed)
598
+
599
+ **`get_market_meta(market_id)`** → `pm_market_meta_object` (§7.18) — parsed category/tags/etc.
600
+ Throws if none indexed yet.
601
+
602
+ **`list_markets_by_category(category, from, limit, [jurisdiction=""], [subcategory=""], [tag=""], [sort="newest"])`**
603
+ → `pm_market_meta_object[]`. Optional filters: `jurisdiction` (ISO code — exclude markets banning
604
+ it), `subcategory` (exact match), `tag` (CSV membership). `sort` ∈ `newest` (market id desc,
605
+ default) · `oldest` (id asc) · `volume` (`bets_sum` desc) · `expiration` (`betting_expiration` asc).
606
+ `volume`/`expiration` load each matching market, so they scan the whole (non-pruned) category
607
+ before paging.
608
+
609
+ **`get_market_categories()`** (no args) → `pm_market_categories_api_object` (§7.23). Category taxonomy
610
+ with live per-category / per-subcategory counts, plus the top 20 hot tags (jurisdiction-* tags
611
+ excluded), aggregated over the currently indexed (non-pruned) markets. Use it to build the browse
612
+ filter chips without hard-coding a taxonomy.
613
+
614
+ **`get_market_kline(market_id, [from=0], [limit=1000])`** → `pm_kline_api_object[]` (§7.19).
615
+ Time-series of per-outcome weight snapshots, ascending by `seq` (oldest→newest). Pagination is
616
+ **offset-from-newest**: `from` skips the newest N points, then up to `limit` are returned. So
617
+ `(from=0, limit=1000)` is the latest ≤1000 changes; `(from=1000, limit=1000)` steps another 1000
618
+ back. Empty array when the market has no recorded points.
619
+
620
+ ---
621
+
622
+ ## 6. `metadata` JSON (client convention)
623
+
624
+ The `metadata` field of `pm_create_market` is consensus-opaque free-form JSON. The
625
+ `prediction_market_api` plugin parses these keys (unknown keys ignored) and exposes them via
626
+ `get_market_meta` / `list_markets_by_category`:
627
+
628
+ | Key | Type | Indexed as |
629
+ |---|---|---|
630
+ | `category` | string | `category` (queryable) |
631
+ | `subcategory` | string | `subcategory` |
632
+ | `tags` | string[] or CSV | `tags` (comma-joined) |
633
+ | `banned_jurisdictions` | string[] or CSV of ISO codes | `banned_jurisdictions` (filtered in `list_markets_by_category`) |
634
+
635
+ Any other keys (title translations, images, source links, etc.) are preserved on-chain in
636
+ `metadata` verbatim but not indexed. Localization is a pure client concern.
637
+
638
+ ---
639
+
640
+ ## 7. Returned object schemas
641
+
642
+ Field order below matches the node's reflection (JSON key order is not guaranteed, but these are
643
+ the exact keys). All monetary fields are `share_type` **integers** (raw, ×1000) unless the type
644
+ says `asset`.
645
+
646
+ ### 7.1 `pm_oracle_object`
647
+ | Field | Type | Description |
648
+ |---|---|---|
649
+ | `id` | object-id | Oracle id |
650
+ | `owner` | account | Oracle account |
651
+ | `insurance` | share_type | Locked insurance bond |
652
+ | `fee_percent` | uint16 (bp) | Oracle % of losers' pool |
653
+ | `fixed_fee` | share_type | Per-market fixed fee |
654
+ | `rules_url` | string | Rules/profile URL |
655
+ | `active_since` | time | First activation time |
656
+ | `last_active_time` | time | Last activity time |
657
+ | `banned_until` | time | `0` not banned; `time_point_sec::maximum()` = permanent |
658
+ | `markets_accepted` | uint32 | Reputation counter |
659
+ | `markets_resolved` | uint32 | Reputation counter |
660
+ | `no_contest_count` | uint32 | Markets voided |
661
+ | `missed_count` | uint32 | Missed resolution deadlines |
662
+ | `disputes_received` | uint32 | Disputes filed against this oracle |
663
+ | `disputes_lost` | uint32 | Disputes where oracle was overturned |
664
+ | `disputes_won` | uint32 | Disputes where oracle was upheld |
665
+ | `disputes_auto_closed` | uint32 | Disputes that hit auto-close |
666
+ | `dispute_responses_missed` | uint32 | Missed dispute-response deadlines |
667
+ | `total_volume_resolved` | share_type | Cumulative resolved volume |
668
+ | `total_insurance_slashed` | share_type | Cumulative insurance slashed |
669
+ | `avg_resolution_time` | uint32 | Avg seconds to resolve |
670
+ | `penalty_stamps` | uint32 | Active penalty stamps (10-day decay) |
671
+ | `bans_received` | uint32 | Ban count |
672
+ | `last_penalty_stamp_time` | time | Time of most recent penalty stamp |
673
+ | `auto_accept_creator` | account | Auto-accept policy (empty = any) |
674
+ | `auto_accept_resolver` | account | Auto-accept policy |
675
+ | `auto_accept` | bool | Auto-accept enabled |
676
+ | `banned_by` | account | Resolver that set `banned_until` (empty if unset); the account allowed to `pm_unban` |
677
+
678
+ ### 7.2 `pm_market_object`
679
+ | Field | Type | Description |
680
+ |---|---|---|
681
+ | `id` | object-id | Market id |
682
+ | `creator` | account | Creator |
683
+ | `oracle` | account | Oracle |
684
+ | `market_type` | uint8 | `0` binary (CPMM), `1` multi (LMSR) |
685
+ | `outcome_count` | uint8 | Number of outcomes (binary = 2) |
686
+ | `url` | string | Resolution criteria/title |
687
+ | `status` | int8 | `-1` deleted, `0` waiting, `1` active, `2` closed, `3` resolved |
688
+ | `payout_status` | uint8 | `0` none, `1` pending, `2` paid, `3` disputed |
689
+ | `created_time` | time | Creation time |
690
+ | `betting_expiration` | time | Betting closes |
691
+ | `result_expiration` | time | Oracle deadline |
692
+ | `resolved_outcome` | int16 | Winning outcome; `-1` if unresolved |
693
+ | `reserve_a` | share_type | Binary CPMM reserve A |
694
+ | `reserve_b` | share_type | Binary CPMM reserve B |
695
+ | `k` | uint128 | CPMM invariant `reserve_a × reserve_b` |
696
+ | `a_bets_sum` | share_type | Binary: total staked on side A |
697
+ | `b_bets_sum` | share_type | Binary: total staked on side B |
698
+ | `lmsr_b` | share_type | Multi: LMSR liquidity parameter |
699
+ | `lmsr_subsidy` | share_type | Multi: LMSR subsidy |
700
+ | `bets_sum` | share_type | Total staked across all outcomes |
701
+ | `liquidity_sum` | share_type | Total LP liquidity |
702
+ | `oracle_fee_percent` | uint16 (bp) | Frozen oracle fee % |
703
+ | `creator_fee_percent` | uint16 (bp) | Creator fee % |
704
+ | `liquidity_fee_percent` | uint16 (bp) | LP fee % |
705
+ | `oracle_fixed_fee` | share_type | Frozen oracle fixed fee |
706
+ | `liquidity_fee_earned` | share_type | Accrued LP fees |
707
+ | `forfeit_pool` | share_type | Forfeited commit penalties added to winners' pool |
708
+ | `time_penalty_type` | uint8 | Time-decay penalty model |
709
+ | `time_penalty_value` | uint32 | Penalty parameter |
710
+ | `penalty_curve_type` | uint8 | Penalty curve |
711
+ | `allow_early_resolution` | bool | |
712
+ | `allow_cancellation` | bool | |
713
+ | `allow_batch` | bool | |
714
+ | `allow_instant_bet` | bool | |
715
+ | `endogeneity_tier` | uint8 | `1`/`2`/`3` |
716
+ | `current_epoch` | uint32 | Current batch epoch |
717
+ | `dispute_mode` | uint8 | `0` committee / `1` account |
718
+ | `dispute_resolver` | account | Configured resolver (account mode) |
719
+ | `dispute_penalty_percent` | int16 (bp) | Oracle penalty policy on successful dispute |
720
+ | `metadata` | string | Raw client JSON |
721
+ | `decision_url` | string | Oracle's cited evidence link, set at resolution (empty until resolved) |
722
+ | `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` |
723
+
724
+ ### 7.3 `pm_outcome_object`
725
+ | Field | Type | Description |
726
+ |---|---|---|
727
+ | `id` | object-id | |
728
+ | `market` | object-id | Owning market |
729
+ | `outcome_index` | uint8 | Index within the market |
730
+ | `label` | string | Outcome label |
731
+ | `q` | share_type | LMSR quantity |
732
+ | `bets_sum` | share_type | Total staked on this outcome |
733
+ | `weight_sum` | share_type | Total curve weight on this outcome |
734
+ | `bets_count` | uint32 | Number of bets |
735
+
736
+ ### 7.4 `pm_bet_object`
737
+ | Field | Type | Description |
738
+ |---|---|---|
739
+ | `id` | object-id | Bet id |
740
+ | `market` | object-id | Owning market |
741
+ | `account` | account | Bettor |
742
+ | `side` | int8 | Binary: `0`/`1`; multi: `-1` |
743
+ | `outcome_index` | int16 | Multi: `0..N-1`; binary: `-1` |
744
+ | `amount` | share_type | Stake |
745
+ | `weight` | share_type | Curve weight received |
746
+ | `price` | uint64 | Execution price (fixed-point) |
747
+ | `time_penalty` | uint32 | Time penalty (`1e6 = 100%`) |
748
+ | `mode` | uint8 | `0` instant, `1` batch |
749
+ | `epoch` | uint32 | Batch epoch (for queued bets) |
750
+ | `status` | uint8 | `0` active, `1` cancelled, `2` refunded, `3` resolved, `5` queued, `6` revealed-pending |
751
+ | `min_tokens` | share_type | Slippage floor for queued batch bets |
752
+ | `resolved_amount` | share_type | Realized payout after settlement |
753
+ | `created_time` | time | |
754
+
755
+ ### 7.5 `pm_liquidity_object`
756
+ | Field | Type | Description |
757
+ |---|---|---|
758
+ | `id` | object-id | LP position id |
759
+ | `market` | object-id | Owning market |
760
+ | `provider` | account | LP account; **empty = Lazy Pool** |
761
+ | `amount` | share_type | Provided liquidity |
762
+ | `weight_a` | share_type | Binary reserve share A |
763
+ | `weight_b` | share_type | Binary reserve share B |
764
+ | `b_share` | share_type | LMSR share |
765
+ | `sec_to_expiration` | uint32 | Position term |
766
+ | `deposit_time` | time | |
767
+ | `earned_fee` | share_type | Accrued fees |
768
+ | `status` | uint8 | `0` active, `3` resolved/closed |
769
+
770
+ ### 7.6 `pm_commit_object`
771
+ | Field | Type | Description |
772
+ |---|---|---|
773
+ | `id` | object-id | |
774
+ | `market` | object-id | Owning market |
775
+ | `account` | account | Committer |
776
+ | `commitment` | sha256 | Commitment hash |
777
+ | `escrow_amount` | share_type | Locked escrow |
778
+ | `no_reveal_fee_percent` | uint16 (bp) | Snapshotted penalty % at commit |
779
+ | `commit_time` | time | |
780
+ | `reveal_deadline` | time | |
781
+ | `status` | uint8 | `0` committed, `1` revealed, `2` forfeited |
782
+
783
+ > Note: there is no direct "get_commit" API method in HF14; commits surface via history/virtual ops.
784
+
785
+ ### 7.7 `pm_dispute_object`
786
+ | Field | Type | Description |
787
+ |---|---|---|
788
+ | `id` | object-id | |
789
+ | `market` | object-id | Disputed market |
790
+ | `disputer` | account | Who filed |
791
+ | `dispute_fee` | share_type | Escrowed fee |
792
+ | `reason` | string | Dispute reason |
793
+ | `filed_time` | time | |
794
+ | `oracle_response_deadline` | time | Oracle must respond by |
795
+ | `dispute_mode` | uint8 | `0` committee / `1` account |
796
+ | `voting_end_time` | time | Committee voting closes |
797
+ | `auto_close_time` | time | Anti-freeze fallback |
798
+ | `proposed_outcome` | int16 | Outcome the disputer proposes |
799
+ | `status` | uint8 | `0` open, `1` oracle-wrong, `2` oracle-right, `3` auto-closed |
800
+ | `oracle_response` | string | Oracle's public rebuttal (empty until it responds via `pm_dispute_oracle_respond`) |
801
+ | `oracle_response_time` | time | When the rebuttal was posted (`0` = none) |
802
+
803
+ ### 7.8 `pm_dispute_vote_object`
804
+ | Field | Type | Description |
805
+ |---|---|---|
806
+ | `id` | object-id | |
807
+ | `market` | object-id | Disputed market |
808
+ | `voter` | account | Voter |
809
+ | `vote_outcome` | int16 | Correct outcome, or `-1` = uphold oracle |
810
+ | `vote_percent` | int16 | Conviction/penalty `[-10000, 10000]` |
811
+ | `time` | time | Vote time |
812
+
813
+ ### 7.9 `pm_lazy_pool_object` (singleton, id 0)
814
+ | Field | Type | Description |
815
+ |---|---|---|
816
+ | `id` | object-id | Always instance 0 |
817
+ | `total_shares` | share_type | Total pool shares outstanding |
818
+ | `free_balance` | share_type | Unallocated VIZ |
819
+ | `allocated_balance` | share_type | VIZ allocated into markets |
820
+ | `earned_balance` | share_type | Cumulative earnings (monotonic) |
821
+ | `reward_per_share` | uint128 | Reward accumulator (`LAZY_POOL_PRECISION = 1e9`) |
822
+ | `leverage_fund_used` | share_type | Total active leverage loans |
823
+
824
+ ### 7.10 `pm_lazy_deposit_object`
825
+ | Field | Type | Description |
826
+ |---|---|---|
827
+ | `id` | object-id | |
828
+ | `account` | account | Depositor |
829
+ | `shares` | share_type | Pool shares held |
830
+ | `principal` | share_type | Original deposited principal |
831
+ | `reward_snapshot` | uint128 | Reward accumulator snapshot |
832
+ | `pending_rewards` | share_type | Unclaimed rewards |
833
+ | `unlock_time` | time | Lock expiry (`pm_lazy_lock_sec` after deposit) |
834
+
835
+ ### 7.11 `pm_lazy_allocation_object`
836
+ Per-market lazy-pool allocation record. Readable via `get_lazy_allocations` (list) and
837
+ `get_market_lazy_allocation(market_id)` (§5).
838
+ `id`, `market`, `amount`, `original_amount`, `recalled_amount`, `returned_amount`,
839
+ `bets_sum_at_check`, `check_step` (uint32 `0..10`), `last_check_time` (time), `status` (uint8: `0` active, `1` returned).
840
+
841
+ ### 7.12 `pm_leverage_position_object`
842
+ | Field | Type | Description |
843
+ |---|---|---|
844
+ | `id` | object-id | Position id |
845
+ | `market` | object-id | Owning market |
846
+ | `account` | account | Position owner |
847
+ | `outcome_index` | int16 | Binary side `0`/`1` |
848
+ | `collateral` | share_type | Bettor's own stake |
849
+ | `loan` | share_type | Pool loan |
850
+ | `total_bet` | share_type | `collateral + loan` deployed |
851
+ | `tokens` | share_type | Weight received from the AMM |
852
+ | `bet` | object-id | Underlying `pm_bet` id |
853
+ | `pool_profit` | share_type | `loan × R%` |
854
+ | `liquidation_threshold` | share_type | `loan × (1 + R%)` |
855
+ | `status` | uint8 | `0` active, `1` liquidated, `2` resolved_won, `3` resolved_lost, `4` closed_voluntary, `5` converted |
856
+ | `liquidated_at` | time | |
857
+ | `liquidated_by_bet` | object-id | The opposing bet that triggered liquidation |
858
+ | `cancel_value_at_liquidation` | share_type | |
859
+ | `pool_received` | share_type | VIZ returned to pool at close |
860
+ | `bettor_received` | share_type | VIZ returned to bettor at close |
861
+ | `created_time` | time | |
862
+ | `last_update` | time | |
863
+
864
+ ### 7.13 `pm_creator_ban_object`
865
+ | Field | Type | Description |
866
+ |---|---|---|
867
+ | `id` | object-id | |
868
+ | `creator` | account | Banned creator |
869
+ | `banned_until` | time | Ban expiry (`maximum()` = permanent; a past time = not banned) |
870
+ | `ban_count` | uint32 | Number of bans received |
871
+ | `banned_by` | account | Resolver that set the current ban (the account allowed to `pm_unban`) |
872
+
873
+ ### 7.14 `pm_oracle_api_object` (from `get_oracle`)
874
+ | Field | Type | Description |
875
+ |---|---|---|
876
+ | `oracle` | `pm_oracle_object` | The raw oracle object (§7.1) |
877
+ | `reliability_score` | uint32 (bp) | Non-consensus heuristic `[0..10000]`: blends resolution success and dispute-win ratios, minus `1000` per ban |
878
+
879
+ ### 7.15 `pm_position_api_object` (from `get_account_positions`)
880
+ | Field | Type | Description |
881
+ |---|---|---|
882
+ | `bet` | `pm_bet_object` | The bet (§7.4) |
883
+ | `expected_payout` | share_type | Parimutuel payout if this side wins (or realized payout once settled). Mirrors settlement exactly |
884
+ | `market_status` | int8 | The market's `status` |
885
+ | `resolved_outcome` | int16 | The market's `resolved_outcome` (`-1` if unresolved) |
886
+
887
+ ### 7.16 `pm_market_weight_sums_api_object` (from `get_market_weight_sums`)
888
+ | Field | Type | Description |
889
+ |---|---|---|
890
+ | `market_type` | uint8 | `0` binary / `1` multi |
891
+ | `bets_sum` | share_type | Total staked |
892
+ | `outcomes` | `pm_weight_entry[]` | Per-outcome breakdown |
893
+
894
+ `pm_weight_entry`: `outcome_index` (int16), `label` (string), `bets_sum` (share_type), `weight_sum` (share_type).
895
+
896
+ ### 7.17 `pm_dispute_votes_api_object` (from `get_dispute_votes`)
897
+ | Field | Type | Description |
898
+ |---|---|---|
899
+ | `votes` | `pm_dispute_vote_object[]` | All ballots (§7.8) |
900
+ | `uphold_weight` | int64 | **Legacy** rough tally (Σ`|vote_percent|` upholding oracle) |
901
+ | `challenge_weight` | int64 | Legacy: Σ`|vote_percent|` backing a change |
902
+ | `total_weight` | int64 | `uphold + challenge` (legacy) |
903
+ | `challenger_leads` | bool | Legacy: challenge share ≥ approve-min |
904
+ | `proposed_outcome` | int16 | Disputer's proposed outcome |
905
+ | `participation_shares` | int64 | Σ voter weight (vesting-shares) that has voted |
906
+ | `electorate_shares` | int64 | `total_vesting_shares + pool_NAV→shares` (quorum base) |
907
+ | `quorum_required_shares` | int64 | `electorate × pm_dispute_approve_min_percent` |
908
+ | `quorum_percent_bp` | int32 | `participation / electorate` (bp) |
909
+ | `quorum_reached` | bool | `participation ≥ quorum_required` |
910
+ | `oracle_defense_shares` | int64 | Σ rshares defending the oracle |
911
+ | `change_shares` | int64 | Σ rshares backing an outcome change |
912
+ | `outcome_change_shares` | int64[] | Per-outcome backing rshares (size = `outcome_count`) |
913
+ | `expected_uphold` | bool | `true` ⇒ oracle resolution stands if finalized now |
914
+ | `expected_outcome` | int16 | Outcome that would be set at finalize now |
915
+ | `expected_consensus_strength_bp` | int32 | `winning / participation` (bp); `0` when uphold |
916
+
917
+ ### 7.18 `pm_market_meta_object` (from `get_market_meta` / `list_markets_by_category`)
918
+ | Field | Type | Description |
919
+ |---|---|---|
920
+ | `id` | object-id | |
921
+ | `market` | object-id | Owning market |
922
+ | `category` | string | Parsed from metadata JSON |
923
+ | `subcategory` | string | |
924
+ | `tags` | string | Comma-joined |
925
+ | `banned_jurisdictions` | string | Comma-joined ISO codes; empty = allowed everywhere |
926
+ | `expiry` | time | Prune time (dispute window close + TTL) |
927
+
928
+ ### 7.19 `pm_kline_api_object` (from `get_market_kline`)
929
+ | Field | Type | Description |
930
+ |---|---|---|
931
+ | `seq` | uint32 | 0-based contiguous index of the change within the market |
932
+ | `timestamp` | uint32 | **Unix seconds** (x coordinate) |
933
+ | `reason` | uint8 | `0` bet, `1` cancel, `2` liquidation, `3` batch settle, `4` leverage open, `5` leverage resolve |
934
+ | `bets_sum` | share_type | Total staked across all outcomes at this point |
935
+ | `weights` | share_type[] | Per-outcome staked weight (y values), index = `outcome_index`. For binary: `[a_bets_sum, b_bets_sum]` |
936
+
937
+ For charting: `x = timestamp`, `y[i] = weights[i]`; implied probability of outcome `i` =
938
+ `weights[i] / Σ weights`.
939
+
940
+ ### 7.20 `pm_leverage_quote_api_object` (from `get_leverage_quote`)
941
+ | Field | Type | Description |
942
+ |---|---|---|
943
+ | `available` | bool | `true` ⇒ `max_loan > 0` (some leverage possible) |
944
+ | `outcome_index` | int16 | Echoed side (0/1) |
945
+ | `collateral` | share_type | Echoed collateral |
946
+ | `max_loan` | share_type | Largest solvent loan (`0` if none qualifies) |
947
+ | `max_leverage_x100` | uint32 | `(collateral+max_loan)/collateral × 100` (`100` = 1.00×) |
948
+ | `pool_free_amount` | share_type | `free_balance − leverage_fund_used` |
949
+ | `fund_available` | share_type | `free_balance × pm_leverage_fund_percent − leverage_fund_used` |
950
+ | `per_position_cap` | share_type | `fund_available × pm_leverage_max_per_position_bp` |
951
+ | `market_position_cap` | share_type | `liquidity_sum × pm_leverage_max_position_ratio_percent` |
952
+ | `pool_profit_percent` | uint16 | `R` (plain %) |
953
+ | `safety_margin_percent` | uint16 | `S` (plain %) |
954
+ | `max_slippage_percent` | uint16 | `SL` (plain %) |
955
+ | `m_factor_percent` | uint16 | worst-opposing m-factor (plain %) |
956
+ | `expiration_buffer_sec` | uint32 | Leverage disabled this long before `betting_expiration` |
957
+ | `auto_close_time` | time | `betting_expiration − buffer` (protocol force-close point) |
958
+ | `stops` | `pm_leverage_stop[]` | Up to 12 solvent slider stops (`0 < loan ≤ max_loan`, ≥1.01×) |
959
+ | `failed_constraints` | `pm_leverage_constraint[]` | Populated when `!available` |
960
+
961
+ `pm_leverage_stop`: `leverage_x100` (uint32), `loan`, `total_bet`, `expected_tokens`, `pool_profit`,
962
+ `liquidation_threshold`, `current_cancel_value`, `worst_case_cancel_value` (all share_type).
963
+ `pm_leverage_constraint`: `constraint` (string key: `leverage_disabled` / `cpmm_binary_only` /
964
+ `market_inactive` / `expiration_buffer` / `min_market_liquidity` / `fund_availability` /
965
+ `position_size` / `solvency`), `reason` (string).
966
+
967
+ ### 7.21 `pm_leverage_close_preview_api_object` (from `get_leverage_close_preview`)
968
+ | Field | Type | Description |
969
+ |---|---|---|
970
+ | `position_id` | int64 | Echoed position |
971
+ | `outcome_index` | int16 | Position side |
972
+ | `cancel_value` | share_type | VIZ the tokens fetch from the curve now |
973
+ | `pool_obligation` | share_type | `liquidation_threshold` → returned to the pool |
974
+ | `bettor_receives` | share_type | `cancel_value − pool_obligation` (floored 0) |
975
+ | `collateral` | share_type | Original bettor stake |
976
+ | `loan` | share_type | Pool loan |
977
+ | `pool_profit_charge` | share_type | Pool's fixed profit on the loan |
978
+ | `closeable` | bool | `cancel_value ≥ pool_obligation` (else the protocol liquidates) |
979
+ | `loss_vs_collateral` | int64 | `collateral − bettor_receives` (negative = profit) |
980
+ | `loss_percent_bp` | int32 | `loss_vs_collateral / collateral` (bp) |
981
+
982
+ ### 7.22 `pm_leverage_convert_preview_api_object` (from `get_leverage_convert_preview`)
983
+ | Field | Type | Description |
984
+ |---|---|---|
985
+ | `position_id` | int64 | Echoed position |
986
+ | `outcome_index` | int16 | Position side |
987
+ | `cancel_value` | share_type | VIZ the tokens fetch now |
988
+ | `pool_obligation` | share_type | Loan + pool profit (repaid on convert) |
989
+ | `current_profit` | share_type | `cancel_value − pool_obligation` |
990
+ | `conversion_profit_cost_percent` | uint16 | Median value the convert op **must** echo |
991
+ | `conversion_fee` | share_type | `current_profit × cost% / 100` |
992
+ | `total_user_payment` | share_type | `pool_obligation + conversion_fee` (debited on convert) |
993
+ | `convertible` | bool | `current_profit > 0` |
994
+
995
+ ### 7.23 `pm_market_categories_api_object` (from `get_market_categories`)
996
+ | Field | Type | Description |
997
+ |---|---|---|
998
+ | `categories` | `pm_category_count[]` | Sorted by count desc |
999
+ | `hot_tags` | `pm_tag_count[]` | Top 20 tags by count (jurisdiction-* excluded) |
1000
+
1001
+ `pm_category_count`: `category` (string), `count` (uint32), `subcategories` (`pm_subcategory_count[]`).
1002
+ `pm_subcategory_count`: `subcategory` (string), `count` (uint32).
1003
+ `pm_tag_count`: `tag` (string), `count` (uint32).
1004
+
1005
+ ### 7.24 `pm_market_full_api_object` (from `get_market_full`)
1006
+ | Field | Type | Description |
1007
+ |---|---|---|
1008
+ | `market` | `pm_market_object` | The market (§7.2) |
1009
+ | `outcomes` | `pm_outcome_object[]` | Outcomes (§7.3); empty for binary markets |
1010
+ | `weight_sums` | `pm_market_weight_sums_api_object` | Per-outcome amount + curve weight (§7.16) |
1011
+ | `oracle` | `pm_oracle_api_object` \| null | The market's oracle + reliability (§7.14); null if not found |
1012
+ | `meta` | `pm_market_meta_object` \| null | Parsed metadata (§7.18); null if not indexed |
1013
+ | `my_positions` | `pm_position_api_object[]` | The `account` arg's bets on this market (empty if no account) |
1014
+ | `my_leverage_positions` | `pm_leverage_position_object[]` | The account's leverage positions on this market |
1015
+ | `my_liquidity` | `pm_liquidity_object[]` | The account's LP positions on this market |
1016
+
1017
+ `oracle` and `meta` are optional (JSON `null` when absent). The `my_*` arrays are empty unless the
1018
+ optional `account` argument was supplied.
1019
+
1020
+ ---
1021
+
1022
+ ## 8. Enum / status quick reference
1023
+
1024
+ | Enum | Values |
1025
+ |---|---|
1026
+ | `market.market_type` | `0` binary (CPMM), `1` multi (LMSR) |
1027
+ | `market.status` | `-1` deleted, `0` waiting, `1` active, `2` closed, `3` resolved |
1028
+ | `market.payout_status` | `0` none, `1` pending, `2` paid, `3` disputed |
1029
+ | `market.dispute_mode` | `0` committee, `1` account |
1030
+ | `market.endogeneity_tier` | `1` econ-data, `2` sports, `3` political |
1031
+ | `bet.status` | `0` active, `1` cancelled, `2` refunded, `3` resolved, `5` queued, `6` revealed-pending |
1032
+ | `bet.mode` / `place_bet.mode` | `0` instant, `1` batch |
1033
+ | `liquidity.status` | `0` active, `3` resolved/closed |
1034
+ | `commit.status` | `0` committed, `1` revealed, `2` forfeited |
1035
+ | `dispute.status` | `0` open, `1` oracle-wrong, `2` oracle-right, `3` auto-closed |
1036
+ | `leverage_position.status` | `0` active, `1` liquidated, `2` resolved_won, `3` resolved_lost, `4` closed_voluntary, `5` converted |
1037
+ | `leverage_liquidate.reason` | `0` opposing_bet, `1` cancel_bet, `2` expiration |
1038
+ | `lazy_allocation.status` | `0` active, `1` returned |
1039
+ | `kline.reason` | `0` bet, `1` cancel, `2` liquidation, `3` batch settle, `4` leverage open, `5` leverage resolve |
1040
+ | `dispute_vote.vote_outcome` | `-1` uphold oracle; `≥0` proposed correct outcome |
1041
+
1042
+ ---
1043
+
1044
+ ## 9. Chain (governance) properties — `get_pm_chain_properties`
1045
+
1046
+ These are median-voted validator params (part of `versioned_chain_properties`). Values shown are
1047
+ **mainnet defaults**; a client must read live values via `get_pm_chain_properties`. Assets are
1048
+ VIZ; `*_percent` are **bp (10000 = 100%)** unless the row says otherwise.
1049
+
1050
+ | Field | Default | Unit | Meaning |
1051
+ |---|---|---|---|
1052
+ | `pm_oracle_registration_fee` | 10.000 VIZ | asset | Oracle registration fee → committee fund |
1053
+ | `pm_min_oracle_insurance` | 5000.000 VIZ | asset | Minimum insurance bond |
1054
+ | `pm_market_creation_fee` | 5.000 VIZ | asset | Market creation fee → committee fund |
1055
+ | `pm_min_liquidity` | 100.000 VIZ | asset | Minimum seed liquidity |
1056
+ | `pm_max_outcomes` | 10 | count | Max outcomes per multi market (`≤ 16`) |
1057
+ | `pm_max_market_duration` | 31536000 | sec | Max market lifetime (≤ 1 year) |
1058
+ | `pm_max_oracle_fee_percent` | 500 | bp | Cap on oracle fee % (5%) |
1059
+ | `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`) |
1060
+ | `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` |
1061
+ | `pm_default_time_penalty_percent` | 50 | bp | Default time penalty |
1062
+ | `pm_max_time_penalty` | 1000000 | 1e6=100% | Max time penalty on profit |
1063
+ | `pm_dispute_fee` | 1000.000 VIZ | asset | Dispute filing fee |
1064
+ | `pm_dispute_grace_sec` | 43200 | sec | Payout grace after resolution (12h) |
1065
+ | `pm_oracle_dispute_response_sec` | 43200 | sec | Oracle response window (12h) |
1066
+ | `pm_dispute_auto_close_sec` | 1209600 | sec | Anti-freeze auto-close (14d) |
1067
+ | `pm_dispute_vote_period_sec` | 259200 | sec | Committee voting period (3d) |
1068
+ | `pm_dispute_approve_min_percent` | 1000 | bp | Quorum / participation threshold |
1069
+ | `pm_oracle_penalty_percent` | 500 | bp | Insurance slashed on missed deadline |
1070
+ | `pm_no_contest_penalty_percent` | 5000 | bp | % of dispute fee on no-contest (50%) |
1071
+ | `pm_dispute_reward_multiplier` | 30000 | bp | Dispute reward multiplier (10000=1×, default 3×) |
1072
+ | `pm_batch_epoch_blocks` | 20 | blocks | Batch epoch length (~60s) |
1073
+ | `pm_reveal_window_blocks` | 200 | blocks | Reveal window (~10min) |
1074
+ | `pm_commit_no_reveal_penalty_percent` | 2000 | bp | No-reveal penalty → winners' pool (20%) |
1075
+ | `pm_min_batch_bet` | 1.000 VIZ | asset | Anti-dust minimum batch bet |
1076
+ | `pm_commit_reveal_enabled` | true | bool | Commit-reveal kill-switch |
1077
+ | `pm_processing_cap_per_block` | 200 | count | Bounded per-block virtual-op work |
1078
+ | `pm_lazy_pool_enabled` | true | bool | Lazy-pool kill-switch |
1079
+ | `pm_lazy_alloc_percent` | 2000 | bp | Free balance allocated per market |
1080
+ | `pm_lazy_max_total_alloc_percent` | 7000 | bp | Cap on total allocation |
1081
+ | `pm_lazy_lock_sec` | 604800 | sec | Deposit lock (7d) |
1082
+ | `pm_lazy_recall_step_percent` | 1000 | bp | Recalled per idle step |
1083
+ | `pm_lazy_emergency_penalty_percent` | 5000 | bp | Emergency-withdraw penalty on profit |
1084
+ | `pm_leverage_enabled` | false | bool | **Leverage kill-switch (off by default)** |
1085
+ | `pm_leverage_fund_percent` | 10 | **percent** | % of free balance usable for loans (F) |
1086
+ | `pm_leverage_max_per_position_bp` | 20 | bp | Fund-available per position (P=0.2%) |
1087
+ | `pm_leverage_pool_profit_percent` | 10 | **percent** | Pool profit per loan (R) |
1088
+ | `pm_leverage_safety_margin_percent` | 1 | **percent** | Open-time safety buffer (S) |
1089
+ | `pm_leverage_max_slippage_percent` | 10 | **percent** | Max price impact per bet (SL) |
1090
+ | `pm_leverage_min_market_liquidity` | 5000.000 VIZ | asset | Min liquidity for leverage |
1091
+ | `pm_leverage_max_position_ratio_percent` | 5 | **percent** | Max position as % of `liquidity_sum` |
1092
+ | `pm_leverage_expiration_buffer_sec` | 86400 | sec | Leverage disabled N sec before expiration |
1093
+ | `pm_leverage_m_factor_percent` | 50 | **percent** | `M_effective = M_max × this%` |
1094
+ | `pm_conversion_profit_cost_percent` | 50 | **percent** | Fee % of unrealized profit on convert |
1095
+
1096
+ > **Percent-scale trap for library authors:** the `pm_leverage_*` and `pm_conversion_*` knobs use
1097
+ > plain percent (`10 = 10%`, validated `≤ 100`), NOT basis points, unlike every other `*_percent`
1098
+ > in this table. `pm_leverage_max_per_position_bp` is the one leverage knob that is genuinely bp.
1099
+
1100
+ ---
1101
+
1102
+ ## 10. Fixed limits (compile-time, from `config.hpp`)
1103
+
1104
+ | Constant | Value | Applies to |
1105
+ |---|---|---|
1106
+ | `MAX_PM_DECISION_URL_LEN` | 256 | `pm_resolve_market.decision_url` |
1107
+ | `MAX_PM_PROFILE_URL_LEN` | 256 | `pm_oracle_register.rules_url` |
1108
+ | `MAX_PM_DISPUTE_REASON_LEN` | 1024 | `pm_no_contest.reason`, `pm_dispute_create.reason` |
1109
+ | `MAX_PM_MARKET_TITLE_LEN` | 256 | `pm_create_market.url` |
1110
+ | `MAX_PM_OUTCOME_LABEL_LEN` | 64 | each `pm_create_market.outcomes[i]` |
1111
+ | `MAX_PM_OUTCOMES_PER_MARKET` | 16 | hard ceiling for `pm_max_outcomes` |
1112
+ | `LAZY_POOL_PRECISION` | 1e9 | `reward_per_share` accumulator scale |
1113
+
1114
+ `metadata` on `pm_create_market` has **no length cap** at the protocol level (like `custom_operation`).
1115
+
1116
+ ---
1117
+
1118
+ ## 11. Implementation checklist per library
1119
+
1120
+ 1. **Operation builders** — 23 user ops (§3), each serialized as `["<name>", {fields}]` in JSON
1121
+ order, with `extensions: []`. Percent-scale per field (§1.1 / §9 trap).
1122
+ 2. **Asset handling** — VIZ, 3 decimals; format/parse `"x.yyy VIZ"`. Internal `share_type` = ×1000 integer.
1123
+ 3. **API client** — 29 read methods (§5) via `call("prediction_market_api", …, [args])`, `limit ≤ 1000`.
1124
+ 4. **Object models** — decode §7 objects; keep `uint128`/large `int64` as big-int/string in JS.
1125
+ 5. **Virtual-op parsing** — recognize the 12 vops (§4) in account/block history.
1126
+ 6. **Enums** — map §8 status codes to human labels.
1127
+ 7. **Metadata** — write the `category`/`subcategory`/`tags`/`banned_jurisdictions` convention (§6).
1128
+ 8. **Governance** — surface `get_pm_chain_properties` (§9) so UIs read live limits, not hard-coded defaults.
1129
+
1130
+ ---
1131
+
1132
+ *Source of truth (regenerate this doc if these change):*
1133
+ `libraries/protocol/include/graphene/protocol/pm_operations.hpp`,
1134
+ `pm_virtual_operations.hpp`, `operations.hpp`,
1135
+ `libraries/chain/include/graphene/chain/pm_objects.hpp`,
1136
+ `libraries/protocol/include/graphene/protocol/chain_operations.hpp` (`chain_properties_pm`),
1137
+ `libraries/protocol/include/graphene/protocol/config.hpp` (`MAX_PM_*`),
1138
+ `plugins/prediction_market_api/` (`prediction_market_api.{hpp,cpp}`, `meta_object.hpp`, `kline_object.hpp`).