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 directory | Source files |
|---|---|
contract | 20 |
node | 10 |
frontend | 9 |
sdk-ts | 7 |
sdk-rs | 4 |
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.
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),- Line 316
timestamp: Clock::get()?.unix_timestamp,: Reads on-chain time from the Clock sysvar. - Line 326
init_if_needed,: Creates the account only if it doesn't exist. Watch for re-initialisation bugs. - 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. - Line 330
bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 334
init,: Creates and pays for a new account. Space and payer must be specified.
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 {- Line 76
.checked_div(config.epochs_total): Uses checked arithmetic so overflow fails instead of wrapping. - Line 85
require!(reward >= 1000, SoladError::InsufficientReward);: An explicit validation that aborts the instruction with an error. - Line 87
let seeds = &[: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 91
&[ctx.accounts.escrow.bump],: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 94
CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
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 {- Line 79
let stake_escrow_seeds = &[: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 82
&[ctx.accounts.stake_escrow.bump],: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 85
CpiContext::new_with_signer(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address. - Line 96
emit!(NodeExitedEvent {: Emits an Anchor event that clients can parse from the logs.
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[..]],- Line 75
.checked_mul(90): Uses checked arithmetic so overflow fails instead of wrapping. - Line 82
let stake_escrow_seeds = &[: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 85
&[exiting_stake_escrow.bump],: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - 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.
| Directory | Command | Runs |
|---|---|---|
frontend | npm run dev | vite |
frontend | npm run build | vite build |
frontend | npm run lint | eslint . |
sdk-ts | npm run test | echo "Error: no test specified" && exit 1 |
contract | npm run test | ts-mocha --require ts-node/register --esm -p ./tsconfig.json -t 1000000 'tests/**/*.ts' |
contract | npm run lint | prettier */*.js "*/**/*{.js,.ts}" --check |
contract | anchor build | compile programs and generate IDLs |
contract | anchor test | local validator + test suite |
contract | anchor run test | yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts |
What you could build from it
- Fork the SuperStack copy and extend it within the Other gaps listed on the idea page.
- Learn the pattern first: Program Derived Addresses (PDAs)
- Learn the pattern first: Cross-Program Invocations (CPIs)