Skip to main content
The SolBid program uses three main account types to store game state, player information, and bid history.

Account overview

All state accounts use Borsh serialization for efficient on-chain storage:
Account TypeSizePurpose
GameState96 bytesStores game configuration and current state
PlayerState32 bytesTracks individual player statistics per game
Bid48 bytesRecords individual bid details
See state.rs:32-34 for size constants.

GameState

Stores the complete state of a bidding game. One GameState account exists per game.

Structure

pub struct GameState {
    pub game_id: u64,
    pub initial_bid_amount: u64,
    pub highest_bid: u64,
    pub last_bid_time: u64,
    pub total_bids: u64,
    pub last_bidder: Pubkey,
    pub prize_pool: u64,
    pub platform_fee_percentage: u64,
    pub game_ended: bool,
}
See state.rs:4-15 for the struct definition.

Fields

game_id
u64
Unique identifier for this game. Used as a seed for PDA derivation.
initial_bid_amount
u64
The first bid amount in lamports. Must be at least 14,000,000 lamports (0.014 SOL). This value is set at game creation and never changes.
highest_bid
u64
Current highest bid amount in lamports. Updated each time a new bid is placed. Initially set to initial_bid_amount.
last_bid_time
u64
Unix timestamp of the most recent bid. Used to enforce the 10-minute (600 second) timeout rule. Games automatically end if no bid is placed within 600 seconds.
total_bids
u64
Total number of bids placed in this game. Starts at 1 (initial bid) and increments with each PlaceBid instruction. Used for PDA derivation and race condition prevention.
last_bidder
Pubkey
Public key of the most recent bidder. This player will win the game if no one outbids them within 10 minutes.
prize_pool
u64
Total SOL accumulated in the game (sum of all bids). Distributed to winners and royalty recipients when the game ends.
platform_fee_percentage
u64
Percentage of prize pool taken as platform fee. Currently hardcoded to 10 (representing 10%).
game_ended
bool
Flag indicating whether the game has concluded. Once true, no more bids can be placed.

Account size

The GameState account is exactly 96 bytes:
  • 8 u64 fields × 8 bytes = 64 bytes
  • 1 Pubkey field × 32 bytes = 32 bytes
  • 1 bool field × 1 byte = 1 byte
  • Total: 97 bytes (likely padded to 96 in actual implementation)

Deserialization

The program includes a custom deserializer for GameState:
pub fn deserialize_game_state(account_data: &[u8]) -> Result<GameState, ProgramError>
This function manually extracts fields from the byte array for better performance and control. See utils.rs:98-127 for implementation.

Example state

{
  "game_id": 1,
  "initial_bid_amount": 14000000,
  "highest_bid": 56000000,
  "last_bid_time": 1709424000,
  "total_bids": 5,
  "last_bidder": "8x7Q...kB2m",
  "prize_pool": 210000000,
  "platform_fee_percentage": 10,
  "game_ended": false
}

PlayerState

Tracks statistics for a single player’s participation in a game. One PlayerState account is created per bid (not per unique player - a player bidding multiple times will have multiple PlayerState accounts).

Structure

pub struct PlayerState {
    pub total_bid_amount: u64,
    pub safe: bool,
    pub royalty_earned: u64,
    pub bid_count: u64,
}
See state.rs:17-24 for the struct definition.

Fields

total_bid_amount
u64
The bid amount in lamports that this player placed. Stored for reference and royalty calculations.
safe
bool
Indicates whether this player’s funds are safe (they received their payout). Set to true when:
  • Player wins the game and receives the prize
  • Player receives royalty distribution as an early bidder
Initially false when the account is created.
royalty_earned
u64
Amount of royalty/winnings this player has earned in lamports. Updated when the game ends and distributions are made. Remains 0 for players who don’t receive payouts.
bid_count
u64
The bid sequence number when this player state was created. Matches the bid count in the PDA seeds. Used to uniquely identify this player state.

Account size

The PlayerState account is exactly 32 bytes:
  • 3 u64 fields × 8 bytes = 24 bytes
  • 1 bool field × 1 byte = 1 byte
  • Padding: ~7 bytes
  • Total: 32 bytes

Deserialization

The program includes a custom deserializer for PlayerState:
pub fn deserialize_player_state(account_data: &[u8]) -> Result<PlayerState, ProgramError>
See utils.rs:129-145 for implementation.
Each bid creates a new PlayerState account, even if the same player bids multiple times. This allows tracking each bid independently for royalty calculations.

Example state

{
  "total_bid_amount": 28000000,
  "safe": true,
  "royalty_earned": 5600000,
  "bid_count": 3
}

Bid

Records details of a single bid. One Bid account is created for each bid placed in the game.

Structure

pub struct Bid {
    pub bidder: Pubkey,
    pub amount: u64,
    pub timestamp: u64,
}
See state.rs:25-31 for the struct definition.

Fields

bidder
Pubkey
Public key of the account that placed this bid. Used to identify the bidder for prize distribution.
amount
u64
Bid amount in lamports. Must be at least 2x the previous highest bid (except for the initial bid).
timestamp
u64
Unix timestamp when this bid was placed. Retrieved from the Solana Clock sysvar.

Account size

The Bid account is exactly 48 bytes:
  • 1 Pubkey field × 32 bytes = 32 bytes
  • 2 u64 fields × 8 bytes = 16 bytes
  • Total: 48 bytes

Bid history

The program provides a utility function to fetch all bids for a game:
pub fn fetch_bid_history(
    program_id: &Pubkey,
    game_id: u64,
    total_bids: u64,
    accounts: &[AccountInfo],
) -> Result<Vec<Bid>, BiddingError>
This function:
  1. Iterates through bid numbers 1 to total_bids
  2. Derives the PDA for each bid
  3. Deserializes the Bid data
  4. Returns a vector of all bids in order
See utils.rs:46-81 for implementation.
Bid history fetching requires all bid accounts to be passed into the instruction. For games with many bids, this can hit transaction size limits.

Example bid

{
  "bidder": "8x7Q...kB2m",
  "amount": 28000000,
  "timestamp": 1709424000
}

Account ownership

All three account types are owned by the SolBid program. This is enforced during account creation:
system_instruction::create_account(
    payer_account.key,
    account.key,
    rent,
    account_size as u64,
    program_id, // Owner
)

Serialization

All accounts use Borsh serialization:
use borsh::{BorshDeserialize, BorshSerialize};

// Serialize to account data
state.serialize(&mut &mut account.data.borrow_mut()[..])?;

// Deserialize from account data
let state = GameState::try_from_slice(&account.data.borrow())?;

Rent exemption

All accounts are created with rent-exempt balance:
let rent = Rent::get()?;
let account_rent = rent.minimum_balance(account_size);
This ensures accounts persist indefinitely on-chain without requiring ongoing rent payments.

Build docs developers (and LLMs) love