Sol.College

Full lesson

Arbitron

A Time bound, Competitive Trading Tournament DApp

What it does

From the project README:

Arbitron transforms crypto trading into a fun, fair, and skill-based competition. Join fast-paced 10-minute contests, strategically draft your virtual portfolio, and compete for prizes—all without liquidation risks.
Original
Jayant818/Arbitron
SuperStack fork
Blockchains/Arbitron @ 105fc75
Stars
1
Last push (original)
2025-11-03
Languages
TypeScript, Rust, CSS, JavaScript
Detected stack
@solana/kitAnchorNext.jsweb3.js
Categories
DeFi
Licence
MIT
Homepage
https://www.arbitron.fun/

Architecture

117 source files. By extension: .tsx ×50, .ts ×39, .rs ×28.

Top-level directorySource files
app81
programs24
packages7
tests2
(root)1
arbitron-pnl-guest1
migrations1

On-chain programs: programs/arbitron

Dapp: devnet deploy pending

⏳ Devnet deploy pending — goes live when the devnet wallet is topped up. This fork's own dapp is not deployed yet, so there is no program address or transaction to show. It is queued in dapp-queue.txt; once devnet SOL is available it is deployed, exercised with real devnet transactions, and this section switches to the live dapp at /dapps/arbitron/. Until then, the lesson and step-by-step guide below are complete, and you can run it locally with the commands below.

Key code, explained

Excerpts chosen automatically where the code uses the most Solana concepts, pinned to the fork's commit.

Jayant818/Arbitron · programs/arbitron/src/handlers/claim_prize.rs L85–108 @ 105fc75 · MIT · original
    msg!("Winner amount: {}", winner_amount);     let host_key = contest.host.key();    let contest_bump = [contest.bump];    let contest_name_bytes = contest.name.as_bytes();    let contest_seeds = &[        b"contest".as_ref(),        contest_name_bytes,         host_key.as_ref(),        &contest_bump,     ][..];     let signer_seeds = &[contest_seeds][..];      if winner_amount > 0 {        let winner_transfer_accounts = TransferChecked {            from: prize_pool_vault.to_account_info(),            mint: ctx.accounts.token_mint.to_account_info(),            to: ctx.accounts.winner_usdc_account.to_account_info(),            authority: contest.to_account_info(),         };        let cpi_ctx_winner = CpiContext::new_with_signer(            ctx.accounts.token_program.to_account_info(),            winner_transfer_accounts,            signer_seeds, 
  1. Line 85 msg!("Winner amount: {}", winner_amount);: Logs to the program log, which is visible in explorers and the test output.
  2. Line 88 let contest_bump = [contest.bump];: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  3. Line 90 let contest_seeds = &[: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  4. Line 99 let winner_transfer_accounts = TransferChecked {: A checked token transfer, which also verifies the mint and decimals.
  5. Line 105 let cpi_ctx_winner = CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
Jayant818/Arbitron · programs/arbitron/src/handlers/start_contest.rs L13–36 @ 105fc75 · MIT · original
        mut,        seeds = [            b"contest",            contest.name.as_bytes(),            contest.host.key().as_ref()        ],        bump = contest.bump,        // constraint = contest.host == host.key() @ErrorCode::UnauthorizedHost,        constraint = contest.status == ContestState::Upcoming @ErrorCode::InvalidContestState,    )]    pub contest: Account<'info, Contest>,} pub fn start_contest(ctx:Context<StartContest>)->Result<()>{     let contest = &mut ctx.accounts.contest;     let clock = Clock::get()?;     require!(contest.participents_count > 1, ErrorCode::MinContestParticipantsError);    require!(clock.unix_timestamp >= contest.start_time, ErrorCode::ContestNotStartedYet);     contest.status = ContestState::Ongoing;    // Contest started and we have recorded the actual start time.
  1. Line 14 seeds = [: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  2. Line 19 bump = contest.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  3. Line 30 let clock = Clock::get()?;: Reads on-chain time from the Clock sysvar.
  4. Line 32 require!(contest.participents_count > 1, ErrorCode::MinContestParticipantsError);: An explicit validation that aborts the instruction with an error.
Jayant818/Arbitron · programs/arbitron/src/handlers/store_contest_input.rs L1–24 @ 105fc75 · MIT · original
use anchor_lang::prelude::*;use crate::{    Contest, ContestInput,    error::ErrorCode}; #[derive(Accounts)]pub struct StoreContestInput<'info>{    #[account(mut)]    pub payer: Signer<'info>,     pub contest: Account<'info,Contest>,     #[account(        init,        payer = payer,        space = ContestInput::SPACE,        seeds = [            b"contest_inputs",            contest.key().as_ref()        ],        bump    )]    pub contest_inputs : Account<'info,ContestInput>,
  1. Line 10 pub payer: Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 15 init,: Creates and pays for a new account. Space and payer must be specified.
  3. Line 18 seeds = [: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  4. Line 22 bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
Jayant818/Arbitron · programs/arbitron/src/handlers/initialize.rs L1–23 @ 105fc75 · MIT · original
use anchor_lang::prelude::*;use crate::config::Config; #[derive(Accounts)]pub struct Initialize<'info>{    #[account(mut)]    pub admin:Signer<'info>,     #[account(        init,        payer = admin,        seeds = [            b"config",        ],        bump,        space = Config::DISCRIMINATOR.len() + Config::INIT_SPACE,    )]    pub config:Account<'info,Config>,     pub system_program:Program<'info,System>,} pub fn initialize(context:Context<Initialize>,platform_fee_wallet:Pubkey,platform_fee_bps:u16, platform_fee_wallet_mint: Pubkey)->Result<()>{
  1. Line 7 pub admin:Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 10 init,: Creates and pays for a new account. Space and payer must be specified.
  3. Line 12 seeds = [: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  4. Line 15 bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.

How to run, build and test

These commands come from the repo's own manifests (package.json scripts, Anchor.toml, Cargo, go.mod, Makefile). Check the README for prerequisites and environment variables.

DirectoryCommandRuns
.npm run devturbo dev
.npm run buildanchor build
.npm run testanchor test
.npm run lintprettier */*.js "*/**/*{.js,.ts}" --check
app/apinpm run devtsx watch src/index.ts
app/apinpm run testecho "Error: no test specified" && exit 1
app/crank-servicenpm run devtsx ./src/index.ts
app/crank-servicenpm run testecho "Error: no test specified" && exit 1
app/arbriton-frontendnpm run devnext dev
app/arbriton-frontendnpm run startnext start
app/arbriton-frontendnpm run buildnext build
app/arbriton-frontendnpm run lintnext lint
app/ws-servernpm run devtsx src/index.ts
app/ws-servernpm run startnode dist/index.js
app/ws-servernpm run buildtsc
app/ws-servernpm run testecho "Error: no test specified" && exit 1

What you could build from it

Used in lessons