Skip to content

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

MethodArgsReturns
get_marketmarket_idpm_market_object
list_marketsstatus, from, limit, [show_risky]pm_market_object[]
list_markets_by_oracleoracle, from, limitpm_market_object[]
list_markets_by_oracle_statusoracle, status, from, limitpm_market_object[]
list_markets_awaiting_resolutionoracle, from, limitpm_market_object[]
list_markets_by_creatorcreator, from, limitpm_market_object[]
get_market_outcomesmarket_idpm_outcome_object[]
get_market_weight_sumsmarket_idpm_market_weight_sums (computed)
get_market_betsmarket_id, from, limitpm_bet_object[]
get_market_liquiditymarket_id, from, limitpm_liquidity_object[]
get_market_fullmarket_id, [account]pm_market_full (computed)

status for list_markets: -1 deleted, 0 waiting, 1 active, 2 closed, 3 resolved.

list_markets_by_oracle_status returns one oracle's markets filtered to a single status (1 active, 3 resolved, …) — walked over the dedicated by_oracle_status composite index (keyed oracle, status, id) as a bounded prefix, instead of pulling the oracle's whole set with list_markets_by_oracle and filtering client-side. Use it for an oracle profile's "Active / Resolved" tabs, or to page just an oracle's resolved history. (For markets awaiting this oracle's result — active but past betting-close — use list_markets_awaiting_resolution, which cannot be expressed by status alone.)

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.

MethodArgsReturns
get_market_metamarket_idpm_market_meta_object (or error if none)
list_markets_by_categorycategory, from, limit, [jurisdiction], [subcategory], [tag], [sort]pm_market_meta_object[]
get_market_categoriespm_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; sortnewest (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

MethodArgsReturns
get_account_positionsaccount, from, limitpm_position[] (bet + expected_payout)
get_account_leverage_positionsaccount, from, limitpm_leverage_position_object[]
get_market_leverage_positionsmarket_id, from, limitpm_leverage_position_object[]
get_creator_banaccountpm_creator_ban_object (or error if none)
get_oracleownerpm_oracle (object + reliability_score + workload/latency reads)
list_oraclesfrom, limitpm_oracle_object[]
list_markets_in_dispute_windoworacle, from, limitpm_market_card[]
list_oracle_disputesoracle, from, limitpm_oracle_dispute[] (computed)

list_markets_in_dispute_window drills into the markets_in_dispute_window gauge: this oracle's resolved (status 3, payout_status 1) markets that still carry no dispute row — i.e. the ones a challenger could still dispute within pm_dispute_grace_sec of the announcement. list_oracle_disputes drills into the two dispute gauges: this oracle's currently open disputes (status 0), each tagged with a stageawaiting_response (the oracle has not answered yet) or awaiting_decision (answered, now with the resolver/committee) — derived from oracle_response_time, plus a market_card for rendering. Both walk only the oracle's own set (by_oracle_status / by_auto_close), so they stay O(this oracle), not a global scan. They pair with the stored gauges on get_oracle (below): the gauge is the count, these methods are the list.

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).

MethodArgsReturns
get_leverage_quotemarket_id, outcome_index, collateralpm_leverage_quote (computed)
get_leverage_close_previewposition_idpm_leverage_close_preview (computed)
get_leverage_convert_previewposition_idpm_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

MethodArgsReturns
get_disputemarket_idpm_dispute_object
get_dispute_votesmarket_idpm_dispute_votes (votes + live tally)
get_lazy_poolpm_lazy_pool_object
get_lazy_depositaccountpm_lazy_deposit_object
get_lazy_allocationsfrom, limitpm_lazy_allocation_object[]
get_market_lazy_allocationmarket_idpm_lazy_allocation_object (or error if none)
get_pm_chain_propertieschain_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.

MethodArgsReturns
get_market_klinemarket_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 — the raw pm_oracle_object plus read-time computed fields. All non-consensus (display only; never gate consensus).

  • reliability_score — bp [0..10000]. v2.1 blends four reputation ratios, then docks decayed penalty stamps and bans, then confidence-shrinks a thin track record toward a neutral prior:
    accuracy    = markets_resolved / (markets_resolved + missed_count)         // resolved vs missed-deadline
    verdicts    = disputes_won / (disputes_won + disputes_lost)                // dispute outcomes
    responsive  = (disputes_received − dispute_responses_missed) / disputes_received
    timely      = (markets_resolved − resolved_late_count) / markets_resolved  // on-time vs past-deadline resolves
    score       = accuracy·40% + verdicts·30% + responsive·15% + timely·15%
    score      −= penalty_stamps × 300bp   (halved per 10 days since last_penalty_stamp_time)
    score      −= bans_received × 1500bp
    if markets_resolved < 20: shrink toward a 6000 prior      // unproven oracles neither sit at 100 nor crater
    Each ratio defaults to a full 10000 until the oracle has the relevant history (optimistic when unproven). timely is the P5 lateness signal: a late-but-delivered resolve still counts as markets_resolved (so it earns full accuracy), and timely is what separates a chronically-late oracle from a punctual one. avg_resolution_time is deliberately not scored — it measures latency from betting close, not deadline overrun, and can't be normalized without the market length. Weights are non-consensus; tune freely.
  • Workload gauges (stored on pm_oracle_object, O(1)-maintained across the op/cron transitions, seeded once on upgrade): markets_in_dispute_window, disputes_awaiting_response, disputes_awaiting_decision — the live counts an Oracle Console badges; drill into the lists with list_markets_in_dispute_window / list_oracle_disputes.
  • Computed-on-read (time-dependent, so not stored): markets_awaiting_resolution + oldest_unresolved_age (this oracle's status 1 markets past betting close, and the age of the oldest — one by_oracle_status walk), and resolution_time_p50 / resolution_time_p95 (percentiles read off the oracle's 8-bucket latency histogram resolution_time_hist, upper bucket edge where the cumulative count first crosses the percentile).

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).

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 for the on-chain objects these methods expose, and Chain Properties for the v5 governance parameters.