Core concepts

Market resolution & voids

How a market settles, how to read a split or refunded outcome, and what happens to positions and open orders

When a market ends, sufwan-local-test records how each outcome settled and updates the user's positions. Most markets settle to one winner. Some settle as a split, where every share pays part of a dollar, or as a void, where holders get their stake back. This page shows how to read each case and what the user has to do.

Market status

closed and resolved in this section are market status, not position status.

StatusMeaning
closedMarket status: trading has stopped. On some venues a voided market also ends in closed.
resolvedMarket status: the venue has published how the market settled.

Status alone does not tell you what an outcome pays. Read the outcome fields below.

Reading an outcome

Read refundAtCost first: when it is true the market was voided and a share pays the user's entry price. If payoutPerShare is set, that is the USD paid per share, from 0 to 1. Otherwise winner: true pays $1 divided by the number of winning outcomes, winner: false pays $0, and winner: null is not settled yet.

Each outcome on GET /venue-markets, GET /venue-events and GET /venue-market-outcomes carries those fields. Check them in this order:

CheckMeaningWhat a share pays
refundAtCost: trueThe market was voided and holders are refunded.The user's entry price
payoutPerShare is setThe venue published a per-share payout.payoutPerShare USD (0 to 1)
winner: trueThe outcome won.$1 divided by the number of winning outcomes
winner: falseThe outcome lost.$0
winner: nullNot settled yet.Keep showing the market price

A void often shows up as more than one outcome with winner: true. Always check refundAtCost and payoutPerShare before assuming a winner pays $1.

Examples:

  • Polymarket 50/50. Both outcomes have winner: true and payoutPerShare: 0.5. Each share pays $0.50.
  • Refund at cost. Every outcome has refundAtCost: true. A position bought at 0.42 is refunded at $0.42 a share.

Inferred winners

Outcomes also carry winnerSource, which says who decided winner. It is null when winner is null, and also on winners recorded before this field existed:

winnerSourceMeaning
venueThe venue's own result. Safe to settle on.
matchedInferred by sufwan-local-test because this venue named no winner yet: from matched markets on other venues, or from the same game's moneyline for a soccer ±0.5 spread. Provisional.
nullwinner is null, or the winner predates this field.

Only ProphetX produces matched winners today. ProphetX's resolution event names no winner. About every 15 minutes after a market resolves, sufwan-local-test fills the winner from matched markets on other venues for the same game, and only when they agree. A soccer spread at exactly ±0.5 goals is filled from the same game's moneyline instead, and is also matched, even when that moneyline is a venue result.

A matched winner can still change. When ProphetX settles the market itself, that overrides the inferred winner, flips winnerSource to venue and bumps the market's updatedAt. A push or void sets refundAtCost: true; otherwise the outcomes take ProphetX's won or lost result. For example, in a tennis match where one player retired, prediction markets paid the player who advanced while ProphetX voided the bet, so the matched winner was replaced by a refund.

The matched fill does not send market_resolved or a webhook. ProphetX's resolution event fires earlier, with no winner. The later correction may or may not send market_resolved and the webhook (with winnerSource: "venue"), depending on how ProphetX's result reaches sufwan-local-test. To see a matched winner and any correction reliably, poll updatedAt or re-read the market.

Settle held ProphetX positions only when winnerSource is "venue" or refundAtCost is true. Treat matched as provisional.

Positions after settlement

GET /execution/positions reflects settlement for each outcome the user holds:

  • winner is set, currentPrice becomes the payout per share, and priceSource is settled.
  • redeemStatus tells you whether to call POST /execution/redeem:
redeemStatusMeaning
eligibleClaim the payout with POST /execution/redeem.
pendingA claim is in progress.
redeemedAlready claimed or settled.
ineligibleNothing to claim through sufwan-local-test: the outcome lost, the venue settles on its own, or the position is self-custody and is claimed on the venue.

A position's status: closed is position status: that holding is settled or sold. It is not market status closed.

