Sol.College

Full lesson

ecosnap

Gamified application on Solana aimed at monetizing everyday trash collection, motivating users to sort waste and clean up their city's streets in return for rewards.

What it does

From the project README:

Pick up litter, snap a photo, earn points — verified by a community vote and settled on Solana.
Original
RedDuck-Software/ecosnap
SuperStack fork
Blockchains/ecosnap @ 3f4b96a
Stars
0
Last push (original)
2026-08-06
Languages
TypeScript, Rust, JavaScript, Dockerfile, CSS, HTML
Detected stack
AnchorMetaplexweb3.js
Categories
Other
Licence
MIT

Architecture

273 source files. By extension: .ts ×152, .tsx ×77, .js ×22, .rs ×22.

Top-level directorySource files
apps221
packages51
(root)1

On-chain programs: apps/contracts/solana/programs/gc, apps/contracts/solana/programs/nft

Dapp: deploying

This fork is queued for its own live dapp on Sol.College, with a Devnet/Mainnet switch. It appears here, at /dapps/ecosnap/, 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.

RedDuck-Software/ecosnap · apps/contracts/solana/programs/gc/src/instructions/new_root.rs L1–24 @ 3f4b96a · MIT · original
use crate::state::GlobalState;use crate::state::RootState;use anchor_lang::prelude::*; #[derive(Accounts)]#[instruction(merkle_uuid: [u8; 16])]pub struct NewRoot<'info> {    #[account(mut)]    pub authority: Signer<'info>,     #[account(        init,        payer = authority,        space = RootState::MEM_LENGTH,        seeds = [RootState::SEED, global_state.key().as_ref(), &merkle_uuid],        bump    )]    pub root_state: Account<'info, RootState>,     #[account(mut)]    pub global_state: Account<'info, GlobalState>,     pub system_program: Program<'info, System>,}
  1. Line 9 pub authority: Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 12 init,: Creates and pays for a new account. Space and payer must be specified.
  3. Line 15 seeds = [RootState::SEED, global_state.key().as_ref(), &merkle_uuid],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  4. Line 16 bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
RedDuck-Software/ecosnap · apps/contracts/solana/programs/nft/src/instructions/create_mint_account.rs L37–60 @ 3f4b96a · MIT · original
    pub merkle_uuid: [u8; 16],    pub proofs: Vec<[u8; 32]>,} #[derive(Accounts)]pub struct CreateMintAccount<'info> {    #[account(mut)]    pub payer: Signer<'info>,     /// CHECK:    #[account(        init_if_needed,        seeds = [VAULT_PDA_SEED],        bump,        payer = payer,        space = 0    )]    pub authority: AccountInfo<'info>,     #[account(            init_if_needed,            seeds = [MINT_ACCOUNT_SEED, nft_global_state.key().as_ref()],            bump,            payer = payer,
  1. Line 44 pub payer: Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 48 init_if_needed,: Creates the account only if it doesn't exist. Watch for re-initialisation bugs.
  3. Line 49 seeds = [VAULT_PDA_SEED],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  4. Line 50 bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
RedDuck-Software/ecosnap · apps/frontend/web/src/hooks/mutations/solana/use-mint-achievement.ts L49–72 @ 3f4b96a · MIT · original
//       const tx = await program.methods//         .create_mint_account(//           new PublicKey(proofs.leaf.user),//           new BN(proofs.leaf.amount),//           'name',//           'symb',//           'uri',//           Array.from(new Uint8Array(toBinaryUUID(achievementId))),//           Array.from(new Uint8Array(toBinaryUUID(merkleTreeId))),//           proofs.proof.map((v) => Array.from(v))//         )//         .accounts({//           payer: publicKey,//           global_state: gcGlobalState,//           nft_global_state: nftGlobalState,//           mint_token_account: mintTokenAccount,//           root_account: gcRootState,//         })//         .transaction(); //       const txHash = await sendTransaction(tx, connection); //       const latestBlockHash = await connection.getLatestBlockhash({//         commitment: 'finalized',
  1. Line 64 // mint_token_account: mintTokenAccount,: Mints new tokens. The mint authority must sign.
  2. Line 69 // const txHash = await sendTransaction(tx, connection);: Submits the signed transaction to the cluster.
  3. Line 71 // const latestBlockHash = await connection.getLatestBlockhash({: Fetches a recent blockhash. Transactions expire if it gets too old.
RedDuck-Software/ecosnap · apps/contracts/solana/programs/gc/src/instructions/initialize_global_state.rs L1–24 @ 3f4b96a · MIT · original
use crate::state::GlobalState;use anchor_lang::prelude::*; #[derive(Accounts)]pub struct InitializeGlobalState<'info> {    #[account(mut)]    pub signer: Signer<'info>,     #[account(        init,        payer = signer,        space = GlobalState::MEM_LENGTH    )]    pub global_state: Account<'info, GlobalState>,     pub system_program: Program<'info, System>,} pub fn handle(ctx: Context<InitializeGlobalState>, authority: Pubkey) -> Result<()> {    let state = &mut ctx.accounts.global_state;    state.authority = authority;     Ok(())}
  1. Line 7 pub signer: 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.

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 buildturbo build --filter='!solana-contracts'
.npm run lintturbo lint
packages/providersnpm run devyarn build
packages/providersnpm run buildrun-p bundle type:gen
packages/providersnpm run lintyarn lint:prettier && yarn lint:typescript && yarn lint:es
packages/storagenpm run devyarn build
packages/storagenpm run buildrun-p bundle type:gen
packages/storagenpm run lintyarn lint:prettier && yarn lint:typescript && yarn lint:es
apps/contracts/solanaanchor buildcompile programs and generate IDLs
apps/contracts/solanaanchor testlocal validator + test suite
apps/contracts/solanaanchor run testyarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.test.ts
apps/contracts/solanaanchor run inityarn ts-node scripts/init.ts

What you could build from it