Create a Bet

Initialize a new prediction market for users to wager on.

What You'll Do

Create a new onchain bet (prediction market) with a question and parameters.

Prerequisites

  • SDK initialized with a wallet
  • Program user account created
  • Wallet funded with SOL and USDC

Create a Bet

Use the initializeBetV2 method to create a new bet:

typescript
import { SDK } from "@solworks/poll-sdk";

const txHash = await sdk.initializeBetV2({
  question: "Will Bitcoin hit $100,000 by end of Q1 2026?",
  expectedUserCount: 10,
  minimumVoteCount: 2,
  isCreatorResolver: false,
  signers: [wallet],
});

console.log("Bet created:", txHash);

Parameters Reference

ParameterTypeConstraintsDescription
questionstringMax 256 charactersThe betting question
expectedUserCountnumber1-50Maximum participants allowed
minimumVoteCountnumber1 to expectedUserCountVotes needed to resolve
isCreatorResolverboolean-Resolution mode
signersKeypair[]RequiredTransaction signers

Parameter Details

question (max 256 characters)

The prediction market question. This is stored onchain and cannot be changed after creation.

Constraints:

  • Maximum 256 characters (enforced onchain)
  • Cannot be empty
  • Should be unambiguous with clear resolution criteria

Good examples:

  • "Will BTC be above $100,000 on March 31, 2026 at 00:00 UTC?"
  • "Will the Lakers win the 2026 NBA Championship?"

Bad examples:

  • "BTC moon?" (too vague)
  • "Will crypto do well?" (no clear resolution criteria)

expectedUserCount (1-50)

The maximum number of participants who can place wagers on this bet.

Constraints:

  • Minimum: 1
  • Maximum: 50
  • Once set, cannot be changed

How it works:

  • Each user can place multiple wagers, but each wager counts toward this limit
  • When the wager count reaches expectedUserCount, no more wagers are accepted
  • Set this based on your expected audience size

Recommendations:

  • Small private bets: 2-5
  • Group bets: 10-20
  • Public markets: 50

minimumVoteCount (1 to expectedUserCount)

The minimum number of votes required to reach consensus and resolve the bet.

Constraints:

  • Minimum: 1
  • Maximum: must be ≤ expectedUserCount
  • Cannot be 0

How resolution works:

1. Any participant can initiate voting

2. All participants with wagers can cast one vote each

3. Once minimumVoteCount votes are cast AND there's a majority, the bet resolves

4. If votes are split (no majority), more votes are needed

Example scenarios:

expectedUserCountminimumVoteCountResolution
102Resolves when 2+ votes agree on outcome
106Requires 6+ votes with majority to resolve
5025Requires 25+ votes with majority

Recommendations:

  • Set to at least 2 for fairness
  • Higher values = more democratic but slower resolution
  • Consider: Math.ceil(expectedUserCount / 2) for majority requirement

isCreatorResolver

Determines who can resolve the bet outcome.

When false (Community Resolution):

  • All participants vote on the outcome
  • Bet resolves when minimumVoteCount is reached with consensus
  • More decentralized and trustless
  • Recommended for public markets

When true (Creator Resolution):

  • The bet creator can directly set the outcome
  • No community voting required
  • Faster resolution
  • Requires trust in the creator
  • Good for private bets or trusted oracles
typescript
// Community-resolved bet (democratic)
await sdk.initializeBetV2({
  question: "Will Team A win the championship?",
  expectedUserCount: 20,
  minimumVoteCount: 10,
  isCreatorResolver: false, // Community votes
  signers: [wallet],
});

// Creator-resolved bet (trusted oracle)
await sdk.initializeBetV2({
  question: "What will be the final score?",
  expectedUserCount: 50,
  minimumVoteCount: 1,
  isCreatorResolver: true, // Creator decides
  signers: [wallet],
});

On-Chain Limits

LimitValue
Question length256 characters
Max participants50
Max wagers per bet50
Max votes per bet50

Get Bet Address

After creating, derive the bet address:

typescript
const userAddress = sdk.addresses.user.get(wallet.publicKey);
const userAccount = await sdk.accounts.user.single(userAddress);

// wagerId is assigned sequentially per user
const wagerId = userAccount.nextWagerId - 1;
const betAddress = sdk.addresses.betV2.get(wagerId, wallet.publicKey);

console.log("Bet Address:", betAddress.toBase58());

Saving the Bet Address

The bet address is a Program Derived Address (PDA) that uniquely identifies your bet onchain. You must save this address to interact with the bet later.

typescript
// After creating a bet, save the address
const result = await sdk.initializeBetV2({
  question: "Will BTC hit $100k?",
  expectedUserCount: 10,
  minimumVoteCount: 2,
  isCreatorResolver: false,
  signers: [wallet],
});

