HACKBET Protocol Reference
Technical documentation for the HackBet rank-weighted staking pool protocol. Covers staking mechanics, payout formulas and on-chain account structures.
Overview
HackBet is a Solana-native staking pool protocol built for hackathons. Participants stake USDC behind hackathon projects before results are announced. When the official judges publish rankings, the total pool is redistributed to backers of ranked projects using a formula that rewards early conviction and optimal selections.
Key stakeholders
| Role | Description |
|---|---|
| Protocol Admin | Controls global settings: creates hackathons, whitelists stakers, resolves outcomes. |
| Builder | Registers a project, pays a refundable deposit, and self-stakes to signal conviction. |
| Staker | Backs one or more projects with USDC. Earns a share of the pool proportional to rank, stake, and timing. |
Where the protocol lives
The program is deployed on Solana mainnet at:
5QyJgZfUCLKZnoxSMu9ejraQ9365HrwBmn9WVPnUayDdAll funds are held in a per-hackathon USDC escrow PDA. Only resolution-gated claim instructions can move USDC tokens.
Hackathon Lifecycle
Each hackathon follows a strict on-chain state machine. The diagram below shows the full flow from creation to payout.
1. Initialize Hackathon
Admin calls initialize_hackathon with name, ranking, deposit amount, and deadlines. This creates the HackathonState PDA and escrow token account.
2. Register Projects
Builders call register_project with their GitHub URL. PDA is seeded with sha256(github_url). If requires_approval is set, admin must approve submissions first.
3. Staking Window
Stakers call stake() to deposit USDC into escrow. Shares are computed at stake time using the time-weighted multiplier (1.5× early → 1.0× at cutoff).
4. Cutoff (−24 h)
If a staking cutoff was assigned (up to 24h before actual deadline), all staking locks by cutoff. Stakers may also choose to unstake, incurring a 3% penalty, before cutoff.
5. Resolve
Results are published and admin assigns rank to winning projects. This is reversible until finalize_resolve is called.
6. Finalize Resolve
Admin calls finalize_resolve once all ranks are set. This finalizes rankings, and prize pools, on-chain irreversibly.
7. Claim
Builders and stakers with winning claims are now eligible to claim their winnings, if any. Protocol fee is deducted at this point.
Staking & Unstaking
Staking
Any whitelisted wallet (or any wallet when requires_approval = false) can stake USDC behind a registered project from the moment the hackathon is initialized until the cutoff timestamp.
| Constraint | Value |
|---|---|
| Max stake per wallet per project | $250 USDC |
| Max builder self-stake | $250 USDC |
| Staking requires | Project must have deposit paid and be declared |
| Staking window closes | Up to 24 hours before actual hackathon deadline, set by admin |
Unstaking
A staker can exit their position at any time before the cutoff, subject to a flat 3% penalty on the amount unstaked. Note that a user can only unstake the full amount and not partially.
penalty = stake_amount × 300 / 10_000 // 3% flat
to_protocol = stake_amount × 150 / 10_000 // 1.5% → fee recipient
stays_in_pool = penalty − to_protocol // 1.5% stays in escrowThe staker receives stake_amount − penalty. After cutoff, unstaking is fully disabled — positions are locked until claim.
Time-Weighted Shares
Shares determine each staker's fraction of a project's total stake pool. They are computed at stake time and never change — unstaking burns your shares entirely.
window = cutoff_timestamp − hackathon.start_timestamp
elapsed = clamp(now − start_timestamp, 0, window)
mult_bps = 15_000 − floor(5_000 × elapsed / window) // 15000 → 10000
shares = floor(amount × mult_bps / 10_000)The multiplier decreases linearly from 1.5× at the project submission to 1.0× at the cutoff. Stakers who commit early receive proportionally more shares for the same USDC amount.
Example
| Stake time | Elapsed / Window | Multiplier | 1,000 USDC → Shares |
|---|---|---|---|
| T+0 (open) | 0% | 1.5× (15,000 bps) | 1,500 |
| T+25% | 25% | 1.375× (13,750 bps) | 1,375 |
| T+50% | 50% | 1.25× (12,500 bps) | 1,250 |
| T+75% | 75% | 1.125× (11,250 bps) | 1,125 |
| T+100% (cutoff) | 100% | 1.0× (10,000 bps) | 1,000 |
Payout Formula
The payout calculation runs in two stages: first allocate the prize pool across rank tiers, then distribute equally within each tier.
Stage 1 — Tier allocation
[12, 88] means the rank-1 project gets 12% The admin configures up to 8 tiers at hackathon creation, each with a percentage (must sum to 100).
[12, 88] means the rank-1 project gets 12% of the pool, and 22 rank-2 projects share 88% equally (4% each).At finalize_resolve, if there are empty tiers (aka projects assigned that rank didn't register on HackBet), those tiers have their allocation redistributed proportionally to occupied tiers.
Stage 2 — Equal split (within tier)
Every ranked project in a tier gets an equal share of that tier's pool. The number of ranked projects per tier (N) is counted and snapshotted at finalize_resolve.
N = hackathon.tier_c_totals[tier] // project count, snapshotted
payout = user_shares × effective_tier_pcts[tier] × total_pool
──────────────────────────────────────────
project.total_shares × N × 10_000The protocol fee is then deducted from payout at claim time:
fee = payout × protocol_fee_bps / 10_000 // default 1.5%
staker_receives = payout − feeWorked example
Same Frontier example as before: 1st = 12%, 2nd = 88% (22 expected). Pool = $1,000. Fee = 1.5%. Alice stakes $200 on Project A (1st), Bob stakes $200 on Project B (2nd). Both stake Day 0 (1.5× multiplier = 300 shares each).
| Project | Rank | Per-project | User Shares / Total | Gross Payout | Net (−1.5%) |
|---|---|---|---|---|---|
| A | 1st | 12% ($120) | 300 / 300 | $120 | $118.2 |
| B | 2nd | 4% ($40) | 300 / 300 | $40 | $39.4 |
| Project | Rank | Per-project | User Shares / Total | Gross Payout | Net (−1.5%) |
|---|---|---|---|---|---|
| A | 1st | 75% ($750) | 300 / 300 | $750 | $738.75 |
| B | 2nd | 25% ($250) | 300 / 300 | $250 | $246.25 |
| Project | Rank | Per-project | User Shares / Total | Gross Payout | Net (−1.5%) |
|---|---|---|---|---|---|
| A | 1st | 37.5% ($375) | 300 / 300 | $375 | $369.37 |
| B | 2nd | 12.5% ($125) | 300 / 300 | $125 | $123.12 |
| C | 2nd | 12.5% ($125) | 300 / 300 | $125 | $123.12 |
| D | 2nd | 12.5% ($125) | 300 / 300 | $125 | $123.12 |
| E | 2nd | 12.5% ($125) | 300 / 300 | $125 | $123.12 |
| F | 2nd | 12.5% ($125) | 300 / 300 | $125 | $123.12 |
On-Chain Constants
These values are compiled into lib.rs and apply to every hackathon unless otherwise overridable at initialization.
| Constant | Value | Description |
|---|---|---|
UNSTAKE_PENALTY_BPS | 300 (3%) | Flat early-exit penalty on full stake |
UNSTAKE_PROTOCOL_BPS | 150 (1.5%) | Portion of penalty sent to fee_recipient |
EARLY_MULTIPLIER_BPS | 15,000 (1.5×) | Share multiplier at hackathon start |
BASE_MULTIPLIER_BPS | 10,000 (1.0×) | Share multiplier at cutoff |
MAX_STAKE_PER_WALLET | 250,000,000 | $250 USDC per wallet per project |
MAX_SELF_STAKE | 250,000,000 | $250 USDC builder self-stake cap |
DEFAULT_PROTOCOL_FEE_BPS | 150 (1.5%) | Protocol fee deducted at claim |
DEFAULT_DEPOSIT_AMOUNT | 10,000,000 | $10 USDC builder commitment deposit |
MAX_TIERS | 8 | Maximum number of rank tiers per hackathon |
protocol_fee_bps is configurable per hackathon but is capped at3000 (30%) as an on-chain constraint.
Account Structures
HackathonState
One per hackathon. Holds all configuration, state, and post-resolution snapshots.
HackathonState {
admin: Pubkey,
usdc_mint: Pubkey,
name: String, // max 50 bytes; also used in PDA seed
start_timestamp: i64,
irl_hackathon_deadline_timestamp: i64,
cutoff_timestamp: i64, // = irl_hackathon_deadline_timestamp − 86_400
total_pool: u64, // running USDC balance in escrow
is_resolved: bool,
tier_count: u8,
tier_pcts: [u8; 8], // raw percentages, must sum to 100
effective_tier_pcts: [u16; 8], // redistributed — set at finalize_resolve
tier_c_totals: [u64; 8], // number of payout-eligible projects per tier — set at finalize_resolve
fee_recipient: Pubkey,
protocol_fee_bps: u16, // capped at 3000
deposit_amount: u64,
requires_approval: bool,
bump: u8,
}ProjectAccount
One per project per hackathon.
ProjectAccount {
hackathon: Pubkey,
github_url: String, // max 200 chars; sha256 used for PDA seed
total_staked: u64,
total_shares: u64,
rank: u8, // 0 = unresolved; 1-indexed
builder_wallet: Pubkey,
deposit_amount_paid: u64,
builder_staked: u64,
builder_declared: bool,
submitted: bool,
is_refund_enabled: bool,
deposit_forfeited: bool,
deposit_refunded: bool,
bump: u8,
}UserStake
One per (user, project) pair. Holds the raw stake and computed shares.
UserStake {
user: Pubkey,
project: Pubkey,
amount: u64,
shares: u64,
stake_timestamp: i64,
is_claimed: bool,
bump: u8,
}WhitelistedWallet
Existence of this PDA grants a wallet permission to stake when requires_approval = true.
WhitelistedWallet {
hackathon: Pubkey,
wallet: Pubkey,
bump: u8,
}PDA Seeds
All program-derived addresses are deterministic from the seeds below.
| Account | Seeds |
|---|---|
HackathonState | "hackathon" + admin pubkey + name (UTF-8 bytes) |
ProjectAccount | "project" + hackathon pubkey + sha256(github_url) |
UserStake | "stake" + user pubkey + project pubkey |
Escrow (token acct) | "escrow" + hackathon pubkey |
WhitelistedWallet | "whitelist" + hackathon pubkey + wallet pubkey |
All helpers live in frontend/lib/pda.ts.
Claim Architecture
When finalize_resolve is called, the program counts the number of ranked projects in each tier and stores those counts on HackathonState. When a staker calls claim, it reads the stored count directly — no iteration required.
This means claim compute cost is flat regardless of how many projects are in the hackathon. The payout denominator is set at resolution time and cannot be manipulated by the caller.
Fee Architecture
The protocol collects fees at three points:
| Event | Amount | Where it goes |
|---|---|---|
| Unstake (early exit) | 3% of stake | 1.5% to fee recipient, 1.5% stays in prize pool |
| Claim (winning payout) | 1.5% of gross payout (default) | fee recipient |
| Builder deposit (no submission) | $10 USDC (default) | fee recipient |
Unstake penalty
Stakers who exit before the cutoff forfeit 3% of their stake. Half leaves the escrow and goes to the fee recipient; the other half stays in the pool and is distributed to remaining stakers at resolution.
Protocol fee
Deducted from each winning payout at claim time. The rate is set per-hackathon at initialization (default 1.5%, maximum 30%). If set to 0, no fee is charged.
Builder deposit
Builders pay a deposit (default $10 USDC) when registering. It is refundable if the project is formally submitted and the admin enables refunds. If the builder never submits, the admin can forfeit the deposit to the fee recipient after a post-deadline grace period.