Skip to main content
Every market on counsel is a self-contained on-chain object: a signed JSON definition stored as a memo on the XRP Ledger, a dedicated pool account that holds all stakes, and a resolution source that determines the winning outcome. Understanding how these pieces fit together tells you what a market can do, when you can act, and what happens if something goes wrong.

Market families

counsel supports two resolution families. The family is set at market creation and cannot change.

Family A, Oracle-resolved (crypto price)

Family A markets resolve automatically against an on-chain XLS-47 price oracle. No human input is required at resolution time. These markets are defined by:
FieldDescription
oracleAccountXRPL account that publishes the oracle
oracleDocumentIdXLS-47 OracleDocumentID
baseAssetAsset being priced (e.g. "XRP")
quoteAssetDenomination (e.g. "USD")
strikePrice threshold for the binary condition
comparison">=" or "<=", the direction of the condition
freshnessSecondsMaximum allowed age (seconds) of the oracle value relative to resolutionTime
Example condition: XRP/USD >= 0.60 at resolution time. If the oracle reports 0.62 within the freshness window, outcome 0 (“Yes”) wins. If it reports 0.58, outcome 1 (“No”) wins. If no fresh value is available, the market voids. Family A markets have disputeWindowHours: 0 by default, resolution is fully automatic with no dispute period.

Family B, Manual (real-world events)

Family B markets cover events with no on-chain data source: sports results, political outcomes, economic data releases, and so on. Resolution is operator-proposed and subject to a dispute window.
FieldDescription
sourceNamed authoritative source (e.g. "FIFA official result")
conditionExact, pre-committed condition text
dateISO date the condition is evaluated
evidenceHintOptional hint to the evidence URL used at resolution
The default dispute window is 48 hours. Any account with a stake can challenge the operator’s proposal within that window by posting a bond.

Market lifecycle

A market moves through a fixed sequence of phases. The current phase is returned as phase in GET /api/v1/markets/:id.
open → closed → resolving → resolved
                          ↘ void
         (Family B only: closed → disputable → resolving → resolved)
1

open

Bets are accepted. The pool accumulates. Odds shift with every new stake. This is the only phase in which you can place a bet.
2

closed

bet_cutoff has passed. No new bets are accepted. The pool is frozen. The market is awaiting resolutionTime before settlement runs.
3

disputable (Family B only)

After resolutionTime, the operator publishes a resolution proposal and the dispute window opens (disputeWindowHours, default 48 hours). Any staked account can post a challenge bond during this period. This phase does not appear for Family A markets, which have disputeWindowHours: 0.
4

resolving

The dispute window has closed (or does not apply for Family A). The committee multisig is in progress. No further challenges can be submitted.
5

resolved

The winning outcome has been determined and payouts have been distributed. A status: "final" record is on-chain.
6

void

The market cannot be resolved fairly. All stakes are refunded at face value. No fee is charged. See Void conditions below.
The status field returned by GET /api/v1/markets/:id shows only open, resolved, or void. The phase field carries the granular phase value (open, closed, disputable, resolving, or resolved).

Pool structure on XRPL

Each market has a dedicated poolAccount, an XRPL r-address that holds all funds staked on that market. Outcomes are separated by DestinationTag:
  • Outcome 0 → DestinationTag 0
  • Outcome 1 → DestinationTag 1
  • Outcome N → DestinationTag N
When you place a bet, you send a native XRP Payment to poolAccount with the DestinationTag corresponding to your chosen outcome. The pool account’s master key is disabled; funds can only leave via a committee multisig at settlement. You can verify the pool balance independently at any time by querying the XRPL for the pool account’s transaction history.

Void conditions

The following conditions trigger a full void and refund:
If all bets land on a single outcome (no opposing stake exists), there is no pool to distribute to the other side. The market voids and all stakes are returned.
At resolutionTime, the XLS-47 oracle value must have been published within freshnessSeconds of that timestamp (with a 120-second post-close grace period). If no fresh value is available, the market voids automatically.
If the underlying event is cancelled, indefinitely postponed, or produces a result that does not map cleanly to the defined outcomes (e.g. a draw on a binary win/loss market), the market voids.
On a void, each bettor receives their original stake back via a native Payment. Market-maker seed stakes are house-neutral and are not refunded, they remain as pool residue.

Betting caps

Markets can impose limits to manage pool concentration. These are defined in the caps object:
FieldDescription
perBettorXrpMaximum total XRP a single account can stake across all outcomes in this market
perOutcomeXrpMaximum total XRP that can accumulate on any single outcome
perMarketXrpMaximum total XRP the whole market pool can hold
Any of these fields may be absent, in which case no cap applies. Caps are enforced at bet submission; a bet that would breach a cap is rejected before it is submitted to the ledger.

Market identity and deduplication

Every market has a deterministic id computed as the first 16 hex characters of sha256(question | resolutionTime). Recurring market templates (e.g. daily XRP price markets) share a templateKey; only the most recently created instance per templateKey is shown in the default listing. Markets with hidden: true are retired or superseded and are not returned by the public API.