Protocol v1

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.

HackBet is not a binary-outcome prediction market. Instead, HackBet adopts pari-mutuels betting mechanism.

Key stakeholders

RoleDescription
Protocol AdminControls global settings: creates hackathons, whitelists stakers, resolves outcomes.
BuilderRegisters a project, pays a refundable deposit, and self-stakes to signal conviction.
StakerBacks 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:

5QyJgZfUCLKZnoxSMu9ejraQ9365HrwBmn9WVPnUayDd

All 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

1. Initialize Hackathon

Admin calls initialize_hackathon with name, ranking, deposit amount, and deadlines. This creates the HackathonState PDA and escrow token account.

2

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

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

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

5. Resolve

Results are published and admin assigns rank to winning projects. This is reversible until finalize_resolve is called.

6

6. Finalize Resolve

Admin calls finalize_resolve once all ranks are set. This finalizes rankings, and prize pools, on-chain irreversibly.

7

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.

ConstraintValue
Max stake per wallet per project$250 USDC
Max builder self-stake$250 USDC
Staking requiresProject must have deposit paid and be declared
Staking window closesUp 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 escrow

The 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 timeElapsed / WindowMultiplier1,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
The builder self-stake, if any, is computed by the same formula. A builder who stakes at genesis gets the maximum 1.5× boost.

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).

Using Colosseum Frontier 2026 as an example: With one grand prize of 30,000 USDC and 22 side-track prizes of 10,000 USDC, we can emulate 1 Tier 1 share of 12% and 22 Tier 2 shares of 4% each:[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_000

The protocol fee is then deducted from payout at claim time:

fee = payout × protocol_fee_bps / 10_000 // default 1.5% staker_receives = payout − fee

Worked 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).

ProjectRankPer-projectUser Shares / TotalGross PayoutNet (−1.5%)
A1st12% ($120)300 / 300$120$118.2
B2nd4% ($40)300 / 300$40$39.4
This assumes that there are 21 other Tier 2 projects that registered. What if these are the only two projects that registered and won?
ProjectRankPer-projectUser Shares / TotalGross PayoutNet (−1.5%)
A1st75% ($750)300 / 300$750$738.75
B2nd25% ($250)300 / 300$250$246.25
Tier 1 draws 12% × 1 project = 1,200 bps. Tier 2 draws 4% × 1 project = 400 bps. The unused 8,400 bps (84%) cascades 3:1 to the occupied tiers — 1st draws 3× more per project (12%) than 2nd (4%), so it gets 3× more cascade. Final: 75% vs 25%. Higher rank always earns more per project. What if there are 5 other Tier 2 projects?
ProjectRankPer-projectUser Shares / TotalGross PayoutNet (−1.5%)
A1st37.5% ($375)300 / 300$375$369.37
B2nd12.5% ($125)300 / 300$125$123.12
C2nd12.5% ($125)300 / 300$125$123.12
D2nd12.5% ($125)300 / 300$125$123.12
E2nd12.5% ($125)300 / 300$125$123.12
F2nd12.5% ($125)300 / 300$125$123.12
Same as before, except this time, the unused 6,800 bps (68%) cascades 3:1 to the occupied tiers. Final: 37.5% vs 12.5%.

On-Chain Constants

These values are compiled into lib.rs and apply to every hackathon unless otherwise overridable at initialization.

ConstantValueDescription
UNSTAKE_PENALTY_BPS300 (3%)Flat early-exit penalty on full stake
UNSTAKE_PROTOCOL_BPS150 (1.5%)Portion of penalty sent to fee_recipient
EARLY_MULTIPLIER_BPS15,000 (1.5×)Share multiplier at hackathon start
BASE_MULTIPLIER_BPS10,000 (1.0×)Share multiplier at cutoff
MAX_STAKE_PER_WALLET250,000,000$250 USDC per wallet per project
MAX_SELF_STAKE250,000,000$250 USDC builder self-stake cap
DEFAULT_PROTOCOL_FEE_BPS150 (1.5%)Protocol fee deducted at claim
DEFAULT_DEPOSIT_AMOUNT10,000,000$10 USDC builder commitment deposit
MAX_TIERS8Maximum 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.

AccountSeeds
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
ProjectAccount uses the SHA-256 hash of the URL, not the raw bytes. This is critical: passing the wrong hash will derive a different PDA address and the transaction will fail. Always derive the hash client-side before constructing the instruction.

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:

EventAmountWhere it goes
Unstake (early exit)3% of stake1.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.