Oracle V2 and price resolution
Oracle V2 has resolved a live BTC market. The September 13 production
verification recorded market 17 as resolved_no; its account-credit review
also distinguished automatic redemption during trading from final settlement.
Account credits and on-chain withdrawal finality are separate checks.
Eight crypto spot references and six explicitly labelled perpetual references
are registered on production: on 2026-09-23 GET /api/v2/public/oracle/feeds
listed Healthy feeds for BTC, ETH, SOL, BNB, XRP, ZEC, LINK, HYPE, XAU-PERP,
XAG-PERP, TSLA-PERP, META-PERP, PLTR-PERP and NVDA-PERP, and open V2 binary
markets 22–35 are bound to them (see the
market snapshot).
Apart from market 17 (BTC), none of these markets has reached its cutoff yet,
so no payout on the additional references has been observed. Each new market
needs its own committed policy before trading opens. The applicable version and terms come from that binding,
not the version of a running worker. Market 1 retains its original V1 contract.
The binding is public on the market item (GET /api/v1/public/markets/{market_id},
field instrumentConfig.oracleBinding). A V1 market carries
{ kind: "price_barrier_v1", sourceProfile, triggerOperator, triggerPrice, confirmSamples, minHealthySources, degradedPolicy, cutoffUtc, ... } together
with resolutionPolicy: "oracle_median". A V2 market carries only
{ kind: "canonical_v2", policyHash } with resolutionPolicy: "oracle_v2";
the policy behind that hash is read from the V2 route in
Client integration.
Existing V1 market
At the readback, market 1 was open with price_barrier_v1 and the following
unchanged terms:
| Term | Bound value |
|---|---|
| Reference | BTC; quote currency USDC |
| Sources | Binance, Kraken and Coinbase; all 3 required |
| Observation | Median; 2000 ms polling; 1 confirmation sample |
| Trigger | gte, price 100000 |
| Cutoff | 2027-01-01T04:59:59Z |
| Degraded policy | resolve_no |
This is a barrier contract: a qualifying earlier threshold hit can latch YES. Its legacy degraded policy must not be interpreted as V2's missing-evidence policy. Read the live binding before trading; this table is a dated snapshot.
V2 exchange profile: price at expiry
The deployed exchange implementation supports new BTC/USD markets with
at_expiry_gt: is the reference price strictly above the published strike
at the committed cutoff? Earlier threshold hits do not decide this rule.
Equality with the strike is NO.
The exchange profile combines:
- Coinbase BTC/USD midpoint;
- Kraken BTC/USD midpoint;
- Binance BTC/USDT midpoint, converted to USD using an explicit Kraken USDT/USD midpoint.
It computes the equal-weight arithmetic mean of the three USD-normalized BTC midpoints, using integer micro-USD arithmetic. This is not the legacy V1 median. All three BTC sources and the required FX conversion must satisfy the bound policy. The signed collector evidence is validated and durably recorded by Core.
Additional assets
Exchange policy versions retain their original contracts: version 1 is BTC-only; version 2 adds ETH/SOL/BNB; version 3 adds XRP/ZEC/LINK/HYPE spot; version 4 is reserved for the six perpetual references below. Existing policy hashes and market bindings are preserved.
| Base | First source | Second source | Third source |
|---|---|---|---|
| BTC, ETH, SOL, BNB, XRP, ZEC, LINK | Coinbase {BASE}-USD | Kraken {BASE}/USD | Binance spot {BASE}USDT |
| HYPE | Coinbase HYPE-USD | Kraken HYPE/USD | Bybit spot HYPEUSDT |
| XAU-PERP, XAG-PERP | Binance perpetual XAUUSDT, XAGUSDT | Bybit perpetual, same symbols | Bitget perpetual, same symbols |
| TSLA-PERP, NVDA-PERP, PLTR-PERP, META-PERP | Binance perpetual {TICKER}USDT | Bybit perpetual, same symbols | Bitget perpetual, same symbols |
The -PERP references measure crypto-exchange perpetual order-book prices.
They are not official equity closes, official bullion benchmarks, tokenized
shares, or the exchanges' mark/index prices. Display the -PERP suffix in the
market name and describe its reference before trading. Tesla's ticker is TSLA;
Chainlink's ticker is LINK. XAU is not the token XAUT; HYPE is not HYPER.
Each reference requires all three venues. Every USDT quote is converted using
an explicit Kraken USDT/USD midpoint, then the three USD prices are averaged
with equal weights and checked integer arithmetic. An unavailable source,
closed/paused exchange market, excessive spread or stale quote cannot be
replaced by a different instrument. Missing evidence follows the bound INVALID
rule, including outside the underlying stock exchange's regular hours.
The collector reconstructs Coinbase, Kraken and Bybit books for the additional assets; Kraken checksums must pass. Binance perpetuals use timestamped partial depth snapshots; Bitget uses complete perpetual ticker BBO snapshots. Separate connections and quote histories isolate assets. BTC retains its existing acquisition path. Heartbeats and repeated updates do not refresh price evidence. Quiet markets can still lack sufficiently fresh evidence, and newer post-cutoff quotes never replace missing cutoff observations.
Each policy commits the source identity, collector identity, strike, observation window, cutoff, recovery deadline, sample grid, freshness/spread/disagreement limits and exposure cap before opening. Candidate limits in engineering tests are not automatically the terms of a live market. Display the actual policy and its hash to the trader.
Only quotes observed and received by the cutoff can contribute to the cutoff price. The later recovery deadline allows handling of retained evidence and conflict detection; it does not authorize using a newer post-cutoff price. Finalization waits until after that committed deadline.
Outcomes and payout
For the V2 exchange at_expiry_gt profile:
| Decision | YES payout per share | NO payout per share |
|---|---|---|
| Valid cutoff price above strike | 1 USDC | 0 USDC |
| Valid cutoff price at or below strike | 0 USDC | 1 USDC |
Required evidence missing or conflicting at finalization (INVALID) | 0.50 USDC | 0.50 USDC |
INVALID uses deterministic rounding carry. It does not refund each trader's individual purchase price. The oracle path has no economic-dispute resolver fee; ordinary trading fees remain governed by the market's fee terms.
Timeline at the cutoff
- At
expiryTsMs(= the policycutoff_ms) new orders, quote-replaces, conditional and algo orders are rejected (market N expired for trading at T); the check ists >= expiryTsMs. The marketstatusstaysopen. Resting orders are not cancelled at this point. They remain on the book, cannot trade, and can still be cancelled by their owner. - Until the recovery deadline the dispatcher admits retained evidence for
the cutoff sample. For
at_expiry_gtthe decision is committed only after the committed recovery deadline has passed, even if the cutoff sample arrived earlier. The deadline is part of the policy (1 ms to 24 h after the cutoff; the current exchange markets state 5 seconds in theirrules.rulesText). - On decision the market moves to
resolved_yes,resolved_noorresolved_invalid. In the same step all resting orders of the market are cancelled and their reservations released. - Settlement then runs asynchronously: the settlement worker burns each account's YES/NO position and credits USDC (see below).
No job changes status at the cutoff itself. An operator can close trading
early with an oracle trading command, which also cancels resting orders; the
market then reads closed.
Resolution commits the outcome. The existing persistent settlement worker then processes positions and credits account balances. A resolved state is not proof that every account has already been paid, and an account credit is separate from final on-chain withdrawal. There is no promise of payout at the exact cutoff millisecond. See Settlement.
Client integration
| Endpoint | Use |
|---|---|
GET /api/v1/public/markets/{market_id} | Discover instrument type and its published resolution binding |
GET /api/v1/public/markets/{market_id}/oracle | Read the legacy price-oracle view for applicable V1 markets |
GET /api/v2/public/markets/{market_id}/oracle | Read a bound V2 market's durable policy and progress view |
GET /api/v2/public/oracle/feeds/{hash} | Read the latest durable report and evaluated health for a registered V2 feed |
GET /api/v2/public/oracle/feeds?assets=BTC,NVDA-PERP | Discover registered feeds and their latest verified display prices; omit assets for all feeds |
WS /api/v2/ws/oracle?assets=BTC,NVDA-PERP | Receive an initial snapshot and latest-price snapshots every second |
Live prices over WebSocket
const oracle = new WebSocket(
"wss://api.sentico-labs.xyz/api/v2/ws/oracle?assets=BTC,NVDA-PERP"
);
oracle.onmessage = ({ data }) => {
const message = JSON.parse(data);
if (message.type === "unavailable") {
// Invalidate all displayed live prices until a fresh snapshot arrives.
return;
}
for (const feed of message.feeds) {
console.log(feed.base, feed.referencePrice, feed.health, feed.observedAtMs);
}
};
The public endpoint requires no API key. The optional assets query accepts
up to 32 exact symbols, including the -PERP suffix. Each feed has its own
immutable policyHash; multiple policy versions of one asset can appear.
Frames have type: "snapshot" initially and type: "prices" afterward,
with version: 2, feeds and asOfMs. Each feed includes its USD decimal
referencePrice, observedAtMs, receivedAtMs, health, referenceKind,
sources, verified price object and original signed report. A feed without a
report has a null price and Unavailable health. Expired feeds remain
discoverable with Expired health.
Feed fields
GET /api/v2/public/oracle/feeds returns { version: 2, feeds: [...], asOfMs }
with Cache-Control: no-store; the feed list behind it is refreshed at most
every 500 ms. Each feed object carries:
| Field | Meaning |
|---|---|
policyHash | Content hash of the feed policy. It identifies the feed; there is no separate feed id. One asset can appear with several policy hashes. |
base, quote | Reference asset and quote (USD for the exchange profiles). |
referenceKind | crypto_spot_bbo, or crypto_exchange_perpetual_bbo for a -PERP base. |
sources | Venues of the policy, for example ["coinbase","kraken","binance"]. |
referencePrice | Latest composite price as a USD decimal string with 6 decimals; null when no verifiable report exists. |
price | The verified price object in snake_case: price_micro (equal-weight mean of the three USD-normalized venue midpoints, rounded down), bid_micro / ask_micro (the lowest and highest of the three venue prices, not an order-book BBO), observed_at_ms, valid_from_ms, expires_at_ms, report_id, feed_policy_hash. |
observedAtMs | Observation time of that price. For the exchange profiles it is the sample-grid timestamp (a whole second), not the time a venue quote arrived. Present only when a report verified. |
receivedAtMs | When Core admitted the quote row. |
maxLiveAgeMs | Policy constant: how long a price stays live after observedAtMs (5000 ms on the current feeds). price.expires_at_ms is observedAtMs + maxLiveAgeMs. |
validUntilMs | End of the feed policy's validity window, not of the current price. After it, the feed reports Expired permanently. It is far in the future for active feeds; the retired market-17 BTC feed shows validUntilMs 2026-09-14 and Expired. |
health | Healthy, Stale, Expired, Future, WrongPolicy or Unavailable (see below). |
displayOnly | Always true on this endpoint. The value is a display copy and is never used as resolution evidence. |
report | The original collector-signed observation, including every venue quote and the USDT/USD conversion quote. |
asOfMs | Server time at which this feed entry was evaluated. |
health is evaluated at asOfMs, in this order: Unavailable when no
report exists or it fails verification; WrongPolicy when the report belongs
to another policy; Future when observedAtMs lies too far ahead of the
server clock; Expired when the server time is past price.expires_at_ms
or past validUntilMs; Stale when the price is older than maxLiveAgeMs.
Because the exchange profiles set expires_at_ms = observedAtMs + maxLiveAgeMs, an aged exchange price reads Expired rather than Stale.
Display quotes are not resolution evidence. The live quote is a separate
latest-value row per feed. Resolution reads only the durable report journal.
Both come from the same collector and algorithm, but the display value for the
cutoff second is not guaranteed to be identical to the settlement sample. Read
the decision from the V2 market view (expirySample, decision), not from
this feed.
Only health: "Healthy" represents a current usable display price. A retained
price can remain visible with Stale or Expired; receipt of a frame does not
refresh its observation timestamp. A stream-wide unavailable frame means
the read failed and clients must invalidate their live display. Reconnect
with backoff after closure to receive a full current snapshot. This stream
does not offer historical tick replay. Browser clients answer protocol pings
automatically; other clients must answer pings to retain the connection.
Latest display quotes occupy one stored row per registered feed and are independent of the durable settlement-report journal. They are produced even before an expiry market's cutoff. Market settlement still requires its exact scheduled report, admitted through the settlement journal before the recovery deadline; display snapshots cannot substitute for that evidence.
A V2 market view exposes liveHealth, referencePrice when available,
priceObservedAtMs when available, and healthAsOfMs, alongside its durable
policy/progress data: policyHash, policy (decision_rule, cutoff_ms,
recovery_deadline_ms, threshold_micro, max_collateral_micro, and the
feed with its sources, min_sources, freshness, skew and deviation
limits), decisionRule, decision, executionStatus, expirySample
(priceMicro, reportId, conflicting) and coverageComplete. Market 17,
resolved on 2026-09-13, reads decisionRule: "at_expiry_gt",
decision: "No", executionStatus: "resolved_no" with an expiry sample
below its threshold_micro. A display price is not itself the final decision.
A V2 market without a durable view returns 404; an unavailable report or failed
read may return 503. A V1 market does not gain a V2 view merely because V2 is
deployed.
Only poll a price-oracle endpoint for markets with the corresponding binding. Prediction markets using economic dispute resolution and spot markets do not automatically have price-oracle data. Clear the previous market's displayed price when changing market or when the binding does not apply.
Market cutoffs are UTC instants. Convert a requested Vienna time using
Europe/Vienna and its date-specific daylight-saving offset before binding.
Market creation and operations
New binary markets follow the closed → policy bound → open lifecycle. The bind and open commands require their canonical terminal receipts; an admission ACK alone is insufficient. Existing resolution contracts cannot be silently converted to V2.
Policy administration and report ingestion are separate privileged capabilities. They are not trader credentials or public market-creation shortcuts. Durable collector buffering supports retry after interruptions, but cannot reconstruct a cutoff observation that was never recorded. Missing required evidence follows the bound INVALID rule.
Prediction-market proposals, bonds and challenges remain described in Resolution.