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.
| Status | Meaning |
|---|---|
closed | Market status: trading has stopped. On some venues a voided market also ends in closed. |
resolved | Market 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:
| Check | Meaning | What a share pays |
|---|---|---|
refundAtCost: true | The market was voided and holders are refunded. | The user's entry price |
payoutPerShare is set | The venue published a per-share payout. | payoutPerShare USD (0 to 1) |
winner: true | The outcome won. | $1 divided by the number of winning outcomes |
winner: false | The outcome lost. | $0 |
winner: null | Not 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: trueandpayoutPerShare: 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:
winnerSource | Meaning |
|---|---|
venue | The venue's own result. Safe to settle on. |
matched | Inferred 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. |
null | winner 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:
winneris set,currentPricebecomes the payout per share, andpriceSourceissettled.redeemStatustells you whether to callPOST /execution/redeem:
redeemStatus | Meaning |
|---|---|
eligible | Claim the payout with POST /execution/redeem. |
pending | A claim is in progress. |
redeemed | Already claimed or settled. |
ineligible | Nothing 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.
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.
| Venue | How a split or void appears | What the user does |
|---|---|---|
| Polymarket | resolved. 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. |
| Kalshi | A 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. |
| Limitless | A 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. |
| Predict | A 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. |
| Myriad | A 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. |
| Hyperliquid | resolved. Split or scalar settlements set payoutPerShare on each outcome. | Nothing. Positions settle automatically. |
| BetDEX | A voided market ends as closed. | Nothing. The hosted account settles automatically, and the position closes with the settled result. |
| TipRun | A void or fractional settlement ends as closed. | Nothing. Positions settle automatically at the venue's payout. |
| Novig | A 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.
Related
- Markets, outcomes & matching
- Execution: redeeming winnings
- Order lifecycle & statuses