Sol.College

Full lesson

Etherings-public

EtheRings public source: Android MVP through live Solana Devnet Public Alpha 1.3.0a. Android app, game servers, Solana programs, tests, build guide and release history.

What it does

From the project README:

EtheRings is an Android game where your everyday steps help you build a collection of Rings. Walk to earn resources, decide how to use them, try your luck in Draw, and discover collectible Silver Ring NFTs that live in your Solana wallet.
Original
Alexrok4834/Etherings-public
SuperStack fork
Blockchains/Etherings-public @ 3e3f9fe
Stars
1
Last push (original)
2026-11-05
Languages
Java, TypeScript, JavaScript, Rust, PLpgSQL, CSS
Detected stack
@solana/kitAnchorweb3.js
Categories
Gaming
Licence
MIT
Homepage
https://etherings.xyz/

Architecture

447 source files. By extension: .ts ×257, .js ×167, .rs ×15, .tsx ×8.

Top-level directorySource files
backend259
backend-alpha161
programs15
frontend11
website1

On-chain programs: programs/eru-gateway-hook/gateway, programs/eru-gateway-hook/hook, programs/eru-gateway-hook/reward-distributor, programs/orao-classic-cpi/program, programs/silver-design-set, programs/silver-design-set-lifecycle/program, programs/silver-design-set-runtime/program, programs/silver-escrow

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/etherings-public/. 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.

