Skip to main content
SolBid enforces strict bidding rules through smart contract validation to ensure fair gameplay. Every bid must meet specific requirements or it will be rejected by the blockchain.

Minimum bid requirements

Initial bid (creating a game)

To create a new game, you must place an initial bid of at least:
Minimum: 0.014 SOL (14,000,000 lamports)
This minimum ensures the prize pool starts at a meaningful amount. The initial bid becomes the first entry in the prize pool and sets the baseline for subsequent bids.
Code reference: The validation happens in create_game.rs:35:
if initial_bid_amount < 14_000_000 {
  return Err(BiddingError::InsufficientInitialBid.into());
}

Subsequent bids

Every bid after the initial bid must be exactly 2x the current highest bid:
Required bid = Current highest bid × 2
Example progression:
  • Bid 1: 0.014 SOL (initial)
  • Bid 2: 0.028 SOL (2x previous)
  • Bid 3: 0.056 SOL (2x previous)
  • Bid 4: 0.112 SOL (2x previous)
  • Bid 5: 0.224 SOL (2x previous)
Bids that are lower than 2x the current highest bid will fail. The smart contract validates this requirement at place_bid.rs:46:
if bid_amount < 2 * game_state.highest_bid {
  return Err(BiddingError::InsufficientBidAmount.into());
}

Timing rules

Game timeout period

The game has a fixed timeout period:
Timeout: 10 minutes (600 seconds)
This timer resets to the full 10 minutes with each new bid.

Timer mechanics

1

Bid is placed

When you successfully place a bid, the smart contract records the current blockchain timestamp as last_bid_time.
2

Timer starts counting

The 10-minute countdown begins from the moment your bid is confirmed on-chain.
3

Next bid resets timer

If another player bids before the 10 minutes expire, the timer completely resets to 10 minutes from their bid time.
4

Timer expires

If 10 minutes pass without a new bid, the game ends automatically. The next transaction that interacts with the game triggers the prize distribution.
Code reference: Timer validation at place_bid.rs:40-44:
let current_time = Clock::get()?.unix_timestamp as u64;

if current_time - game_state.last_bid_time > 600 {
  return end_game(program_id, game_state.game_id, accounts, &mut game_state);
}
The timer is enforced by blockchain timestamps, not client-side timers. This means you cannot manipulate the timing by adjusting your local clock.

Bid validation

Bid count synchronization

Each bid must include the correct bid count to prevent race conditions:
Expected bid count = Current total_bids + 1
If your bid count doesn’t match, it means another player submitted a bid first, and your transaction will fail:
if bid_count != new_bid_count {
  return Err(BiddingError::BidCountMismatch.into());
}
This validation prevents double-bidding and ensures bids are processed in the correct order. If your transaction fails due to a bid count mismatch, refresh the game state and try again with the updated bid count.

Game state validation

Before accepting a bid, the smart contract verifies:
  1. Game is active: The game_ended flag must be false
  2. Timer hasn’t expired: Current time minus last bid time must be ≤ 600 seconds
  3. Bid amount is correct: Must be exactly 2x the current highest bid
  4. Bid count matches: Your submitted count must match expected count
Code reference: Game ended check at place_bid.rs:36-38:
if game_state.game_ended {
  return Err(BiddingError::GameEnded.into());
}

Account requirements

Player accounts

Each bid creates a new player account (PDA) with:
  • Seeds: ["player", game_id, bidder_pubkey, bid_count]
  • Size: 32 bytes
  • Rent: Automatically calculated and paid by bidder

Bid accounts

Each bid also creates a bid record account (PDA) with:
  • Seeds: ["bid", game_id, bid_count]
  • Size: 48 bytes
  • Rent: Automatically calculated and paid by bidder
The rent for these accounts is typically very small (less than 0.002 SOL total) and is automatically included in your transaction cost.

Transaction costs

When placing a bid, you pay:
  1. Bid amount: The actual bid (2x current highest)
  2. Account rent: For player and bid PDAs (~0.002 SOL)
  3. Transaction fee: Standard Solana network fee (~0.000005 SOL)
Total cost = Bid amount + Rent + Network fee

Example calculation

Bid amount:        0.056 SOL
Account rent:      0.002 SOL
Network fee:       0.000005 SOL
─────────────────────────────
Total cost:        0.058005 SOL

Edge cases and error handling

Common bid rejection reasons

Your bid was less than 2x the current highest bid. Check the current game state and submit the correct amount.
The game has already ended and is no longer accepting bids. This can happen if the timer expired between when you loaded the page and submitted your bid.
Another player submitted a bid before yours. Refresh the game state to get the updated bid count and highest bid amount.
The derived PDA addresses don’t match expected values. This is usually a client-side error in how the PDAs were calculated.

Handling failed bids

If your bid transaction fails:
1

Check the error message

The blockchain will return a specific error indicating why the bid was rejected.
2

Refresh game state

Fetch the latest game data including current highest bid and bid count.
3

Recalculate bid amount

Ensure your new bid is exactly 2x the current (updated) highest bid.
4

Retry transaction

Submit the corrected bid with updated parameters.

Best practices

Before placing a bid

  • ✅ Verify the game is still active
  • ✅ Check the time remaining on the timer
  • ✅ Confirm you have enough SOL for bid + fees
  • ✅ Calculate the exact 2x bid amount
  • ✅ Verify the current bid count

During high competition

  • ✅ Expect bid count mismatches if multiple players are active
  • ✅ Have retry logic ready in your client
  • ✅ Monitor the timer closely near expiration
  • ✅ Account for blockchain confirmation time (~400-800ms)

Transaction optimization

  • ✅ Use skipPreflight: true for faster submission in competitive situations
  • ✅ Set maxRetries appropriately (recommended: 3-5)
  • ✅ Request recent blockhash immediately before sending
Skipping preflight checks means failed transactions will still consume fees. Only use this option when speed is critical and you’re confident in your transaction validity.

Next steps

Prize distribution

Learn how the prize pool is distributed when the game ends

How to play

Review the complete gameplay flow

Build docs developers (and LLMs) love