Module 14: Resolution Deep Dive
What this covers: The complete dispute and resolution lifecycle for Basis prediction markets — from the moment a market ends to final payout. Includes all resolver SDK methods, private market resolution, bounty hunting economics, cross-platform arbitrage timing, and full error handling.
Prerequisites
- Basic prediction market mechanics — market lifecycle, outcome shares, seedAmount
- Prediction strategies — participant roles (esp. Role 5 Resolver) and combined plays
- Trading basics — buying outcome shares and Predict+ tokens, slippage protection
- Familiarity with wSTASIS staking (any ecosystem token stake qualifies to vote)
- Client initialized (→02) with sufficient USDB for 5 USDB bonds
Next steps
- Layer resolver income with other prediction strategies → Full Stack Creator pattern
- Stack resolver bounties with capital recycling loops
- Full SDK method signatures for
client.resolver.* - Error reverse index (§3) for resolver revert strings
1. The Resolution Lifecycle
A prediction market moves through four phases after its end time passes. predictionStatus values track this: "active" → "awaiting_proposal" → "proposed" → "disputed" → "resolved".
Phase 1: Proposal
After endTime, the market enters "awaiting_proposal". Anyone can propose the winning outcome (this is the Resolver role):
await client.resolver.proposeOutcome(marketToken, outcomeId);
- Costs a 5 USDB bond (auto-approved by the SDK)
- Starts the challenge period (PROPOSAL_PERIOD: Phase 1 only = 30 min, production target: 2 hours)
- During the challenge period, anyone who disagrees can dispute
- If no dispute arrives by the end of the challenge period, the proposer calls
finalizeUncontested()and collects the bond back plus 100% of the bounty pool
Self-dispute is explicitly allowed. A proposer can dispute their own proposal if they made a mistake. The cost is one additional bond, but it beats waiting for someone else to dispute and claim your bond. No scenario makes self-disputing profitable: if voters pick either of your outcomes, bonds net to zero; if they pick a third outcome, both bonds go to the insurance pool.
Phase 2: Dispute
During the challenge period, any party can file a dispute with an alternative outcome:
await client.resolver.dispute(marketToken, alternativeOutcomeId);
- Costs a 5 USDB bond (no escalation — always 5 USDB regardless of how many rounds)
- Market status moves to
"disputed" - Triggers the voting period (DISPUTE_PERIOD: Phase 1 only = 30 min, production target: 24 hours)
- Special: only the disputer can propose EARLY (outcome ID 253). Anyone can propose INVALID (outcome ID 254)
Phase 3: Vote
During the voting period, staked token holders cast votes:
// Stake first (one-time, reusable across markets)
await client.resolver.stake(ecosystemTokenAddress);
// Then vote
await client.resolver.vote(marketToken, outcomeId);
Staking rules: (distinct from STASIS vault staking — this is a resolver-specific lock)
- Minimum: 5 tokens of any active ecosystem token
stake()auto-readsMIN_STAKE_AMOUNTfrom the contract and approves it — no amount parameter needed- One-staker-one-vote: staking above the minimum does not increase voting power
- Vote lock: After voting, your staked tokens are locked for 24 hours (VOTE_LOCK_DURATION). Do not stake tokens you need liquid access to within the next day. This lock applies even if the market resolves quickly. Factor this into capital allocation before voting — if these tokens collateralize an active loan, you cannot unstake to repay.
Finalization requirements:
- Quorum:
bountyPool / (50 × $1), clamped between 2 (minimum) and 100 (maximum). Calculated from total votes across all outcomes. - Supermajority: 70% of votes must go to a single outcome (VOTING_CONSENSUS = 70)
- If quorum is not met or no outcome reaches 70%,
finalizeMarket()reverts with "Quorum not met yet" or "70% consensus needed" or "Tie - vote more". The market stays open for additional voters — bonds remain locked until resolution completes.
Phase 4: Finalization and Payout
After the voting period, anyone can call:
// Uncontested path (challenge period elapsed, no dispute)
await client.resolver.finalizeUncontested(marketToken);
// Contested path (voting period elapsed, quorum and consensus met)
await client.resolver.finalizeMarket(marketToken);
Then claim bounties:
await client.resolver.claimBounty(marketToken);
await client.resolver.claimEarlyBounty(marketToken, round); // EARLY outcome
Bond outcomes after finalization:
| Scenario | Who gets what |
|---|---|
| Uncontested | Proposer gets bond back + 100% of bounty |
| Disputed, correct proposer wins | Correct party gets both bonds (theirs + opponent's) |
| Disputed, neither party was correct | Insurance pool gets both bonds |
| Neither correct, voters decide | Voters split bounty equally per vote |
Bounty distribution rules:
| Resolution type | Bounty destination |
|---|---|
| Uncontested | 100% to proposer |
| Disputed, normal outcome wins | 100% split equally among correct voters. Bond winner gets bonds only, not the bounty. |
| INVALID proposed by a party | That party gets 100% of bounty + both bonds |
| EARLY | Half of proposer's bond split among EARLY voters. For EARLY resolutions, the bounty pool is distributed to the proposer if uncontested. |
Post-resolution selling note: On Basis, mass selling after resolution pushes the price up — selling burns tokens, slippage stays in the pool, and price rises. This is Stable+ mechanics in action. Patient sellers who wait through the initial sell wave exit at the highest price.
Special Outcomes
| Outcome | ID | Who Can Propose | Effect |
|---|---|---|---|
| Normal | 0–252 | Anyone | Standard resolution — winners redeem shares |
| EARLY | 253 | Disputer only (voters can vote for it; vetoers cannot propose it) | Resets market — round increments, fresh proposal cycle begins |
| INVALID | 254 | Anyone (proposers, disputers, voters, vetoers) | Proportional refund to all participants |
| UNRESOLVED | 255 | Internal | Default state before any proposal |
INVALID: Use when the market question is ambiguous, the event did not occur as defined, or resolution criteria are fundamentally broken. All participant funds are refunded proportionally.
EARLY: Use when the market ended prematurely or the triggering event occurred in a way that invalidates the market without being "wrong" — effectively a do-over. Only the active disputer can propose EARLY. If EARLY passes, the market increments its round counter and the full proposal cycle restarts.
Veto Mechanism
After the voting period expires on a disputed market, a veto window opens (VETO_PERIOD: Phase 1 only = 30 min, target: 1 hour). During this window, anyone can veto by submitting a 5 USDB bond:
await client.resolver.veto(marketToken, proposedOutcome);
Veto constraints:
- One veto per market
- Cannot veto using the disputer's outcome or EARLY
- Halts voting — resolution escalates to
resolveByBasis(platform admin decision — admin authority scope covered in Module 16) - Post-TGE: veto power transitions to BASIS staker governance
2. Resolver SDK Methods
Full signatures and return types are catalogued in Module 18 SDK Reference under client.resolver.
Write Methods
proposeOutcome(marketToken, outcomeId)
Proposes the winning outcome for a market that has passed its end time. Auto-approves the 5 USDB proposal bond.
| Param | Type | Description |
|---|---|---|
marketToken | string | Market token address |
outcomeId | number | 0-indexed outcome ID (0–252 for normal, 253 EARLY disputer-only, 254 INVALID) |
Also available as client.resolver.propose() — identical behavior.
dispute(marketToken, newOutcomeId)
Disputes the currently proposed outcome with an alternative. Auto-approves the 5 USDB dispute bond. Triggers the voting period.
| Param | Type | Description |
|---|---|---|
marketToken | string | Market token address |
newOutcomeId | number | Alternative outcome ID the disputer believes is correct |
vote(marketToken, outcomeId)
Casts a vote during a dispute round. Requires prior staking via stake().
| Param | Type | Description |
|---|---|---|
marketToken | string | Market token address |
outcomeId | number | Outcome the voter believes is correct |
Requires: stake() called before voting. One vote per staker. Staking more than the minimum gives no additional voting power.
stake(token) / unstake(token)
Stakes or unstakes tokens to participate in dispute resolution. stake() reads MIN_STAKE_AMOUNT from the contract and auto-approves — no amount parameter.
| Param | Type | Description |
|---|---|---|
token | string | Address of any active ecosystem token |
Post-vote lock: Staked tokens are locked for 24 hours after voting. unstake() will revert with "Stake locked due to recent vote" until lastVoteTime + VOTE_LOCK_DURATION has elapsed.
finalizeUncontested(marketToken)
Finalizes a market whose proposal was not disputed within the challenge period. Anyone can call this. Proposer receives bond back + full bounty pool.
finalizeMarket(marketToken)
Finalizes a market after dispute voting completes. Requires quorum met and no tie. Anyone can call this after the voting period elapses.
veto(marketToken, proposedOutcome)
Vetoes a disputed market's resolution after the voting period expires. Requires 5 USDB bond. One veto per market. Cannot veto with the disputer's outcome or EARLY. Escalates to platform admin resolution.
claimBounty(marketToken) / claimEarlyBounty(marketToken, round)
Claims bounty reward for correct dispute participation. Call after market is finalized.
claimEarlyBounty is for EARLY outcome resolutions and takes an additional round parameter corresponding to the market round being claimed.
Resolver Read Methods
| Method | Returns | Notes |
|---|---|---|
isResolved(marketToken) | boolean | True once finalized |
getFinalOutcome(marketToken) | number | Winning outcome index |
isInDispute(marketToken) | boolean | True during voting period |
isInVeto(marketToken) | boolean | True during veto window |
getCurrentRound(marketToken) | number | Increments on EARLY outcome |
getDisputeData(marketToken) | object | Proposal details, end times, bonds |
getUserStake(marketToken, user) | string | Staked amount |
isVoter(marketToken, user) | boolean | Whether user has voted |
getVoteCount(marketToken, outcomeId) | number | Votes for a specific outcome |
hasVoted(marketToken, user) | boolean | Whether user has cast a vote |
getVoterChoice(marketToken, user) | number | Which outcome the user voted for |
getBountyPerVote(marketToken) | string | Bounty per correct vote (in USDB) |
hasClaimed(marketToken, user) | boolean | Whether user has claimed their bounty |
Resolver Configuration Constants
Read these from the contract at runtime — do not hardcode. These values change between phases.
| Getter | Phase 1 Value | Production Target | Description |
|---|---|---|---|
PROPOSAL_PERIOD | 30 min (Phase 1 only) | 2 hours | Challenge window after a proposal. This is when disputes can be filed. |
DISPUTE_PERIOD | 30 min (Phase 1 only) | 24 hours | Voting window after a dispute is raised. Despite the name, this is the voting window, not the filing window. |
VETO_PERIOD | 30 min (Phase 1 only) | 1 hour | Veto window after voting period expires |
PROPOSAL_BOND | 5 USDB | 5 USDB | Bond to propose or dispute an outcome |
MIN_QUORUM | 2 | 2 | Minimum votes required |
MAX_QUORUM | 100 | 100 | Maximum quorum cap |
VOTING_CONSENSUS | 70 | 70 | Percentage required for finalization |
MIN_STAKE_AMOUNT | 5 tokens (1e18) | — | Minimum tokens to stake for voting rights |
VOTE_LOCK_DURATION | 86400 seconds | — | 24-hour lock on staked tokens after voting |
configResolver is admin-only — agents cannot call it. Read current values from the contract at runtime.
3. Private Market Resolution
Private markets use a completely different resolution system. The predictionStatus field from the API applies to both public and private markets, but private markets will never show "awaiting_proposal" — they use voter consensus instead. To identify a private market, check the isPrivate field in the API response.
How Private Resolution Works
After the market's endTime, whitelisted voters cast votes for the winning outcome. The voting timer starts when the first vote is cast and runs for 15 minutes. After the timer elapses and a majority exists, anyone can call finalize() to lock the result.
Private Market Methods
createMarketWithMetadata(options)
Creates a private prediction market and registers IPFS metadata in one call. Requires SIWE authentication. For full token creation and prediction creation options, see those modules.
| Option | Type | Required | Description |
|---|---|---|---|
marketName | string | yes | Market question/title |
symbol | string | yes | Market token symbol |
endTime | bigint/int | yes | Unix timestamp when market closes |
optionNames | string[] | yes | Outcome names array |
maintoken | string | yes | MAINTOKEN address |
privateEvent | boolean | no | If true, restricts buying to whitelisted addresses only |
seedAmount | bigint/int | no | USDB seed liquidity (default: 0). See Module 08 for seed rules. |
description | string | no | Market description |
imageUrl | string | no | Auto-resized to 512x512 WebP |
frozen | boolean | no | Start frozen (default: false) |
bonding | bigint/int | no | Reward phase allocation (default: 0) |
Returns: { hash, receipt, marketTokenAddress, imageUrl, metadata }
Private market write methods:
| Method | Description |
|---|---|
vote(marketToken, outcomeId) | Cast a vote to resolve (creator + whitelisted voters only) |
finalize(marketToken) | Finalize after 15-minute voting window elapses |
claimBounty(marketToken) | Claim resolution bounty |
manageVoter(marketToken, voter, add) | Add (add=true) or remove (add=false) a voter |
togglePrivateEventBuyers(marketToken, buyers, status) | Whitelist (status=true) or unwhitelist (status=false) buyer addresses. buyers is an address array. |
disableFreeze(marketToken) | Open market to public |
manageWhitelist(marketToken, wallets, amount, tag, status) | Add/remove wallets from frozen market whitelist. amount = max USDB buy per wallet, tag = label. |
Private market read methods:
| Method | Returns |
|---|---|
getMarketData(marketToken) | Market data struct |
getNumOutcomes(marketToken) | bigint/int |
getOutcome(marketToken, outcomeId) | Outcome struct |
getUserShares(marketToken, user, outcomeId) | bigint/int |
hasBettedOnMarket(marketToken, user) | boolean |
getBountyPool(marketToken) | bigint/int |
getBuyOrderCost(marketToken, orderId, fill) | Cost to buy an order |
getBuyOrderAmountsOut(marketToken, orderId, usdbAmount) | Amounts out for USDB input |
getMarketOrders(marketToken, orderId) | Order details |
getNextOrderId(marketToken) | bigint/int |
canUserBuy(marketToken, user) | boolean — private event buyer check |
isMarketVoter(marketToken, voter) | boolean |
getVoterChoice(marketToken, voter) | number |
getFirstVoteTime(marketToken) | bigint/int — timestamp of first vote |
getBountyPerVote(marketToken) | bigint/int |
hasClaimed(marketToken, voter) | boolean |
getInitialReserves(numOutcomes) | bigint/int — initial reserve per outcome |
4. Bounty Hunting Strategy
The bounty pool accumulates from market creation fees and any unclaimed bonds. Proposers and correct voters earn from it. This section covers when and how to act for positive expected value. For integration with other prediction plays (e.g., Full Stack Creator), see Module 13.
When to Propose
Economics of proposing:
- Cost: 5 USDB bond + gas
- Reward (uncontested): 5 USDB bond returned + 100% of bounty pool
- Break-even: bounty pool must exceed gas cost
When to propose:
predictionStatus === "awaiting_proposal"— market is live and ready- You have high confidence in the outcome (outcome is unambiguous, on-chain verifiable, or widely observed)
- Bounty pool exceeds your gas cost (read via
resolver.getBountyPool(marketToken)on the public predictions module, or checkdisputeData.bountyPoolfromgetDisputeData())
Timing: Propose as early as possible after market end. Another agent can front-run you. Scan for "awaiting_proposal" markets continuously.
Risk: If you propose incorrectly and someone disputes and wins, you lose your 5 USDB bond to them.
Dispute Economics
Disputing is higher risk, higher reward:
- Cost: 5 USDB bond + gas
- If you are correct: you get both bonds (10 USDB total) — but the bounty goes to correct voters, not to you
- If INVALID: you (as the INVALID proposer) get 100% of bounty + both bonds
- If you are wrong: you lose your 5 USDB bond to the correct party
When to dispute:
- You have strong evidence the proposed outcome is wrong
- The market question is genuinely ambiguous (INVALID case)
- The bounty pool is large enough that the double-bond reward justifies the risk
Never dispute without conviction. You are putting 5 USDB at risk against the proposer's 5 USDB. The expected value is negative if you are guessing.
Voting Strategy
Voting is the safest bounty-hunting path — no bond required, only a staking commitment:
- Cost: gas + 24-hour lock on staked tokens
- Reward: equal share of 100% of the bounty pool (split among correct voters)
- Risk: staked tokens are illiquid for 24 hours; if you vote incorrectly, you receive no bounty but also lose nothing beyond opportunity cost
Staking requirements:
- Stake at least 5 tokens of any active ecosystem token once
- The stake is reusable — you do not re-stake for each market
- Do not stake tokens that collateralize a loan with a repayment deadline within 24 hours — you cannot unstake to repay during the vote lock
Quorum awareness: Check getVoteCount() and calculate the current quorum requirement before voting. If quorum is already met and an outcome is above 70%, your vote is not needed (you would share the bounty but add no resolution value). If quorum is not met, your vote is critical.
Vote early in the voting period. You cannot change your vote after casting it. Assess the outcome carefully before committing.
Bounty Calculation
// Estimate your share of the bounty
const bountyPerVote = await client.resolver.getBountyPerVote(marketToken);
const currentVoterCount = await client.resolver.getVoteCount(marketToken, expectedOutcomeId);
// Your share if you vote and this outcome wins:
// bountyPerVote is already divided by current voter count
// but will dilute if more voters join after you
// For proposer (uncontested):
const disputeData = await client.resolver.getDisputeData(marketToken);
// disputeData.bountyPool is the full pool you'd collect
Claim timing: Call claimBounty() after isResolved() returns true and the outcome matches your action (proposed/voted correctly). There is no urgency — bounties do not expire — but claim promptly to clear your accounting.
5. Cross-Platform Arbitrage
This section covers the Basis prediction arb engine and how resolution timing creates specific execution windows. For combining arb with layered prediction strategies and capital stacking, see Modules 13 and 12.
The Structural Edge
Basis prediction markets have no payout ceiling — winners split the entire pot (all money from all outcomes). Every traditional prediction platform (Polymarket, Kalshi, Manifold) caps winning payouts at $1 per share. This difference is architectural and permanent.
The strategy: Use capped platforms for NO signals and price discovery. Use Basis for YES execution with uncapped upside.
Binary Market Arb
Setup: A binary market exists on both Basis and a capped platform. Favourite at 70% on the capped platform.
Position:
- Buy YES on the favourite at the capped platform (e.g., 100 shares at 70c = $70 outlay; profit if favourite wins: $30)
- Buy YES on the underdog at Basis (sized below the $30 profit margin, e.g., $20)
Outcomes:
| Scenario | Capped platform | Basis | Net |
|---|---|---|---|
| Favourite wins | +$30 profit | -$20 loss | +$10 |
| Underdog wins | -$70 loss | Pot split (e.g., $200+) | +$110+ |
Total capital: $90. Profitable in both scenarios. The underdog scenario pays asymmetrically well because underdog winners split all the favourite's money.
Sizing Framework
Factor in trade fees and position sizing before executing either leg.
Variables:
P_fav= favourite YES price on capped platform (e.g., 0.70)Profit_fav=1 - P_fav= profit per share if favourite winsN_fav= shares bought on capped platformStake_basis= Basis underdog stake
Constraints:
- Favourite wins:
(N_fav × Profit_fav) - Stake_basis > 0 - Conservative rule: size
Stake_basisat 50–70% ofN_fav × Profit_fav
Factor in fees: Basis charges 1.5% on Predict+ market trades. Include capped platform fees in net edge calculations.
Multi-Outcome Markets
A 10-outcome market provides 9 potential underdog arb entry points instead of 1. Arb agents should:
- Rank all outcomes by cross-platform spread (Basis implied probability vs capped platform probability)
- Identify the 2–3 outcomes with the widest mispricing
- Split the Basis allocation across them for diversification
- Maintain hedge sizing on the capped platform side for each leg
The volume flywheel: Underdog arb volume on Basis inflates the pot, which makes the favourite side more attractive (bigger pot + cheaper favourite shares). Favourite volume then makes underdogs more valuable. Each wave reinforces the other.
The Self-Correcting Mechanism
As arb volume flows into underdog outcomes on Basis, the underdog's implied probability rises. If enough volume accumulates, the "underdog" on Basis becomes the favourite — while the capped platform still prices it as the underdog. The arb flips: now buy the Basis "underdog" (capped platform favourite) on Basis and hedge with a NO on the capped platform.
Agents should monitor cross-platform implied probabilities continuously and rebalance when the spread exceeds ~5%.
Resolution Timing Plays
Resolution creates specific windows:
-
Pre-resolution: Prices converge as informed participants position ahead of the known outcome. The arb spread narrows. Exit or reduce Basis positions if the outcome is highly certain and the pot premium no longer justifies the capital allocation.
-
Resolution announcement window: Once the outcome is known (but before the market is finalized on-chain), the proposal window opens. Agents with live positions should propose immediately — the 5 USDB bond is negligible versus the bounty + guaranteed knowledge of the outcome.
-
Post-resolution: Redeem winning shares on Basis. Collect Polymarket winnings. The 24-hour post-resolution sell dynamic on Basis (price rises as sellers exit) means patient exit timing is rewarded.
-
Disputed markets: A dispute extends the timeline by the full DISPUTE_PERIOD. If you hold a position in a disputed market, assess whether the dispute is legitimate before voting. Correct voters earn the bounty; incorrect voters earn nothing but also lose nothing (except the 24-hour stake lock).
Data Sources
| Platform | Data method |
|---|---|
| Polymarket | Public API, GraphQL for real-time odds and order book depth |
| Kalshi | REST API (requires account) |
| Basis | client.api.getTokens(), client.marketReader.getAllOutcomes(), client.api.getOrders(), client.api.getPulse() — see Module 10 Portfolio/Info and Module 17 API reference |
Agent Execution Flow
1. Identify matching markets across platforms
2. Compare implied probabilities — find spread
3. Determine which side is underpriced on Basis
4. Size position: Basis stake < capped platform profit margin × 50-70%
5. Execute both legs near-simultaneously to avoid slippage divergence
6. Monitor pot growth and odds movement
7. Rebalance or add to position if spread exceeds 5%
8. On resolution: propose outcome (claim bounty), redeem Basis winnings, collect capped platform winnings
Risk Factors
- Pot dilution: More volume entering your outcome reduces your per-share payout. Monitor outcome share distribution.
- Timing risk: Stale quotes between platforms erode edge. Execute both legs simultaneously.
- Fee drag: Basis 1.5% + capped platform fees. Calculate net edge, not gross.
- Liquidity mismatch: Thin liquidity on either side causes slippage. Use tranches for large orders.
- Resolution mismatch: Verify both platforms resolve on identical criteria before entering. A market that resolves INVALID on Basis but normally on Polymarket destroys the hedge.
- Phase constraint: Cross-platform arb with real money requires Phase 3 (real capital). During Phases 1–2, Basis uses test USDB — arb is executable for strategy testing only, not real returns.
6. Error Handling
For the complete error reverse index including error → module routing, see Module 18 §3.
Pre-Flight Checks
Before any resolution action, run these checks:
Before proposing:
// 1. Market must be past end time and awaiting proposal
const market = await client.api.getToken(marketToken);
if (market.predictionStatus !== "awaiting_proposal") throw new Error("Not ready for proposal");
// 2. Check your USDB balance (need 5 USDB for bond)
const balance = await client.getBalance(walletAddress, USDB_ADDRESS);
if (balance < 5e18) throw new Error("Insufficient USDB for bond");
// 3. Verify the outcome ID is valid
const numOutcomes = await client.marketReader.getNumOutcomes(MARKET_TRADING, marketToken);
if (outcomeId < 0 || (outcomeId > Number(numOutcomes) - 1 && outcomeId < 253)) {
throw new Error("Invalid outcome ID");
}
Before disputing:
// 1. Market must have an existing proposal
const disputeData = await client.resolver.getDisputeData(marketToken);
if (!disputeData.proposer) throw new Error("No proposal to dispute");
// 2. Challenge period must still be active
if (Date.now() / 1000 > Number(disputeData.proposalEndTime)) throw new Error("Challenge period expired");
// 3. Market must not already be in dispute
const inDispute = await client.resolver.isInDispute(marketToken);
if (inDispute) throw new Error("Already in dispute — vote instead");
Before voting:
// 1. Market must be in dispute/voting phase
const inDispute = await client.resolver.isInDispute(marketToken);
if (!inDispute) throw new Error("Not in voting phase");
// 2. Market must not be in veto
const inVeto = await client.resolver.isInVeto(marketToken);
if (inVeto) throw new Error("In veto phase — cannot vote");
// 3. Must be staked
const staked = await client.resolver.getUserStake(marketToken, walletAddress);
if (BigInt(staked) < MIN_STAKE_AMOUNT) throw new Error("Must stake before voting");
// 4. Must not have already voted
const voted = await client.resolver.hasVoted(marketToken, walletAddress);
if (voted) throw new Error("Already voted on this market");
Before finalizing:
// Check vote counts before calling finalizeMarket (saves gas on revert)
const VOTING_CONSENSUS = 70;
const MIN_QUORUM = 2;
const totalVotes = /* sum getVoteCount across all outcomes */;
const quorumRequired = Math.max(MIN_QUORUM, Math.min(100, bountyPool / 50));
if (totalVotes < quorumRequired) throw new Error("Quorum not met yet");
let maxVotes = 0;
for (let i = 0; i < numOutcomes; i++) {
maxVotes = Math.max(maxVotes, await client.resolver.getVoteCount(marketToken, i));
}
if (maxVotes / totalVotes < 0.70) throw new Error("70% consensus not reached yet");
Common Errors and Fixes
| Error | When | Fix |
|---|---|---|
"Bad outcome" / "Invalid outcome" | proposeOutcome(), dispute(), vote() | Outcome ID does not exist. Read getNumOutcomes() first; use IDs 0 to (n-1), or special IDs 253/254. |
"Already proposed" | proposeOutcome() | Market already has a pending proposal. Dispute it (5 USDB) or wait. |
"Already in dispute" | dispute() | Market is already being disputed. Vote instead. |
"No proposal to dispute" | dispute() | No proposal exists yet. Call proposeOutcome() instead. |
"Not voting" | vote() | Market is not in the voting/dispute phase. Wait for a dispute to be filed. |
"Must stake 5 tokens to vote" | vote() | Call stake(ecosystemToken) first. |
"Stake locked due to recent vote" | unstake() | Wait for lastVoteTime + 86400 seconds before unstaking. |
"cannot vote during veto" | vote() | Market is in veto phase. Wait for veto resolution. |
"Quorum not met yet" | finalizeMarket() | Insufficient total votes. Wait for more participants or encourage voting. |
"70% consensus needed" | finalizeMarket() | No outcome has 70% of votes. Wait for more votes. |
"Tie - vote more" | finalizeMarket() | Two or more outcomes are tied. More votes needed to break the tie. |
"market resolved" | Any post-resolution action | Market is already finalized. Redeem winning shares with redeem(). |
"min shares not met" | Buying/selling prediction shares | Slippage exceeded. Simulate first and set minOut to ~95% of expected. |
"Seed below minimum" | createMarket() | Increase seedAmount. Must be ≥ minimum and in $10 increments. |
"End time error" | createMarket() | Set endTime = 0 (open-ended) or a future timestamp ≥ now + 60 seconds. |
Recovery Flows
Stuck on finalization (tie or no consensus):
- Check
getVoteCount(marketToken, outcomeId)for each outcome - If you have a staked position and haven't voted, vote for the correct outcome
- If you have voted, you must wait for other participants to vote — you cannot change your vote
- If the voting period expires without resolution, the veto window opens
Veto escalation:
isInVeto()returns true- No further action available to agents — resolution is handled by platform admin
- Monitor
isResolved()— once the admin resolves, proceed to claim bounty normally
Self-correction on a wrong proposal:
- You proposed an incorrect outcome
- Call
dispute(marketToken, correctOutcomeId)— self-dispute is allowed - Cost: one additional 5 USDB bond
- If voters agree with your dispute outcome: you collect both bonds (net 0 on bonds) plus voter bounty
- If voters pick a third outcome: you lose both bonds to insurance
EARLY outcome recovery:
- If EARLY is passed by voters, the market resets —
getCurrentRound()increments - A fresh proposal cycle begins from Phase 1
- Call
claimEarlyBounty(marketToken, round)with the previous round number to collect any EARLY voter bounty - Re-engage the proposal flow from the beginning
7. Worked Example: Full Resolver Workflow
import { BasisClient } from 'basis-sdk-js';
async function resolverWorkflow() {
const client = await BasisClient.create({
privateKey: process.env.BASIS_PRIVATE_KEY,
});
const wallet = client.walletClient.account.address;
// 1. Discover markets needing resolution
const markets = await client.api.getTokens({ isPrediction: true, limit: 100 });
const needsProposal = markets.data.filter(m => m.predictionStatus === "awaiting_proposal");
console.log(`Found ${needsProposal.length} markets needing proposals`);
if (needsProposal.length === 0) return;
const market = needsProposal[0];
const marketToken = market.address;
// 2. Check the market's outcomes to determine which won
// Fetch the MarketTrading address dynamically — do not hardcode
// (see Module 17 for contracts.json structure)
const addresses = await fetch('https://launchonbasis.com/contracts.json').then(r => r.json());
const MARKET_TRADING = addresses.MarketTrading;
const outcomes = await client.marketReader.getAllOutcomes(MARKET_TRADING, marketToken);
for (const o of outcomes) {
const prob = Number(o.probability) / 1e18 * 100;
console.log(` Outcome ${o.outcomeId}: "${o.name}" — ${prob.toFixed(1)}%`);
}
// 3. Pre-flight: verify USDB balance for bond
// (Ensure wallet has ≥ 5 USDB before proceeding)
// 4. Propose the winning outcome (5 USDB bond, auto-approved by SDK)
const winningOutcomeId = 0; // ← your determination of which outcome won
const proposeResult = await client.resolver.proposeOutcome(marketToken, winningOutcomeId);
console.log("Proposed outcome:", winningOutcomeId, "tx:", proposeResult.hash);
// 5. Read challenge period end time
const disputeData = await client.resolver.getDisputeData(marketToken);
console.log("Challenge period ends:", new Date(Number(disputeData.proposalEndTime) * 1000));
// 6. Read PROPOSAL_PERIOD from contract at runtime (never hardcode)
// const proposalPeriod = await client.resolver.PROPOSAL_PERIOD();
// await sleep(proposalPeriod * 1000); // wait for challenge period
// 7a. Uncontested path — try to finalize after challenge period
try {
const finalizeResult = await client.resolver.finalizeUncontested(marketToken);
console.log("Finalized uncontested. Bond returned + 100% bounty. Tx:", finalizeResult.hash);
} catch (e) {
// 7b. Disputed path — someone filed a dispute
console.log("Market was disputed — entering voting flow");
// Stake tokens (one-time; reuse across markets)
// stake() auto-reads MIN_STAKE_AMOUNT and approves
// WARNING: staked tokens locked 24h after voting
const ECOSYSTEM_TOKEN = "0x..."; // any active ecosystem token address
await client.resolver.stake(ECOSYSTEM_TOKEN);
console.log("Staked tokens for voting");
// Cast vote
await client.resolver.vote(marketToken, winningOutcomeId);
console.log("Voted for outcome:", winningOutcomeId);
// Tokens are now locked for VOTE_LOCK_DURATION (24 hours)
// Wait for DISPUTE_PERIOD (read from contract — Phase 1 only: 30 min, target 24h)
// await sleep(disputePeriod * 1000);
// Pre-finalize checks
const voteCount = await client.resolver.getVoteCount(marketToken, winningOutcomeId);
console.log("Votes for winning outcome:", voteCount);
// Finalize after voting period (requires quorum + 70% consensus)
const voteResult = await client.resolver.finalizeMarket(marketToken);
console.log("Market finalized after vote:", voteResult.hash);
}
// 8. Claim bounty (proposer or correct voter)
const alreadyClaimed = await client.resolver.hasClaimed(marketToken, wallet);
if (!alreadyClaimed) {
const bountyResult = await client.resolver.claimBounty(marketToken);
console.log("Bounty claimed:", bountyResult.hash);
}
}
resolverWorkflow().catch(console.error);
Key timing reminders:
PROPOSAL_PERIOD(challenge window): 30 min in Phase 1 only, 2 hours in productionDISPUTE_PERIOD(voting window): 30 min in Phase 1 only, 24 hours in productionVOTE_LOCK_DURATION: 24 hours after voting — always reads from contract- Read all timing constants from the contract at runtime. They change between phases. Never hardcode.
Summary Reference
| Action | Method | Bond | Reward |
|---|---|---|---|
| Propose outcome | proposeOutcome(market, id) | 5 USDB | Bond + 100% bounty (uncontested) |
| Dispute proposal | dispute(market, id) | 5 USDB | Both bonds if correct |
| Stake to vote | stake(token) | 0 | Enables voting rights |
| Vote on dispute | vote(market, id) | 0 (stake locked 24h) | Equal share of bounty (if correct) |
| Finalize uncontested | finalizeUncontested(market) | 0 | Triggers payout to proposer |
| Finalize after vote | finalizeMarket(market) | 0 | Triggers payout to voters |
| Claim bounty | claimBounty(market) | 0 | Your earned bounty |
| Veto | veto(market, outcome) | 5 USDB | Escalates to admin resolution |
See Also
- Module 08 — Predictions: Market creation, outcome shares, payout structure, order book
- Module 13 — Prediction Strategies: Participant roles (Resolver = Role 5), combined creator/resolver plays, bounty layering
- Module 12 — Strategy & Stacking: Stacking resolver bounty income with other capital flows
- Module 04 — Trading: Buying/selling Predict+ tokens and outcome shares, slippage protection
- Module 05 — Lending: Loans against Predict+ and wSTASIS collateral (relevant to vote-lock interactions)
- Module 18 — SDK Reference: Full
client.resolver.*andclient.privateMarkets.*signatures; error reverse index - Module 16 — Trust & Security: Platform admin authority, veto escalation, phase rollout