Redeem

POST /execution/redeem claims outcomes whose redeemStatus is eligible. mode (live or paper) is optional.

SDK
const claimed = await client.redeem({ venueMarketOutcomeIds: [outcomeId] });

Redeem winnings for resolved positions.

The call returns before an EVM claim finishes. Read redeemId and each results[] entry's status (submitted, confirmed, ineligible, or rejected) and reason. That body is not the settlement record above.

Auto-redeem. When a market resolves, sufwan-local-test automatically claims payouts for your users' winning positions on venues that require an on-chain claim (Polymarket, Limitless, Predict, Myriad), including winnings that were left unclaimed before this feature launched. The claim starts about 2 minutes after sufwan-local-test sees the market resolve (an inherent delay so the venue's on-chain payout is in place first), so expect the payout a few minutes after the market closes, depending on how quickly the venue resolves it. On by default; turn it off in the dashboard under Settings → Auto-redeem winning positions.

By venue

In this table, closed and resolved are market status.

VenueHow a split or void appearsWhat the user does
Polymarketresolved. A 50/50 has both outcomes winner: true with payoutPerShare: 0.5.Managed: call POST /execution/redeem, which pays $0.50 a share. Self-custody: redeem on Polymarket.
KalshiA cancelled or postponed event settles resolved at a fair price: YES pays Kalshi's settlement value as payoutPerShare and NO pays the rest. Every side that pays anything has winner: true.Nothing. Kalshi settles the payout itself; there is no redeem step.
LimitlessA 50/50 settles resolved. Both outcomes have winner: true and their payoutPerShare, for example 0.5.Managed: call POST /execution/redeem, which pays the split.
PredictA 50/50, for example a cancelled match, settles resolved with both outcomes winner: true and no payoutPerShare. Each share pays $1 divided by the two winners, $0.50.Call POST /execution/redeem, which pays $0.50 a share.
MyriadA voided market ends as closed. Each outcome's payoutPerShare is its last market price, which Myriad refunds.Call POST /execution/redeem to claim the refund. redeemStatus is eligible.
Hyperliquidresolved. Split or scalar settlements set payoutPerShare on each outcome.Nothing. Positions settle automatically.
BetDEXA voided market ends as closed.Nothing. The hosted account settles automatically, and the position closes with the settled result.
TipRunA void or fractional settlement ends as closed.Nothing. Positions settle automatically at the venue's payout.
NovigA void, for example a player who didn't play, settles resolved with each outcome winner: true and its refund price as payoutPerShare. When a game is cancelled, markets that traded are voided this way, and markets nobody traded end as closed with no winner.Nothing. Novig settles positions in the user's Novig account; there is no redeem step through sufwan-local-test.
ProphetX (direct accounts)A cancelled market, or a game ProphetX settles as a push, a draw or with no official result, is market status resolved with refundAtCost: true on every outcome.Nothing. The stake is refunded at the entry price in the user's ProphetX account.

A ProphetX direct account is the user's own ProphetX account, using that account's cash rather than an sufwan-local-test balance. Choose your integration does not list it: sufwan-local-test does not deposit, withdraw, or redeem those funds, and the position settles in the ProphetX account.

Open orders

When a market closes or resolves, or its end date passes, sufwan-local-test cancels the user's resting managed limit orders on the venue. They end with status expired.

Self-custody limit orders on Polymarket and Hyperliquid are different: sufwan-local-test does not cancel them. They stay on the venue until the venue expires them or the user cancels. See Limit orders.

Webhooks

The webhook markets.resolved and the socket event market_resolved are different channels, and EVM redeem completion is the socket event redeem_event, not market_resolved. On either settlement channel, read refundAtCost and payoutPerShare before you show an amount. When more than one outcome has winner: true, the market settled as a split or void. payoutPerShare is absent when the venue has not reported it. winnerSource is absent when winner is, and on winners recorded before this field. See the webhook event reference.

On this page