Alexrok4834/Etherings-public · programs/eru-gateway-hook/reward-distributor/src/lib.rs L373–396 @ 3e3f9fe · MIT · original
        key(&data, 49..81)? != *destination.key {        return Err(ProgramError::InvalidAccountData);    }    let recipient = key(&data, 17..49)?;    drop(data);    let source_state = token(reserve)?;    let destination_state = token(destination)?;    let (expected_delegate, bump) = Pubkey::find_program_address(&[DELEGATE_SEED], program_id);    if delegate.key != &expected_delegate || source_state.owner !=        key(&config.try_borrow_data()?, 1..33)? ||        source_state.mint != *mint.key || source_state.delegate != COption::Some(*delegate.key) ||        source_state.delegated_amount < amount || destination_state.owner != recipient ||        destination_state.mint != *mint.key {        return Err(ProgramError::InvalidAccountData);    }    payout.try_borrow_mut_data()?[105] = 1;    let mut ix = spl_token_2022::instruction::transfer_checked(        token_program.key, reserve.key, mint.key, destination.key, delegate.key, &[], amount, 9)?;    ix.accounts.push(AccountMeta::new_readonly(*meta_list.key, false));    ix.accounts.push(AccountMeta::new_readonly(*sysvar.key, false));    ix.accounts.push(AccountMeta::new_readonly(*gateway_config.key, false));    ix.accounts.push(AccountMeta::new_readonly(*hook.key, false));    invoke_signed(&ix, &[reserve.clone(), mint.clone(), destination.clone(), delegate.clone(),        meta_list.clone(), sysvar.clone(), gateway_config.clone(), hook.clone(), token_program.clone()],
  1. Line 380 let (expected_delegate, bump) = Pubkey::find_program_address(&[DELEGATE_SEED], program_id): Derives a PDA from seeds and the program ID and returns the address with its canonical bump.
  2. Line 380 let (expected_delegate, bump) = Pubkey::find_program_address(&[DELEGATE_SEED], program_id): Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  3. Line 389 let mut ix = spl_token_2022::instruction::transfer_checked(: A checked token transfer, which also verifies the mint and decimals.
  4. Line 389 let mut ix = spl_token_2022::instruction::transfer_checked(: Targets the Token-2022 program instead of the original Token Program.
  5. Line 395 invoke_signed(&ix, &[reserve.clone(), mint.clone(), destination.clone(), delegate.clone(),: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
Alexrok4834/Etherings-public · programs/silver-marketplace/src/marketplace.rs L85–108 @ 3e3f9fe · MIT · original
        let bump = Pubkey::find_program_address(&[CONFIG_SEED_MARKET], program_id).1;        create_pda(config, vault, system, program_id, CONFIG_LEN,            &[CONFIG_SEED_MARKET, &[bump]])?;    } else {        check_config(program_id, config)?;        if read_u64(&config.try_borrow_data()?, 33)?.checked_add(1) != Some(version)        { return Err(ProgramError::InvalidInstructionData); }    }    let mut bytes = config.try_borrow_mut_data()?;    bytes[0] = 1;    bytes[1..33].copy_from_slice(vault.key.as_ref());    bytes[33..41].copy_from_slice(&version.to_le_bytes());    bytes[41..73].copy_from_slice(royalty.as_ref());    bytes[73..105].copy_from_slice(platform.as_ref());    bytes[105..137].copy_from_slice(silver.as_ref());    Ok(())} fn check_asset(program_id: &Pubkey, silver: &Pubkey, mint: &AccountInfo, state: &AccountInfo,    lifecycle: &AccountInfo, extra: &AccountInfo) -> Result<bool, ProgramError>{    if mint.owner != &spl_token_2022::id() || state.owner != silver        || extra.owner != silver        || *extra.key != get_extra_account_metas_address_and_bump_seed(mint.key, silver).0
  1. Line 85 let bump = Pubkey::find_program_address(&[CONFIG_SEED_MARKET], program_id).1;: Derives a PDA from seeds and the program ID and returns the address with its canonical bump.
  2. Line 85 let bump = Pubkey::find_program_address(&[CONFIG_SEED_MARKET], program_id).1;: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  3. Line 90 if read_u64(&config.try_borrow_data()?, 33)?.checked_add(1) != Some(version): Uses checked arithmetic so overflow fails instead of wrapping.
  4. Line 106 if mint.owner != &spl_token_2022::id() || state.owner != silver: Targets the Token-2022 program instead of the original Token Program.
Alexrok4834/Etherings-public · programs/silver-escrow/src/lib.rs L49–72 @ 3e3f9fe · MIT · original
            Pubkey::find_program_address(&[b"silver-state", mint.as_ref()], &SILVER_PROGRAM).0                == ctx.accounts.box_state.key(),            ProofError::WrongBox        );        require!(            Pubkey::find_program_address(&[b"silver-collection"], &SILVER_PROGRAM)                .0                .to_bytes()                == state[68..100],            ProofError::WrongBox        );        let cooldown_until = i64::from_le_bytes(state[196..204].try_into().unwrap());        require!(            Clock::get()?.unix_timestamp >= cooldown_until,            ProofError::Cooldown        );        drop(state);        require!(            ctx.accounts.box_mint.supply == 1 && ctx.accounts.box_mint.decimals == 0,            ProofError::WrongBox        );        require!(            ctx.accounts.box_mint.to_account_info().owner == &spl_token_2022::id(),            ProofError::WrongBox
  1. Line 49 Pubkey::find_program_address(&[b"silver-state", mint.as_ref()], &SILVER_PROGRAM).0: Derives a PDA from seeds and the program ID and returns the address with its canonical bump.
  2. Line 53 require!(: An explicit validation that aborts the instruction with an error.
  3. Line 62 Clock::get()?.unix_timestamp >= cooldown_until,: Reads on-chain time from the Clock sysvar.
  4. Line 71 ctx.accounts.box_mint.to_account_info().owner == &spl_token_2022::id(),: Targets the Token-2022 program instead of the original Token Program.
Alexrok4834/Etherings-public · programs/silver-identity/src/lib.rs L181–204 @ 3e3f9fe · MIT · original
    let (collection, _) = Pubkey::find_program_address(&[COLLECTION_SEED], program_id);    let mint_space = ExtensionType::try_calculate_account_len::<Mint>(&[        ExtensionType::MetadataPointer,        ExtensionType::TransferHook,    ])?;    let mint_bump_seed = [mint_bump];    let mint_seeds: &[&[u8]] = &[MINT_SEED, issuance_id, &mint_bump_seed];    let authority_bump_seed = [authority_bump];    let authority_seeds: &[&[u8]] = &[AUTHORITY_SEED, &authority_bump_seed];    create_pda(mint, admin, system, token_program.key, mint_space, mint_seeds)?;    let funded_size = mint_space        .checked_add(768)        .ok_or(ProgramError::InvalidAccountData)?;    let final_rent = Rent::get()?.minimum_balance(funded_size);    if mint.lamports() < final_rent {        invoke(            &system_instruction::transfer(admin.key, mint.key, final_rent - mint.lamports()),            &[admin.clone(), mint.clone(), system.clone()],        )?;    }    invoke(        &spl_token_2022::extension::metadata_pointer::instruction::initialize(            token_program.key,            mint.key,
  1. Line 181 let (collection, _) = Pubkey::find_program_address(&[COLLECTION_SEED], program_id);: Derives a PDA from seeds and the program ID and returns the address with its canonical bump.
  2. Line 192 .checked_add(768): Uses checked arithmetic so overflow fails instead of wrapping.
  3. Line 196 invoke(: Makes a cross-program invocation into another program.
  4. Line 202 &spl_token_2022::extension::metadata_pointer::instruction::initialize(: Targets the Token-2022 program instead of the original Token Program.

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
backend-alphanpm run buildnode --check src/json-rpc.js && node --check src/admin-operational-stats.js && node --check src/admin-dashboard.js && no
backend-alphanpm run testnode --test test/*.test.js
frontendnpm run devvite --host 127.0.0.1
frontendnpm run buildtsc -b && vite build
frontendnpm run testnode --test --import tsx "src/**/*.spec.tsx"
backendnpm run startnode dist/main.js
backendnpm run buildtsc -p tsconfig.build.json
backendnpm run testnode --test --import tsx "src/**/*.spec.ts"

What you could build from it

Used in lessons