// The result contains the bet address
const betAddress = result.bet;

// Save to your database
await db.bets.create({
  address: betAddress.toBase58(),
  question: "Will BTC hit $100k?",
  creatorWallet: wallet.publicKey.toBase58(),
  createdAt: new Date(),
});

Why save the address?

  • The address is required to place wagers, initiate voting, cast votes, and settle
  • Users need the address to join and interact with the bet
  • You cannot derive the address without knowing the wagerId and creator

What to store:

FieldPurpose
addressThe bet's onchain address (base58 string)
questionFor display (also stored onchain)
creatorWalletTo verify ownership
wagerIdTo re-derive the address if needed
createdAtFor sorting/filtering

Re-deriving the Address

If you have the wagerId and creator wallet, you can re-derive the address:

typescript
// Re-derive from stored wagerId and creator
const betAddress = sdk.addresses.betV2.get(
  storedWagerId,
  new PublicKey(storedCreatorWallet)
);

Fetch Bet Details

Retrieve the bet account data to check its current state:

typescript
const bet = await sdk.accounts.betV2.single(betAddress);

console.log("Bet Details:");
console.log("- Question:", bet.question);
console.log("- Status:", bet.status);
console.log("- Creator Resolver:", bet.isCreatorResolver);
console.log("- Expected Users:", bet.expectedUserCount);
console.log("- Min Votes:", bet.minimumVoteCount);
console.log("- Total For:", bet.totalOiFor.toNumber() / 1e6, "USDC");
console.log("- Total Against:", bet.totalOiAgainst.toNumber() / 1e6, "USDC");
console.log("- Wager Count:", bet.wagers.length);

Tracking Bet State Over Time

Poll the bet account to track changes and update your UI:

typescript
import { MarketStatus } from "@solworks/poll-sdk";

async function trackBet(betAddress: PublicKey) {
  const bet = await sdk.accounts.betV2.single(betAddress);
  
  // Check current status
  switch (bet.status) {
    case MarketStatus.Pending:
      console.log("Accepting wagers");
      console.log("Wagers:", bet.wagers.length);
      break;
      
    case MarketStatus.Resolving:
      console.log("Voting in progress");
      console.log("Votes:", bet.votes.length, "/", bet.minimumVoteCount);
      break;
      
    case MarketStatus.Resolved:
      console.log("Outcome:", bet.resolvedOutcome);
      console.log("Ready for settlement");
      break;
      
    case MarketStatus.Distributed:
      console.log("Settlement complete");
      break;
  }
  
  return bet;
}

// Poll for updates (e.g., every 5 seconds)
setInterval(async () => {
  const bet = await trackBet(betAddress);
  updateUI(bet);
}, 5000);

Key fields to monitor:

FieldWhat it tells you
statusCurrent lifecycle stage
wagers.lengthNumber of participants
totalOiFor / totalOiAgainstPool sizes per side
votes.lengthVotes cast during resolution
resolvedOutcomeFinal outcome (after resolution)
updatedAtLast onchain update timestamp

Bet Account Structure

The bet account includes:

typescript
interface BetAccount {
  creator: PublicKey;
  createdAt: BN;
  updatedAt: BN;
  status: MarketStatus;
  resolvedOutcome: Outcome;
  question: string;           // Max 256 chars
  expectedUserCount: number;  // 1-50
  minimumVoteCount: number;   // 1 to expectedUserCount
  isCreatorResolver: boolean; // Resolution mode
  totalOiFor: BN;             // Total "For" wagers in lamports
  totalOiAgainst: BN;         // Total "Against" wagers in lamports
  poolAddress: PublicKey;     // Escrow holding USDC
  wagers: Wager[];            // Max 50 wagers
  votes: Vote[];              // Max 50 votes
  wagerId: BN;
}

Error Cases

ErrorCause
QuestionIsEmptyQuestion string is empty
ExpectedUserCountIsZeroexpectedUserCount is 0
ExpectedUserCountIsGreaterThanFiveexpectedUserCount > 50
MinimumVoteCountIsZerominimumVoteCount is 0
MinimumVoteCountIsGreaterThanExpectedUserCountminimumVoteCount > expectedUserCount

Best Practices

  • Clear questions: Write unambiguous questions with clear resolution criteria and dates
  • Appropriate participant count: Set expectedUserCount based on your expected audience
  • Balanced vote requirements: Set minimumVoteCount to ensure fair resolution
  • Choose resolution mode wisely: Use isCreatorResolver: true only when you have a trusted oracle

What Happens

When you create a bet:

1. Bet account is created at a derived PDA

2. Pool (escrow) account is created for holding wagers

3. Bet status is set to "Pending"

4. Creator is recorded as the bet owner

5. User's nextWagerId is incremented

Next Steps

Your bet is live and accepting wagers. Continue to learn how to place wagers.