Sol.College

Full lesson

viral_sync

Civic forecasting and sponsor-funded action rewards with verifiable receipt settlement on Solana devnet.

What it does

From the project README:

Verified civic forecasting and sponsor-funded action rewards, settled with Viral Sync receipt evidence on Solana.
Original
dantwoashim/viral_sync
SuperStack fork
Blockchains/viral_sync @ 2ad0c55
Stars
0
Last push (original)
2026-08-12
Languages
TypeScript, Rust, JavaScript, CSS, Shell
Detected stack
AnchorNext.jsweb3.js
Categories
Infrastructure & Tooling
Licence
MIT
Homepage
https://viralsync1.vercel.app

Architecture

144 source files. By extension: .ts ×73, .tsx ×38, .rs ×33.

Top-level directorySource files
app78
programs33
scripts24
tests6
sdk1
examples1
relayer1

On-chain programs: programs/viral_sync

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/viral-sync/. 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.

dantwoashim/viral_sync · programs/viral_sync/src/instructions/merchant_init.rs L1–23 @ 2ad0c55 · MIT · original
use anchor_lang::prelude::*;use anchor_spl::token_2022::{self, spl_token_2022::{extension::{transfer_hook, PodStateWithExtensions}, pod::PodMint}};use anchor_spl::token_interface::Mint;use crate::errors::ViralSyncError;use crate::state::merchant_config::MerchantConfig; // Phase 1: create_mint_and_config#[derive(Accounts)]pub struct CreateMintAndConfig<'info> {    #[account(        init,        payer = merchant,        space = 8 + 200,         seeds = [b"merchant_v4", mint.key().as_ref()],        bump    )]    pub merchant_config: Account<'info, MerchantConfig>,        #[account(mut)]    pub merchant: Signer<'info>,        // The mint is pre-created with Token-2022 extensions by the deployment runner.    pub mint: InterfaceAccount<'info, Mint>,
  1. Line 2 use anchor_spl::token_2022::{self, spl_token_2022::{extension::{transfer_hook, PodStateWit: Targets the Token-2022 program instead of the original Token Program.
  2. Line 11 init,: Creates and pays for a new account. Space and payer must be specified.
  3. Line 14 seeds = [b"merchant_v4", mint.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  4. Line 15 bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  5. Line 20 pub merchant: Signer<'info>,: Requires this account to have signed the transaction.
dantwoashim/viral_sync · programs/viral_sync/src/instructions/session_management.rs L37–60 @ 2ad0c55 · MIT · original
        // Bound the session tightly to prevent infinite authority    require!(expires_at > Clock::get()?.unix_timestamp, ViralSyncError::TokensExpired);         session.bump = ctx.bumps.session_key;    session.authority = ctx.accounts.authority.key();    session.target_generation = gen.key();    session.delegate = ctx.accounts.delegate.key();    session.expires_at = expires_at;    session.max_tokens_per_session = max_tokens_per_session;    session.tokens_spent = 0;    session.is_active = true;        Ok(())} #[derive(Accounts)]pub struct RevokeSessionKey<'info> {    #[account(        mut,         close = authority,         has_one = authority    )]    pub session_key: Account<'info, SessionKey>,
  1. Line 39 require!(expires_at > Clock::get()?.unix_timestamp, ViralSyncError::TokensExpired);: An explicit validation that aborts the instruction with an error.
  2. Line 39 require!(expires_at > Clock::get()?.unix_timestamp, ViralSyncError::TokensExpired);: Reads on-chain time from the Clock sysvar.
  3. Line 41 session.bump = ctx.bumps.session_key;: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  4. Line 57 close = authority,: Closes the account at the end of the instruction and refunds its lamports.
  5. Line 58 has_one = authority: Checks that a stored pubkey field matches the passed account.
dantwoashim/viral_sync · programs/viral_sync/src/instructions/causal_commerce.rs L49–72 @ 2ad0c55 · MIT · original
    #[account(        mut,        has_one = merchant_authority @ ViralSyncError::AccessDenied,        constraint = merchant_config.status == CausalMerchantStatus::Active @ ViralSyncError::InvalidState,    )]    pub merchant_config: Account<'info, CausalMerchantConfig>,     #[account(mut)]    pub merchant_authority: Signer<'info>,     pub terminal_authority: Signer<'info>,     #[account(        init,        payer = merchant_authority,        space = TerminalDevice::SIZE,        seeds = [            TerminalDevice::SEED_PREFIX,            merchant_config.key().as_ref(),            terminal_authority.key().as_ref(),        ],        bump    )]    pub terminal_device: Account<'info, TerminalDevice>,
  1. Line 51 has_one = merchant_authority @ ViralSyncError::AccessDenied,: Checks that a stored pubkey field matches the passed account.
  2. Line 57 pub merchant_authority: Signer<'info>,: Requires this account to have signed the transaction.
  3. Line 62 init,: Creates and pays for a new account. Space and payer must be specified.
  4. Line 65 seeds = [: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  5. Line 70 bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
dantwoashim/viral_sync · programs/viral_sync/src/instructions/bond_management.rs L49–72 @ 2ad0c55 · MIT · original
    require!(!bond.is_locked, ViralSyncError::InvalidState);    require!(        bond.bonded_lamports.saturating_sub(amount) >= bond.min_required_lamports,        ViralSyncError::InsufficientBalance    );        // Simulate Time Lock Check natively     // require!(Clock::get()?.unix_timestamp > bond.unlock_requested_at + 172800, ViralSyncError::AccessDenied);        bond.bonded_lamports = bond.bonded_lamports.checked_sub(amount).ok_or(ViralSyncError::MathOverflow)?;    // Native sublamports from bond vault to merchant...    Ok(())} #[derive(Accounts)]pub struct InitiateCloseMerchant<'info> {    #[account(mut, has_one = merchant)]    pub merchant_config: Account<'info, MerchantConfig>,        pub merchant: Signer<'info>,} pub fn initiate_close_merchant(ctx: Context<InitiateCloseMerchant>) -> Result<()> {    let config = &mut ctx.accounts.merchant_config;
  1. Line 49 require!(!bond.is_locked, ViralSyncError::InvalidState);: An explicit validation that aborts the instruction with an error.
  2. Line 56 // require!(Clock::get()?.unix_timestamp > bond.unlock_requested_at + 172800, ViralSyncErr: Reads on-chain time from the Clock sysvar.
  3. Line 58 bond.bonded_lamports = bond.bonded_lamports.checked_sub(amount).ok_or(ViralSyncError::Math: Uses checked arithmetic so overflow fails instead of wrapping.
  4. Line 65 #[account(mut, has_one = merchant)]: Checks that a stored pubkey field matches the passed account.
  5. Line 68 pub merchant: Signer<'info>,: Requires this account to have signed the transaction.

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 buildnpm run build --workspaces --if-present
.npm run lintnpm run lint --workspaces --if-present
appnpm run devnext dev
appnpm run startnext start
appnpm run buildnext build
appnpm run linteslint
sdknpm run buildtsc -p tsconfig.json --noEmit
relayernpm run devts-node --project tsconfig.json src/index.ts
relayernpm run startnode dist/index.js
relayernpm run buildtsc -p tsconfig.json
.anchor buildcompile programs and generate IDLs
.anchor testlocal validator + test suite
.anchor run testnpm exec -- ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts
.cargo buildRust workspace build
.cargo testRust tests

What you could build from it

Used in lessons