Full lesson
Epochnotes
What it does
From the project README:
Explore the registry · Ask your AI agent · Found in the field · Get started · Run the demo · Visual identity
- Original
- mskrad/Epochnotes
- SuperStack fork
- Blockchains/Epochnotes @
b9401ae - Stars
- 0
- Last push (original)
- 2026-11-04
- Languages
- TypeScript, HTML, JavaScript, Rust, Solidity, Go
- Detected stack
- @solana/kitAnchor
- Categories
- Other
- Licence
- MIT
Architecture
144 source files. By extension: .ts ×112, .sol ×13, .rs ×11, .go ×4, .js ×2, .py ×2.
| Top-level directory | Source files |
|---|---|
packages | 91 |
corpus | 47 |
(root) | 3 |
programs | 3 |
On-chain programs: programs/registry, programs/rent-example
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/epochnotes/. 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.
programs/registry/src/lib.rs L145–168 @ b9401ae · MIT · original /// The publisher's latest version when the entry was withdrawn. pub at_version: u64, pub slot: u64, pub bump: u8,} #[derive(Accounts)]pub struct Initialize<'info> { #[account(init, payer = admin, space = 8 + Config::INIT_SPACE, seeds = [b"config"], bump)] pub config: Account<'info, Config>, #[account(mut)] pub admin: Signer<'info>, pub system_program: Program<'info, System>,} #[derive(Accounts)]#[instruction(authority: Pubkey)]pub struct RegisterPublisher<'info> { #[account(seeds = [b"config"], bump = config.bump, has_one = admin @ RegistryError::NotAdmin)] pub config: Account<'info, Config>, #[account(init, payer = admin, space = 8 + Publisher::INIT_SPACE, seeds = [b"publisher", authority.as_ref()], bump)] pub publisher: Account<'info, Publisher>, #[account(mut)] pub admin: Signer<'info>,- Line 148
pub bump: u8,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 153
#[account(init, payer = admin, space = 8 + Config::INIT_SPACE, seeds = [b"config"], bump)]: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 153
#[account(init, payer = admin, space = 8 + Config::INIT_SPACE, seeds = [b"config"], bump)]: Creates and pays for a new account. Space and payer must be specified. - Line 156
pub admin: Signer<'info>,: Requires this account to have signed the transaction. - Line 163
#[account(seeds = [b"config"], bump = config.bump, has_one = admin @ RegistryError::NotAdm: Checks that a stored pubkey field matches the passed account.
programs/rent-example/src/lib.rs L13–36 @ b9401ae · MIT · original pub fn initialize(ctx: Context<Initialize>) -> Result<()> { ctx.accounts.vault.authority = ctx.accounts.authority.key(); Ok(()) } pub fn withdraw_excess(ctx: Context<WithdrawExcess>) -> Result<()> { withdraw_excess::withdraw_excess(ctx) }} #[derive(Accounts)]pub struct Initialize<'info> { #[account(init, payer = authority, space = 8 + Vault::INIT_SPACE, seeds = [b"vault", authority.key().as_ref()], bump)] pub vault: Account<'info, Vault>, #[account(mut)] pub authority: Signer<'info>, pub system_program: Program<'info, System>,} #[account]#[derive(InitSpace)]pub struct Vault { pub authority: Pubkey,- Line 26
#[account(init, payer = authority, space = 8 + Vault::INIT_SPACE, seeds = [b"vault", autho: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 26
#[account(init, payer = authority, space = 8 + Vault::INIT_SPACE, seeds = [b"vault", autho: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 26
#[account(init, payer = authority, space = 8 + Vault::INIT_SPACE, seeds = [b"vault", autho: Creates and pays for a new account. Space and payer must be specified. - Line 29
pub authority: Signer<'info>,: Requires this account to have signed the transaction.
programs/rent-example/src/withdraw_excess.rs L13–36 @ b9401ae · MIT · original use crate::Vault; #[derive(Accounts)]pub struct WithdrawExcess<'info> { /// Only the authority stored in the account may withdraw. #[account(mut, has_one = authority @ WithdrawExcessError::NotAuthority)] pub account: Account<'info, Vault>, #[account(mut)] pub authority: Signer<'info>,} pub fn withdraw_excess(ctx: Context<WithdrawExcess>) -> Result<()> { let account = ctx.accounts.account.to_account_info(); // The minimum is asked from the cluster at run time. A rate compiled into the program goes stale at every // step of the reduction, and the reduction is not guaranteed to be one-way. let minimum = Rent::get()?.minimum_balance(account.data_len()); let excess = account.lamports().saturating_sub(minimum); require!(excess > 0, WithdrawExcessError::NothingToWithdraw); // The program owns the account, so it may debit it directly; the account stays rent-exempt by construction. **account.try_borrow_mut_lamports()? -= excess; **ctx.accounts.authority.to_account_info().try_borrow_mut_lamports()? += excess; Ok(())- Line 19
#[account(mut, has_one = authority @ WithdrawExcessError::NotAuthority)]: Checks that a stored pubkey field matches the passed account. - Line 22
pub authority: Signer<'info>,: Requires this account to have signed the transaction. - Line 31
require!(excess > 0, WithdrawExcessError::NothingToWithdraw);: An explicit validation that aborts the instruction with an error.
packages/core/src/rent-template.ts L73–96 @ b9401ae · MIT · original use crate::${accountType}; #[derive(Accounts)]pub struct WithdrawExcess<'info> { /// Only the authority stored in the account may withdraw. #[account(mut, has_one = ${authorityField} @ WithdrawExcessError::NotAuthority)] pub account: Account<'info, ${accountType}>, #[account(mut)] pub ${authorityField}: Signer<'info>,} pub fn withdraw_excess(ctx: Context<WithdrawExcess>) -> Result<()> { let account = ctx.accounts.account.to_account_info(); // The minimum is asked from the cluster at run time. A rate compiled into the program goes stale at every // step of the reduction, and the reduction is not guaranteed to be one-way. let minimum = Rent::get()?.minimum_balance(account.data_len()); let excess = account.lamports().saturating_sub(minimum); require!(excess > 0, WithdrawExcessError::NothingToWithdraw); // The program owns the account, so it may debit it directly; the account stays rent-exempt by construction. **account.try_borrow_mut_lamports()? -= excess; **ctx.accounts.${authorityField}.to_account_info().try_borrow_mut_lamports()? += excess; Ok(())- Line 79
#[account(mut, has_one = ${authorityField} @ WithdrawExcessError::NotAuthority)]: Checks that a stored pubkey field matches the passed account. - Line 82
pub ${authorityField}: Signer<'info>,: Requires this account to have signed the transaction. - Line 91
require!(excess > 0, WithdrawExcessError::NothingToWithdraw);: An explicit validation that aborts the instruction with an error.
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 |
|---|---|---|
. | npm run build | tsc -b |
. | npm run test | vitest run |
. | npm run lint | npm run typecheck:tests && eslint . && prettier --check . && node scripts/check-language.mjs |
packages/cli | npm run test | vitest run --root ../.. packages/cli/test |
packages/core | npm run test | vitest run --root ../.. packages/core/test |
. | anchor build | compile programs and generate IDLs |
. | anchor test | local validator + test suite |
. | anchor run test | npx vitest run --config vitest.onchain.config.ts --reporter=verbose |
. | cargo build | Rust workspace build |
. | cargo test | Rust tests |
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)