Sol.College

Full lesson

Fitr

Fitness commitment app for Solana Seeker: stake SKR on your daily workout, on-device AI counts every rep, live challenges and an onchain vault (Squads 2-of-3) settle it. No-shows fund the people who show up.

What it does

From the project README:

A fitness commitment app for the Solana Seeker: stake your own SKR on a daily workout goal, let on-device AI count every rep, and settle the day onchain.
Original
NetLayerLabs/Fitr
SuperStack fork
Blockchains/Fitr @ 79b3dae
Stars
0
Last push (original)
2026-10-12
Languages
TypeScript, Rust, CSS, JavaScript, Kotlin, Shell
Detected stack
@solana/kitAnchorNext.js
Categories
AI & AgentsDeFi
Licence
MIT
Homepage
https://fitr.day

Architecture

717 source files. By extension: .ts ×485, .tsx ×214, .rs ×16, .js ×1, .py ×1.

Top-level directorySource files
src660
video37
anchor16
(root)1
scripts1
public1
ops1

On-chain programs: anchor/programs/fitr-vault

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/fitr/. 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.

NetLayerLabs/Fitr · anchor/programs/fitr-vault/src/lib.rs L733–756 @ 79b3dae · MIT · original
        pool.reserve = pool.reserve.checked_add(amount).ok_or(VaultError::MathOverflow)?;        emit!(ReserveFunded {            pool: pool.key(),            funder: ctx.accounts.funder.key(),            amount,            crew_id: pool.crew_id,            reserve: pool.reserve,            daily_sponsor_amount: pool.daily_sponsor_amount,        });        Ok(())    }     /// Authority can only ever withdraw accrued fees - never principal or the reserve directly.    pub fn withdraw_treasury(ctx: Context<WithdrawTreasury>, amount: u64) -> Result<()> {        require!(amount <= ctx.accounts.pool.treasury_accrued, VaultError::InsufficientFunds);        let mint_key = ctx.accounts.mint.key();        let crew = ctx.accounts.pool.crew_seed();        let bump = [ctx.accounts.pool.bump];        let signer_seeds: &[&[&[u8]]] = &[&[POOL_SEED, mint_key.as_ref(), &crew, &bump]];        token_interface::transfer_checked(            CpiContext::new_with_signer(                ctx.accounts.token_program.to_account_info(),                TransferChecked {                    from: ctx.accounts.vault.to_account_info(),
  1. Line 733 pool.reserve = pool.reserve.checked_add(amount).ok_or(VaultError::MathOverflow)?;: Uses checked arithmetic so overflow fails instead of wrapping.
  2. Line 734 emit!(ReserveFunded {: Emits an Anchor event that clients can parse from the logs.
  3. Line 747 require!(amount <= ctx.accounts.pool.treasury_accrued, VaultError::InsufficientFunds);: An explicit validation that aborts the instruction with an error.
  4. Line 750 let bump = [ctx.accounts.pool.bump];: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  5. Line 752 token_interface::transfer_checked(: A checked token transfer, which also verifies the mint and decimals.
  6. Line 753 CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
NetLayerLabs/Fitr · anchor/programs/fitr-vault/src/contexts.rs L97–120 @ 79b3dae · MIT · original
    #[account(mut)]    pub creator: Signer<'info>,    /// The global pool (crew_id 0) of `mint`: the config source.    #[account(        seeds = [POOL_SEED, mint.key().as_ref(), &GLOBAL_CREW_SEED],        bump = global_pool.bump,        has_one = mint    )]    pub global_pool: Box<Account<'info, Pool>>,    #[account(mint::token_program = token_program)]    pub mint: Box<InterfaceAccount<'info, Mint>>,    /// The crew pool. `init` fails if this crew_id already exists.    #[account(        init,        payer = creator,        space = 8 + Pool::INIT_SPACE,        seeds = [POOL_SEED, mint.key().as_ref(), &crew_id.to_le_bytes()],        bump    )]    pub pool: Box<Account<'info, Pool>>,    #[account(        init,        payer = creator,        seeds = [VAULT_SEED, pool.key().as_ref()],
  1. Line 98 pub creator: Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 101 seeds = [POOL_SEED, mint.key().as_ref(), &GLOBAL_CREW_SEED],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  3. Line 102 bump = global_pool.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  4. Line 103 has_one = mint: Checks that a stored pubkey field matches the passed account.
  5. Line 110 init,: Creates and pays for a new account. Space and payer must be specified.
NetLayerLabs/Fitr · anchor/programs/fitr-vault/src/math.rs L13–36 @ 79b3dae · MIT · original
} /// Inclusive start of day `day`: day * len + offset.pub fn day_start(day: u32, offset: i64, len: i64) -> Result<i64> {    (day as i64)        .checked_mul(len)        .and_then(|v| v.checked_add(offset))        .ok_or(error!(VaultError::MathOverflow))} /// Exclusive end of the day window (== start of next day).pub fn day_end(day: u32, offset: i64, len: i64) -> Result<i64> {    day_start(day.checked_add(1).ok_or(VaultError::MathOverflow)?, offset, len)} /// Largest allowed commit cutoff: len - len/24 (23 h for a 24 h day), so at least/// 1/24 of the day always remains to do the workout after the last possible commit.pub fn max_commit_cutoff(len: i64) -> i64 {    len - len / 24} /// Hard stake ceiling in base units: MAX_STAKE_CEILING_TOKENS * 10^decimals.pub fn stake_ceiling(decimals: u8) -> Result<u64> {    require!(decimals <= MAX_MINT_DECIMALS, VaultError::UnsupportedMintDecimals);
  1. Line 18 .checked_mul(len): Uses checked arithmetic so overflow fails instead of wrapping.
  2. Line 34 /// Hard stake ceiling in base units: MAX_STAKE_CEILING_TOKENS * 10^decimals.: Touches staking state: the amount locked and when it was locked.
  3. Line 36 require!(decimals <= MAX_MINT_DECIMALS, VaultError::UnsupportedMintDecimals);: An explicit validation that aborts the instruction with an error.
NetLayerLabs/Fitr · src/lib/actions/spec.ts L1–24 @ 79b3dae · MIT · original
/** * Solana Actions (Blinks) wire format and HTTP helpers. Pure: no network, importable anywhere. * * Spec: https://solana.com/docs/advanced/actions (Actions spec v2.x). * - GET  → ActionGetResponse (metadata: icon, title, description, label, links.actions) * - POST {account} → ActionPostResponse {type: "transaction", transaction (base64, unsigned, *   fee payer = account), message} * - OPTIONS → CORS preflight. Every response (including errors) carries the CORS headers plus *   X-Action-Version / X-Blockchain-Ids so blink clients (dial.to, wallets) accept it. * - errors → 4xx/5xx with ActionError {message} (shown to the user verbatim). * - /actions.json at the domain root maps website paths to action API paths. */import type { SolanaCluster } from "@/lib/solana/config"; export const ACTION_VERSION = "2.4"; /** CAIP-2 ids: solana:<first 32 chars of the genesis hash>. */export const BLOCKCHAIN_IDS: Readonly<Record<SolanaCluster, string>> = Object.freeze({  mainnet: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",  devnet: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",}); export function actionsCorsHeaders(cluster: SolanaCluster): Record<string, string> {  return {
  1. Line 5 * - GET → ActionGetResponse (metadata: icon, title, description, label, links.actions): The metadata a Blink client renders (title, icon, buttons).
  2. Line 6 * - POST {account} → ActionPostResponse {type: "transaction", transaction (base64, unsigne: Returns the serialized transaction the wallet will sign.

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 devnext dev
.npm run startnext start
.npm run buildnext build
.npm run testvitest run
.npm run linteslint
anchoranchor buildcompile programs and generate IDLs
anchoranchor testlocal validator + test suite
anchoranchor run testcd programs/fitr-vault && CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS=fallback cargo test

What you could build from it

Used in lessons