Sol.College

Full lesson

solad

SoLad securely store Solana data under flexible conditions

What it does

From the project README:

SoLad is a peer-to-peer storage network for Solana, tackling transient log pruning (RPCs clear logs in days) and centralized explorer risks (e.g., Solscan). It supports Event streams data lake, off-chain assets (e.g., NFT metadata), and Layer 2 data at $6/GB for 20 years. It uses 1-10 shards, each with 3 nodes, for redundancy and speed (~10MB/s).
Original
cenwadike/solad
SuperStack fork
Blockchains/solad @ e79cfc0
Stars
2
Last push (original)
2025-07-08
Languages
Rust, TypeScript, JavaScript, Dockerfile, HTML, CSS
Detected stack
Anchorweb3.js
Categories
Other
Licence
MIT
Homepage
https://solad.vercel.app/

Architecture

50 source files. By extension: .rs ×32, .ts ×9, .jsx ×6, .js ×3.

Top-level directorySource files
contract20
node10
frontend9
sdk-ts7
sdk-rs4

On-chain programs: contract/programs/contract

Dapp: deploying

This fork is queued for its own live dapp on Sol.College, with a Devnet/Mainnet switch. It appears here, at /dapps/solad/, once it is deployed and checked on devnet. Until then, 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.

cenwadike/solad · contract/programs/contract/src/instructions/upload_data.rs L313–336 @ e79cfc0 · MIT · original
        payer: ctx.accounts.payer.key(),        nodes: updated_nodes,        storage_duration_days,        timestamp: Clock::get()?.unix_timestamp,    });     Ok(())} #[derive(Accounts)]#[instruction(data_hash: String, size_bytes: u64, shard_count: u8, storage_duration_days: u64)]pub struct UploadData<'info> {    #[account(        init_if_needed,        payer = payer,        space=size_of::<UserUploadKeys>() + 8 + (config.max_user_uploads as usize * 32),        seeds = [USER_UPLOAD_KEYS_SEED, payer.key().as_ref()],        bump    )]    pub user_upload_keys: Box<Account<'info, UserUploadKeys>>,    #[account(        init,        payer = payer,        space = 8 + 64 + 8 + 1 + 8 + 32 + 8 + 8 + 8 + 8 + (146 * shard_count as usize),
  1. Line 316 timestamp: Clock::get()?.unix_timestamp,: Reads on-chain time from the Clock sysvar.
  2. Line 326 init_if_needed,: Creates the account only if it doesn't exist. Watch for re-initialisation bugs.
  3. Line 329 seeds = [USER_UPLOAD_KEYS_SEED, payer.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  4. Line 330 bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  5. Line 334 init,: Creates and pays for a new account. Space and payer must be specified.
cenwadike/solad · contract/programs/contract/src/instructions/claim_rewards.rs L73–96 @ e79cfc0 · MIT · original
            .ok_or(SoladError::MathOverflow)?            / 100;        let epoch_lamports = endowment_lamports            .checked_div(config.epochs_total)            .ok_or(SoladError::MathOverflow)?;        if node_count == 1 || upload.shard_count == 1 {            epoch_lamports        } else {            epoch_lamports // PoS ensures verified_count > 0        }    };     require!(reward >= 1000, SoladError::InsufficientReward);     let seeds = &[        ESCROW_SEED,        upload.data_hash.as_bytes(),        upload.payer.as_ref(),        &[ctx.accounts.escrow.bump],    ];    system_program::transfer(        CpiContext::new_with_signer(            ctx.accounts.system_program.to_account_info(),            system_program::Transfer {
  1. Line 76 .checked_div(config.epochs_total): Uses checked arithmetic so overflow fails instead of wrapping.
  2. Line 85 require!(reward >= 1000, SoladError::InsufficientReward);: An explicit validation that aborts the instruction with an error.
  3. Line 87 let seeds = &[: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  4. Line 91 &[ctx.accounts.escrow.bump],: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  5. Line 94 CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
cenwadike/solad · contract/programs/contract/src/instructions/request_replacement.rs L73–96 @ e79cfc0 · MIT · original
            if *key == node.key() {                *key = Pubkey::default();                break;            }        }         let stake_escrow_seeds = &[            STAKE_ESCROW_SEED,            node.owner.as_ref(),            &[ctx.accounts.stake_escrow.bump],        ];        system_program::transfer(            CpiContext::new_with_signer(                ctx.accounts.system_program.to_account_info(),                system_program::Transfer {                    from: ctx.accounts.stake_escrow.to_account_info(),                    to: ctx.accounts.owner.to_account_info(),                },                &[&stake_escrow_seeds[..]],            ),            node.stake_amount,        )?;         emit!(NodeExitedEvent {
  1. Line 79 let stake_escrow_seeds = &[: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  2. Line 82 &[ctx.accounts.stake_escrow.bump],: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  3. Line 85 CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
  4. Line 96 emit!(NodeExitedEvent {: Emits an Anchor event that clients can parse from the logs.
cenwadike/solad · contract/programs/contract/src/instructions/slash_timeout.rs L73–96 @ e79cfc0 · MIT · original
        / 100;    let treasury_amount = slash_amount        .checked_mul(90)        .ok_or(SoladError::MathOverflow)?        / 100;    let caller_amount = slash_amount        .checked_sub(treasury_amount)        .ok_or(SoladError::MathOverflow)?;     let stake_escrow_seeds = &[        STAKE_ESCROW_SEED,        exiting_node.owner.as_ref(),        &[exiting_stake_escrow.bump],    ];     if treasury_amount > 0 {        system_program::transfer(            CpiContext::new_with_signer(                ctx.accounts.system_program.to_account_info(),                system_program::Transfer {                    from: exiting_stake_escrow.to_account_info(),                    to: ctx.accounts.treasury.to_account_info(),                },                &[&stake_escrow_seeds[..]],
  1. Line 75 .checked_mul(90): Uses checked arithmetic so overflow fails instead of wrapping.
  2. Line 82 let stake_escrow_seeds = &[: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  3. Line 85 &[exiting_stake_escrow.bump],: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  4. Line 90 CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.

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
frontendnpm run devvite
frontendnpm run buildvite build
frontendnpm run linteslint .
sdk-tsnpm run testecho "Error: no test specified" && exit 1
contractnpm run testts-mocha --require ts-node/register --esm -p ./tsconfig.json -t 1000000 'tests/**/*.ts'
contractnpm run lintprettier */*.js "*/**/*{.js,.ts}" --check
contractanchor buildcompile programs and generate IDLs
contractanchor testlocal validator + test suite
contractanchor run testyarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts

What you could build from it