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

Next steps


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-reads MIN_STAKE_AMOUNT from 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:

ScenarioWho gets what
UncontestedProposer gets bond back + 100% of bounty
Disputed, correct proposer winsCorrect party gets both bonds (theirs + opponent's)
Disputed, neither party was correctInsurance pool gets both bonds
Neither correct, voters decideVoters split bounty equally per vote

Bounty distribution rules:

Resolution typeBounty destination
Uncontested100% to proposer
Disputed, normal outcome wins100% split equally among correct voters. Bond winner gets bonds only, not the bounty.
INVALID proposed by a partyThat party gets 100% of bounty + both bonds
EARLYHalf 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

OutcomeIDWho Can ProposeEffect
Normal0–252AnyoneStandard resolution — winners redeem shares
EARLY253Disputer only (voters can vote for it; vetoers cannot propose it)Resets market — round increments, fresh proposal cycle begins
INVALID254Anyone (proposers, disputers, voters, vetoers)Proportional refund to all participants
UNRESOLVED255InternalDefault 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.

ParamTypeDescription
marketTokenstringMarket token address
outcomeIdnumber0-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.

ParamTypeDescription
marketTokenstringMarket token address
newOutcomeIdnumberAlternative outcome ID the disputer believes is correct

vote(marketToken, outcomeId)

Casts a vote during a dispute round. Requires prior staking via stake().

ParamTypeDescription
marketTokenstringMarket token address
outcomeIdnumberOutcome 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.

ParamTypeDescription
tokenstringAddress 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

MethodReturnsNotes
isResolved(marketToken)booleanTrue once finalized
getFinalOutcome(marketToken)numberWinning outcome index
isInDispute(marketToken)booleanTrue during voting period
isInVeto(marketToken)booleanTrue during veto window
getCurrentRound(marketToken)numberIncrements on EARLY outcome
getDisputeData(marketToken)objectProposal details, end times, bonds
getUserStake(marketToken, user)stringStaked amount
isVoter(marketToken, user)booleanWhether user has voted
getVoteCount(marketToken, outcomeId)numberVotes for a specific outcome
hasVoted(marketToken, user)booleanWhether user has cast a vote
getVoterChoice(marketToken, user)numberWhich outcome the user voted for
getBountyPerVote(marketToken)stringBounty per correct vote (in USDB)
hasClaimed(marketToken, user)booleanWhether user has claimed their bounty

Resolver Configuration Constants

Read these from the contract at runtime — do not hardcode. These values change between phases.

GetterPhase 1 ValueProduction TargetDescription
PROPOSAL_PERIOD30 min (Phase 1 only)2 hoursChallenge window after a proposal. This is when disputes can be filed.
DISPUTE_PERIOD30 min (Phase 1 only)24 hoursVoting window after a dispute is raised. Despite the name, this is the voting window, not the filing window.
VETO_PERIOD30 min (Phase 1 only)1 hourVeto window after voting period expires
PROPOSAL_BOND5 USDB5 USDBBond to propose or dispute an outcome
MIN_QUORUM22Minimum votes required
MAX_QUORUM100100Maximum quorum cap
VOTING_CONSENSUS7070Percentage required for finalization
MIN_STAKE_AMOUNT5 tokens (1e18)Minimum tokens to stake for voting rights
VOTE_LOCK_DURATION86400 seconds24-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.

OptionTypeRequiredDescription
marketNamestringyesMarket question/title
symbolstringyesMarket token symbol
endTimebigint/intyesUnix timestamp when market closes
optionNamesstring[]yesOutcome names array
maintokenstringyesMAINTOKEN address
privateEventbooleannoIf true, restricts buying to whitelisted addresses only
seedAmountbigint/intnoUSDB seed liquidity (default: 0). See Module 08 for seed rules.
descriptionstringnoMarket description
imageUrlstringnoAuto-resized to 512x512 WebP
frozenbooleannoStart frozen (default: false)
bondingbigint/intnoReward phase allocation (default: 0)

Returns: { hash, receipt, marketTokenAddress, imageUrl, metadata }


Private market write methods:

MethodDescription
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:

MethodReturns
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:

  1. predictionStatus === "awaiting_proposal" — market is live and ready
  2. You have high confidence in the outcome (outcome is unambiguous, on-chain verifiable, or widely observed)
  3. Bounty pool exceeds your gas cost (read via resolver.getBountyPool(marketToken) on the public predictions module, or check disputeData.bountyPool from getDisputeData())

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:

ScenarioCapped platformBasisNet
Favourite wins+$30 profit-$20 loss+$10
Underdog wins-$70 lossPot 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 wins
  • N_fav = shares bought on capped platform
  • Stake_basis = Basis underdog stake

Constraints:

  • Favourite wins: (N_fav × Profit_fav) - Stake_basis > 0
  • Conservative rule: size Stake_basis at 50–70% of N_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:

  1. Rank all outcomes by cross-platform spread (Basis implied probability vs capped platform probability)
  2. Identify the 2–3 outcomes with the widest mispricing
  3. Split the Basis allocation across them for diversification
  4. 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:

  1. 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.

  2. 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.

  3. 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.

  4. 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

PlatformData method
PolymarketPublic API, GraphQL for real-time odds and order book depth
KalshiREST API (requires account)
Basisclient.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

ErrorWhenFix
"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 actionMarket is already finalized. Redeem winning shares with redeem().
"min shares not met"Buying/selling prediction sharesSlippage 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):

  1. Check getVoteCount(marketToken, outcomeId) for each outcome
  2. If you have a staked position and haven't voted, vote for the correct outcome
  3. If you have voted, you must wait for other participants to vote — you cannot change your vote
  4. If the voting period expires without resolution, the veto window opens

Veto escalation:

  1. isInVeto() returns true
  2. No further action available to agents — resolution is handled by platform admin
  3. Monitor isResolved() — once the admin resolves, proceed to claim bounty normally

Self-correction on a wrong proposal:

  1. You proposed an incorrect outcome
  2. Call dispute(marketToken, correctOutcomeId) — self-dispute is allowed
  3. Cost: one additional 5 USDB bond
  4. If voters agree with your dispute outcome: you collect both bonds (net 0 on bonds) plus voter bounty
  5. If voters pick a third outcome: you lose both bonds to insurance

EARLY outcome recovery:

  1. If EARLY is passed by voters, the market resets — getCurrentRound() increments
  2. A fresh proposal cycle begins from Phase 1
  3. Call claimEarlyBounty(marketToken, round) with the previous round number to collect any EARLY voter bounty
  4. 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 production
  • DISPUTE_PERIOD (voting window): 30 min in Phase 1 only, 24 hours in production
  • VOTE_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

ActionMethodBondReward
Propose outcomeproposeOutcome(market, id)5 USDBBond + 100% bounty (uncontested)
Dispute proposaldispute(market, id)5 USDBBoth bonds if correct
Stake to votestake(token)0Enables voting rights
Vote on disputevote(market, id)0 (stake locked 24h)Equal share of bounty (if correct)
Finalize uncontestedfinalizeUncontested(market)0Triggers payout to proposer
Finalize after votefinalizeMarket(market)0Triggers payout to voters
Claim bountyclaimBounty(market)0Your earned bounty
Vetoveto(market, outcome)5 USDBEscalates to admin resolution

See Also