Technical Design
Deep dive into the smart contract architecture and implementation details.
Architecture Overview
The Daily Lottery is built on Solana using an account-based program architecture.
┌─────────────────────────────────────────────────────────────────┐
│ Daily Lottery Program │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────────┐ │
│ │ Config │ │ Lottery │ │ Participant │ │
│ │ Account │ │ Account │ │ Accounts │ │
│ └──────────────┘ └──────────────┘ └────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌────────────────────┐ ┌─────────────────┐ │
│ │ Vault PDA │ │ Vote Tally PDA │ │ Winners Ledger │ │
│ └──────────────┘ └────────────────────┘ └─────────────────┘ │
│ │
│ Instructions: create_lottery, buy_tickets, attest_uploaded, │
│ upload_reveals, finalize_winners, settle_payout │
└─────────────────────────────────────────────────────────────────┘
Account Structure
Config Account
pub struct Config {
pub authority: Pubkey,
pub ticket_price_lamports: u64,
pub service_charge_bps: u16,
pub buy_window_secs: u32,
pub upload_window_secs: u32,
pub max_winners_cap: u32,
}
Lottery Account (simplified)
pub struct Lottery {
pub id: u64,
pub authority: Pubkey,
pub buy_deadline_unix: i64,
pub upload_deadline_unix: i64,
pub total_tickets: u64,
pub total_funds: u64,
pub attested_count: u64,
pub selected_number_of_winners: u64,
pub poc_aggregate_hash: [u8; 32],
pub winners_merkle_root: [u8; 32],
pub settled: bool,
}
Participant Account (simplified)
pub struct Participant {
pub lottery: Pubkey,
pub wallet: Pubkey,
pub proof_of_chance_hash: [u8; 32],
pub tickets_bought: u64,
pub attested_uploaded: bool,
pub voted_number_of_winners: u64,
pub reveal_score: u64, // informational only
}
PDA seeds: Lottery = ['lottery', config_pubkey, lottery_id_le_bytes], Participant =
['participant', lottery_pubkey, wallet_pubkey], Vault = ['vault', lottery_pubkey].
State Machine
| Phase | Description | Allowed Actions |
|---|---|---|
Buy | Accepting ticket purchases | buy_tickets |
Upload | Attestation window | attest_uploaded |
Settlement | Provider uploads + winner finalization | upload_reveals, finalize_winners, settle_payout_batch |
Settled | Winners paid or refunds issued | (read-only) |
Key Instructions
create_lottery
Creates a new lottery instance and its vault PDA.
buy_tickets
Creates or updates the participant account and stores immutable commitment hash on first purchase.
attest_uploaded
Records upload attestation and vote for winner count.
upload_reveals
Verifies reveal plaintexts against commitments and computes vote tally. reveal_score is stored for transparency analytics, not payout entropy.
finalize_winners
Runs reveal-plaintext draw v2 from reveal-included participants, stores winners merkle root and settlement metadata.
settle_payout_batch
Processes payout batches with merkle proof verification.
finalize_no_attesters
Refund path for no-attester rounds and for attested rounds where zero reveals were uploaded by deadline. The instruction requires config, lottery, vault, and the configured authority signer. Single-participant rounds auto-complete upload when the upload/attestation phase opens and may settle through this path immediately; multi-participant refunds remain gated on the upload deadline.
Winner Algorithm (Reveal-Plaintext Draw v2)
finalize_winners computes winners from reveal-included commitments and tickets:
- Sort eligible participants (
tickets > 0and reveal-included) by wallet bytes ascending. - Build seed chain:
S0 = SHA256(0x494B494741495F5250445F56325F53454544 || lottery_id_le64 || eligible_count_le64 || total_revealed_tickets_le64 || poc_aggregate_hash_32)
S_next = SHA256(S_prev || wallet_32 || tickets_le64)
- For each round
j:
Dj = SHA256(0x494B494741495F5250445F56325F44524157 || S || j_le64)
rj = LE_u128(Dj[0..16]) mod total_remaining_tickets
- Walk ticket ranges to select winner, remove winner, repeat.
This is deterministic, human-auditable, and independent of reveal string length.
Security Measures
Access Control
- Only admin can create/configure lotteries.
- Only authorized signer can execute admin settlement instructions.
- PDA validation is enforced for config/lottery/vault/participant accounts.
Economic Security
- Prize pool is locked in a vault PDA.
- Payouts are merkle-verified winner transfers.
- Refund flow remains available for no-attester rounds and zero-reveal rounds after the upload deadline, plus immediate single-participant outcomes after upload opens.
Integrity and Replay Resistance
- Commitments are immutable per participant.
- Winner selection is deterministic from immutable on-chain fields.
- Settlement state prevents re-finalization and double processing.
Contract Addresses
See Addresses for deployed contract addresses per network.