From ba6fb7ce1f75da8495e273ef925f1f141c033186 Mon Sep 17 00:00:00 2001 From: resourcefulmind Date: Sun, 19 Oct 2025 13:27:59 +0100 Subject: [PATCH 1/5] docs: add Slides & Facilitator Notes Integration section to 3h Anchor Workshop README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added detailed guidance on how to use the official Google Slides deck with built-in speaker notes. - Clarified slide block structure (A–F) and how it aligns with README learning objectives. - Included presenter workflow (before, during, after) to standardize delivery. - Ensures facilitators understand how slides, notes, and curriculum align for consistent teaching experience. --- .../workshop/anchor-3h/README.md | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/README.md diff --git a/courses/anchor-and-programs/workshop/anchor-3h/README.md b/courses/anchor-and-programs/workshop/anchor-3h/README.md new file mode 100644 index 0000000..bca35e9 --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/README.md @@ -0,0 +1,220 @@ +# 🧭 Solana Anchor & dApp Workshop (3-Hour) + +## 🎯 Purpose + +This workshop introduces developers to Solana’s on-chain programming model using the **Anchor framework**, guiding them from setup to deployment and client interaction. +It is designed for university builders, hackathon participants, and developers transitioning from Web2 or EVM backgrounds. + +## 🧩 Scope + +Participants will build, test, and interact with a simple on-chain program (counter example) while learning Solana’s account model, PDAs, and IDL-based client interaction. +The session combines live coding, conceptual slides, and quick quizzes for reinforcement. + +**Duration:** 3 hours +**Difficulty:** Beginner β†’ Intermediate +**Delivery Mode:** Live + Hands-On (Localnet) + +--- + +## πŸŽ“ Learning Outcomes + +By the end of the workshop, learners will be able to: + +1. Set up and validate their local **Solana + Anchor** development environment. +2. Initialize and structure an **Anchor workspace** (programs, accounts, and instructions). +3. Explain how Solana’s **account model** underpins program state. +4. Implement and derive **Program Derived Addresses (PDAs)** securely in Anchor. +5. Compile, deploy, and test a working Solana program using `anchor build` and `anchor test`. +6. Use the generated **IDL** to interact with their program via a TypeScript client. +7. Apply key **security practices**, including dev-wallet separation and safe testing clusters. +8. Troubleshoot common **build, deploy, and validator errors**. +9. Extend their program logic and connect to broader Solana learning tracks. + +--- + +### 🎞️ Slides & Facilitator Notes Integration + +This workshop includes a **Google Slides deck** with embedded speaker notes that guide facilitators through each concept, code demo, and discussion point. + +#### πŸ“˜ Workshop Slides + +- Access the official Solana-branded slides here: + πŸ‘‰ [View Slides on Google Drive](https://docs.google.com/presentation/d/1b-Bb9AJESqjWV7A7p9I2AzLEQsu95GIgGO2frUxQ3BI/edit?usp=sharing) +- The deck is structured into six teaching blocks that align with this README: + - 🟒 Block A β€” On-Ramp & Environment + - 🟣 Block B β€” Program Skeleton & Accounts + - πŸ”΅ Block C β€” PDA Lite + - 🟠 Block D β€” Build β†’ Deploy β†’ Test + - 🟑 Block E β€” IDL & Client Interaction + - 🟀 Block F β€” Wrap-Up & Next Steps +- Each block maps directly to the workshop flow and learning objectives. + +#### πŸ—’οΈ Speaker Notes as Facilitator Guide + +- The **speaker notes within the slides** contain: + - Purpose and learning outcome for each slide. + - Key talking points and analogies. + - Code cues and timing guidance. + - Engagement prompts and expected learner questions. +- Use **Presenter View** during delivery to see these private notes while presenting live. + +#### 🧭 How to Use Slides + +1. **Before the workshop:** Review the slides alongside this README to understand flow and setup. +2. **During delivery:** Present from Google Slides in *Presenter View* to see your private notes. +3. **After delivery:** Export the deck as PDF with notes to share a recap or upload to your LMS. + +> πŸ’‘ *Tip:* The slides carry the visuals, the speaker notes carry your voice, and the curriculum ensures every session feels unified across the Solana Foundation’s learning network. + +--- + +## 🧱 Workshop Flow + +### 🟒 Block A β€” On-Ramp & Environment +**Purpose:** Equip learners with the mindset and tools to begin Solana development. +**Slides:** 1–6 +**Learning Outcome Alignment:** (1, 3, 7) + +**Facilitator Notes:** +- Contrast Solana programs vs. dApps (backend vs. frontend logic). +- Verify `rustc`, `solana`, and `anchor` installation versions. +- Run a local validator (`solana-test-validator`) and confirm via `solana balance`. +- Reinforce wallet hygiene: dev wallet β‰  personal wallet. +- Engagement cue: *β€œWhy is separating data (accounts) from logic (programs) safer?”* + +--- + +### 🟣 Block B β€” Program Skeleton & Accounts +**Purpose:** Introduce Anchor’s workspace, macros, and the Solana account model. +**Slides:** 7–13 +**Learning Outcome Alignment:** (2, 3, 5) + +**Facilitator Notes:** +- Explain the purpose of `lib.rs`, `Cargo.toml`, and `Anchor.toml`. +- Use `anchor init counter` live and show the generated folder structure. +- Code cue: implement `initialize()` and explain the `#[derive(Accounts)]` context. +- Highlight that programs are *stateless*β€”state lives in accounts. +- Quick check: learners describe what `init`, `mut`, `payer`, and `space` mean. + +--- + +### πŸ”΅ Block C β€” PDA Lite (Program Derived Addresses) +**Purpose:** Teach secure, deterministic account creation via PDAs. +**Slides:** 14–19 +**Learning Outcome Alignment:** (4, 7) + +**Facilitator Notes:** +- Conceptualize PDAs: β€œaccounts owned by programs, not people.” +- Show `Pubkey::find_program_address` β†’ seeds + bump β†’ PDA. +- Add PDA logic to `#[account(seeds = [...], bump)]` and test. +- Engagement: ask why PDAs eliminate the need for private keys. +- Safety tip: discuss deterministic addresses and ownership checks (`has_one`). + +--- + +### 🟠 Block D β€” Build β†’ Deploy β†’ Test +**Purpose:** Compile, deploy, and validate program logic. +**Slides:** 20–25 +**Learning Outcome Alignment:** (5, 8) + +**Facilitator Notes:** +- Build flow: `anchor build` β†’ `.so` binary β†’ `target/deploy` β†’ IDL JSON. +- Emphasize syncing program ID: `declare_id!()` ↔ `Anchor.toml`. +- Deploy with `anchor deploy --provider.cluster localnet`. +- Run `anchor test` to execute both Rust and TypeScript tests. +- Troubleshooting: ID drift, missing IDL, validator reset (`Ctrl+C` β†’ restart). + +--- + +### 🟑 Block E β€” IDL & Client Interaction +**Purpose:** Bridge on-chain logic to a frontend client via the IDL. +**Slides:** 26–31 +**Learning Outcome Alignment:** (6, 7) + +**Facilitator Notes:** + +- Define IDL: β€œInterface Definition Language describing your program’s API.” +- Show how `anchor build` auto-generates the `IDL JSON` and client bindings. +- Code cue: + ```ts + await program.methods.increment() + .accounts({ counter: counterPubkey }) + .rpc() + ``` +- Emphasize how Anchor abstracts complex RPC calls into simple methods. +- Reinforce security: always use dev wallets for frontend testing. +- Quick quiz: β€œWhat belongs in the IDL vs. what stays in Rust code?” + +βΈ» + +### 🟀 Block F β€” Wrap-Up & Next Steps + +**Purpose**: Reinforce learning and connect to the broader Solana curriculum. +**Slides**: 32–36 +**Learning Outcome Alignment**: (9) + +**Facilitator Notes:** + +- Recap flow: build β†’ deploy β†’ test β†’ interact. +- Share common pitfalls: wrong cluster, keypair mismatch, insufficient SOL. +- Discussion prompt: β€œWhat could you build next using a PDA?” +- Mention advanced workshops: Full-Stack, Quant, Security, and Web Dev. +- End with resources and appreciation for participation. + +βΈ» + +### βš™οΈ Presenter Prep & Environment Setup + +Pre-Session Checklist: + +- rustc, cargo, solana, and anchor versions verified. +- Local validator running (solana-test-validator). +- Dev wallet configured (~/.config/solana/dev.json). +- solana airdrop 4 successful on localnet. +- anchor build completes without warnings. +- Program ID matches in both lib.rs and Anchor.toml. + +Command References: +```bash +solana config get +solana address -k target/deploy/-keypair.json +anchor build +anchor deploy --provider.cluster localnet +anchor test +``` + +βΈ» + +### 🧰 Troubleshooting + +### βš™οΈ Troubleshooting Guide + +| 🧩 **Issue** | πŸ’‘ **Likely Cause** | πŸ› οΈ **Fix** | +|--------------|--------------------|-------------| +| **Program is not deployed** | Mismatch between `declare_id!()` and `Anchor.toml` | Copy the correct key from `target/deploy/-keypair.json` and redeploy. | +| **Account does not exist** | Local validator was restarted or wiped | Restart validator and re-run `anchor deploy`. | +| **Transaction simulation failed** | Wrong cluster or missing SOL balance | Run `solana airdrop 4` or check `solana config get`. | +| **IDL not found** | IDL JSON not generated | Run `anchor build` to regenerate the IDL. | +| **Command not found: anchor** | Anchor CLI not in PATH | Run `source ~/.cargo/env` or add Cargo bin to your `.zshrc`. | + +βΈ» + +### πŸ”— Resources & Further Learning + +- πŸ“˜ [The Anchor Book](https://www.anchor-lang.com/docs) +- 🧭 [Solana Docs](https://solana.com/docs) +- 🧩 [Solana Cookbook](https://solanacookbook.com) +- πŸ” [Solana Security Guidelines](https://www.helius.dev/blog/a-hitchhikers-guide-to-solana-program-security) +- 🧠 [Solana Playground](https://beta.solpg.io) +- πŸ’¬ [Solana Discord](https://discord.gg/solana) +- πŸŽ“ [Solana Bootcamp](https://github.com/solana-developers/developer-bootcamp-2024) + +--- + +### 🏁 Summary + +This README provides facilitators and learners with a complete guide to the **Solana Anchor & dApp Workshop** β€” aligning slides, demos, and learning objectives in one document. + +It’s designed to be **reproducible, discoverable, and educationally sound**, so any instructor, anywhere, can deliver the same world-class developer experience. + +--- \ No newline at end of file From ba0fe6fc5f6446a16933e6658dad2aee249d63d1 Mon Sep 17 00:00:00 2001 From: resourcefulmind Date: Tue, 21 Oct 2025 13:23:40 +0100 Subject: [PATCH 2/5] feat: Complete 3h Anchor Workshop scaffold with PDA integration and client interaction - Add comprehensive PDA program logic with initialize_pda and update_pda functions - Implement State account struct with proper PDA constraints and seed management - Create robust test suite covering both regular counter and PDA operations - Build functional client script with IDL-based program interaction - Add comprehensive README with live coding guidance and troubleshooting - Include complete error handling for common deployment and testing scenarios - Verify end-to-end functionality with build, deploy, test, and client execution - Add workshop completion checklist and learning outcome definitions Features: - PDA derivation using findProgramAddressSync - Type-safe client interactions via generated IDL - Graceful error handling for existing PDA accounts - Production-ready code patterns for real dApp development - Instructor-friendly documentation with TODO markers for live coding All commands tested and verified for live workshop delivery. Ready for production use in 3-hour Solana Anchor & dApp workshops. --- .../workshop/anchor-3h/.gitignore | 35 ++ .../workshop/anchor-3h/code/README.md | 319 ++++++++++++++++++ .../anchor-3h/code/client/callProgram.ts | 117 +++++++ .../anchor-3h/code/client/counter.json | 204 +++++++++++ .../workshop/anchor-3h/code/client/counter.ts | 210 ++++++++++++ .../anchor-3h/code/client/package.json | 18 + .../anchor-3h/code/client/tsconfig.json | 21 ++ .../anchor-3h/code/counter/Anchor.toml | 22 ++ .../anchor-3h/code/counter/Cargo.toml | 14 + .../anchor-3h/code/counter/package.json | 26 ++ .../code/counter/programs/counter/Cargo.toml | 28 ++ .../code/counter/programs/counter/src/lib.rs | 83 +++++ .../anchor-3h/code/counter/tests/counter.ts | 133 ++++++++ .../anchor-3h/code/counter/tsconfig.json | 10 + 14 files changed, 1240 insertions(+) create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/.gitignore create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/README.md create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/client/callProgram.ts create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/client/counter.json create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/client/counter.ts create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/client/package.json create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/client/tsconfig.json create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/counter/Anchor.toml create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/counter/Cargo.toml create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/counter/package.json create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/counter/programs/counter/Cargo.toml create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/counter/programs/counter/src/lib.rs create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/counter/tests/counter.ts create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/counter/tsconfig.json diff --git a/courses/anchor-and-programs/workshop/anchor-3h/.gitignore b/courses/anchor-and-programs/workshop/anchor-3h/.gitignore new file mode 100644 index 0000000..8dbf221 --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/.gitignore @@ -0,0 +1,35 @@ +# Rust build artifacts +**/target/ +**/Cargo.lock + +# Node.js dependencies +**/node_modules/ +**/yarn.lock +**/package-lock.json + +# IDE files +**/.vscode/ +**/.idea/ +**/*.swp +**/*.swo + +# OS files +**/.DS_Store +**/Thumbs.db + +# Solana/Anchor specific +**/.anchor/ +**/test-ledger/ +**/keypairs/ + +# Environment files +**/.env +**/.env.local + +# Logs +**/*.log +**/logs/ + +# Temporary files +**/tmp/ +**/temp/ diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/README.md b/courses/anchor-and-programs/workshop/anchor-3h/code/README.md new file mode 100644 index 0000000..980bf81 --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/README.md @@ -0,0 +1,319 @@ +# Anchor 3-Hour Workshop - Live Scaffold + +**PDA Integration + IDL-Based Client Interaction** + +Welcome to the live coding scaffold for the 3-Hour Solana Anchor & dApp Workshop! This is your hands-on environment where we'll build upon the 1-hour counter program and add powerful PDA (Program Derived Address) functionality with real client interaction. + +## 🎯 What's New in 3h vs 1h + +### **PDA Mastery** +- **Deterministic Addresses**: Learn how PDAs create predictable, program-controlled accounts +- **Seed Management**: Master the art of crafting seeds for unique PDA derivation +- **Bump Validation**: Understand how bumps ensure PDA uniqueness and security + +### **Client Integration** +- **IDL-Based Interaction**: Use generated Interface Definition Language for type-safe program calls +- **Real dApp Patterns**: Experience how frontend applications communicate with Solana programs +- **State Management**: Fetch and display on-chain data in your client applications + +### **Production-Ready Concepts** +- **Account Validation**: Proper PDA constraints and ownership checks +- **Error Handling**: Robust client-side error management +- **Type Safety**: Leverage TypeScript for bulletproof program interactions + +## πŸš€ Quick Start + +### Prerequisites +- **Solana CLI**: `solana --version β‰₯ 1.18` +- **Anchor Framework**: `anchor --version β‰₯ 0.30` +- **Rust**: `rustc stable` +- **Node.js**: `node --version β‰₯ 16` + +### Verify Your Setup +Before starting, run these commands to ensure everything is ready: + +```bash +# Check Solana version (should be β‰₯ 1.18) +solana --version + +# Check Anchor version (should be β‰₯ 0.30) +anchor --version + +# Check Rust version +rustc --version + +# Check Node.js version (should be β‰₯ 16) +node --version +``` + +**If any command fails or shows an outdated version, please install/update before proceeding.** + +### Pre-flight Checklist +```bash +# 1. Set up localnet (if not running) +solana-test-validator --reset --quiet & + +# 2. Configure for localnet +solana config set --url localhost + +# 3. Verify localnet is running +solana cluster-version +``` + +## πŸ—οΈ Workshop Structure + +### **Hour 1-2: Program Development** +Building on the 1h counter program (if you haven't completed it, see the [1h Workshop](../anchor-1h/) first), we'll extend it with PDA functionality: + +```rust +// TODO: Add PDA state account +#[account] +pub struct State { + pub count: u64, +} + +// TODO: Implement PDA initialization +pub fn initialize_pda(ctx: Context) -> Result<()> { + let state = &mut ctx.accounts.state; + state.count = 0; + Ok(()) +} + +// TODO: Add PDA update functionality +pub fn update_pda(ctx: Context) -> Result<()> { + let state = &mut ctx.accounts.state; + state.count = state.count.checked_add(1).unwrap(); + Ok(()) +} +``` + +### **Hour 3: Client Integration** +We'll build a minimal client that interacts with our PDA program: + +```typescript +// TODO: Derive PDA using findProgramAddressSync +const [statePda, bump] = anchor.web3.PublicKey.findProgramAddressSync( + [Buffer.from("state"), payer.publicKey.toBuffer()], + program.programId +); + +// TODO: Initialize PDA from client +await program.methods + .initializePda() + .accounts({ + state: statePda, + payer: payer.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + }) + .rpc(); +``` + +## πŸ› οΈ Build & Deploy + +### **Step 1: Build the Program** +```bash +# From the code/ directory: +cd counter +anchor build +``` + +**Expected Output:** +``` +Finished `release` profile [optimized] target(s) in X.XXs +``` + +### **Step 2: Deploy to Localnet** +```bash +anchor deploy +``` + +**Expected Output:** +``` +Program Id: [YOUR_PROGRAM_ID] (will be different for each deployment) +Deploy success +``` + +### **Step 3: Run Tests** +```bash +anchor test +``` + +**Expected Output:** +``` +βœ… initialize β†’ increment β†’ fetch +βœ… PDA: derive β†’ initialize β†’ update β†’ fetch +``` + +## πŸ–₯️ Client Demo + +### **Step 1: Install Dependencies** +```bash +# From the code/ directory: +cd client +yarn install +``` + +### **Step 2: Run Client Script** +```bash +ANCHOR_PROVIDER_URL=http://localhost:8899 ANCHOR_WALLET=~/.config/solana/id.json yarn ts-node callProgram.ts +``` + +**Expected Output:** +``` +πŸš€ Starting Anchor 3h Workshop Client Demo +========================================== +πŸ“‹ Program ID: [YOUR_PROGRAM_ID] +πŸ‘€ Payer: [YOUR_WALLET_ADDRESS] +πŸ”‘ Derived PDA: [YOUR_PDA_ADDRESS] +πŸ“Š Bump: [YOUR_BUMP_VALUE] + +πŸ“ Initializing PDA... +βœ… PDA initialized! Count = 0 + +πŸ“ˆ Updating PDA (1st time)... +βœ… PDA updated! Count = 1 + +πŸ“ˆ Updating PDA (2nd time)... +βœ… PDA updated! Count = 2 + +πŸ“ˆ Updating PDA (3rd time)... +βœ… PDA updated! Count = 3 + +πŸŽ‰ Client demo completed successfully! +πŸ“Š Final PDA state: count = 3 +πŸ”— PDA address: [YOUR_PDA_ADDRESS] +``` + +## βœ… Workshop Completion Checklist + +Mark these off as you complete them: + +- [ ] Program builds successfully (`anchor build`) +- [ ] Program deploys to localnet (`anchor deploy`) +- [ ] All tests pass (`anchor test`) +- [ ] Client script runs successfully +- [ ] You can explain PDA derivation +- [ ] You understand IDL-based client interaction +- [ ] You can modify the PDA seeds and see the address change + +## πŸŽ“ Learning Outcomes + +By the end of this workshop, you'll understand: + +### **PDA Fundamentals** +- **Deterministic Derivation**: How seeds create predictable addresses +- **Bump Management**: Why bumps matter for PDA uniqueness +- **Account Constraints**: Proper validation in Anchor contexts + +### **Client Integration** +- **IDL Usage**: Type-safe program interaction patterns +- **State Fetching**: Reading on-chain data efficiently +- **Error Handling**: Robust client-side error management + +### **dApp Architecture** +- **Program-Client Communication**: How frontends interact with programs +- **State Management**: Managing application state across the blockchain +- **Production Patterns**: Real-world development practices + +## πŸ”§ Troubleshooting + +### **Program ID Mismatch** +If you see `DeclaredProgramIdMismatch` errors: +```bash +# Update the program ID in lib.rs +declare_id!("YOUR_ACTUAL_PROGRAM_ID"); + +# Update Anchor.toml +[programs.localnet] +counter = "YOUR_ACTUAL_PROGRAM_ID" +``` + +### **Localnet Validator Won't Start** +```bash +# Kill any existing validator +pkill solana-test-validator + +# Start fresh +solana-test-validator --reset --quiet & + +# Verify it's running +solana cluster-version +``` + +### **Client Connection Issues** +If the client can't connect: +```bash +# Verify localnet is running +solana cluster-version + +# Check wallet configuration +solana config get + +# Ensure you're on localnet +solana config set --url localhost +``` + +### **Yarn Install Fails** +```bash +# Clear cache and retry +yarn cache clean +yarn install + +# If still failing, try npm +npm install +``` + +### **Program Deploys But Tests Fail** +```bash +# Check if program is actually deployed +solana program show [YOUR_PROGRAM_ID] + +# Redeploy if needed +anchor deploy + +# Run tests again +anchor test +``` + +### **Build Errors** +If you encounter build issues: +```bash +# Clean and rebuild +anchor clean +anchor build + +# If Rust issues, update Rust +rustup update +``` + +### **Environment Variable Issues** +If client script fails with environment errors: +```bash +# Set environment variables explicitly +export ANCHOR_PROVIDER_URL=http://localhost:8899 +export ANCHOR_WALLET=~/.config/solana/id.json + +# Then run the script +yarn ts-node callProgram.ts +``` + +## πŸ“š Resources + +- **[Anchor Book](https://book.anchor-lang.com/)**: Comprehensive Anchor documentation +- **[Solana Docs - PDAs](https://solana.com/docs/core/pda)**: Official PDA documentation +- **[Solana Cookbook](https://solanacookbook.com/core-concepts/pdas.html)**: Practical PDA examples +- **[1h Workshop](../anchor-1h/)**: Foundation concepts + +## 🎯 Next Steps + +After completing this workshop: +1. **Explore Advanced PDAs**: Multi-seed PDAs, cross-program invocations +2. **Build Full dApps**: Add React frontends, wallet integration +3. **Deploy to Mainnet**: Take your programs to production +4. **Join the Community**: Connect with other Solana developers + +--- + +**Ready to build the future of decentralized applications? Let's dive in!** πŸš€ + +*This scaffold is designed for live coding sessions. All commands are tested and ready to run.* diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/client/callProgram.ts b/courses/anchor-and-programs/workshop/anchor-3h/code/client/callProgram.ts new file mode 100644 index 0000000..35f71c6 --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/client/callProgram.ts @@ -0,0 +1,117 @@ +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import idl from "./counter.json"; + +async function main() { + console.log("πŸš€ Starting Anchor 3h Workshop Client Demo"); + console.log("=========================================="); + + // Set up provider for localnet + const provider = anchor.AnchorProvider.env(); + anchor.setProvider(provider); + + // Load the program using the generated IDL + const program = new Program(idl as anchor.Idl, provider); + console.log("πŸ“‹ Program ID:", program.programId.toString()); + + // Get the payer wallet + const payer = provider.wallet as anchor.Wallet; + console.log("πŸ‘€ Payer:", payer.publicKey.toString()); + + // Derive PDA using findProgramAddressSync + const [statePda, bump] = anchor.web3.PublicKey.findProgramAddressSync( + [Buffer.from("state"), payer.publicKey.toBuffer()], + program.programId + ); + console.log("πŸ”‘ Derived PDA:", statePda.toString()); + console.log("πŸ“Š Bump:", bump); + + try { + // Initialize PDA (starts at 0) - handle case where it already exists + console.log("\nπŸ“ Initializing PDA..."); + try { + await program.methods + .initializePda() + .accounts({ + state: statePda, + payer: payer.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + }) + .rpc(); + } catch (error) { + // If PDA already exists, that's fine - we can still test updates + console.log("βœ… PDA already exists, continuing with updates..."); + } + + // Fetch initial state using connection directly + const stateAccountInfo = await provider.connection.getAccountInfo(statePda); + if (stateAccountInfo) { + const stateData = stateAccountInfo.data; + const count = stateData.readBigUInt64LE(8); // Skip 8-byte discriminator + console.log("βœ… PDA initialized! Count =", count.toString()); + } + + // First PDA update: 0 β†’ 1 + console.log("\nπŸ“ˆ Updating PDA (1st time)..."); + await program.methods + .updatePda() + .accounts({ + state: statePda, + payer: payer.publicKey, + }) + .rpc(); + + const stateAccountInfo1 = await provider.connection.getAccountInfo(statePda); + if (stateAccountInfo1) { + const stateData = stateAccountInfo1.data; + const count = stateData.readBigUInt64LE(8); + console.log("βœ… PDA updated! Count =", count.toString()); + } + + // Second PDA update: 1 β†’ 2 + console.log("\nπŸ“ˆ Updating PDA (2nd time)..."); + await program.methods + .updatePda() + .accounts({ + state: statePda, + payer: payer.publicKey, + }) + .rpc(); + + const stateAccountInfo2 = await provider.connection.getAccountInfo(statePda); + if (stateAccountInfo2) { + const stateData = stateAccountInfo2.data; + const count = stateData.readBigUInt64LE(8); + console.log("βœ… PDA updated! Count =", count.toString()); + } + + // Third PDA update: 2 β†’ 3 + console.log("\nπŸ“ˆ Updating PDA (3rd time)..."); + await program.methods + .updatePda() + .accounts({ + state: statePda, + payer: payer.publicKey, + }) + .rpc(); + + const stateAccountInfo3 = await provider.connection.getAccountInfo(statePda); + if (stateAccountInfo3) { + const stateData = stateAccountInfo3.data; + const count = stateData.readBigUInt64LE(8); + console.log("βœ… PDA updated! Count =", count.toString()); + console.log("\nπŸŽ‰ Client demo completed successfully!"); + console.log("πŸ“Š Final PDA state: count =", count.toString()); + console.log("πŸ”— PDA address:", statePda.toString()); + } + + } catch (error) { + console.error("❌ Error:", error); + process.exit(1); + } +} + +main().catch((error) => { + console.error("❌ Fatal error:", error); + process.exit(1); +}); \ No newline at end of file diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/client/counter.json b/courses/anchor-and-programs/workshop/anchor-3h/code/client/counter.json new file mode 100644 index 0000000..c03295c --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/client/counter.json @@ -0,0 +1,204 @@ +{ + "address": "841TgnQsPTuTihCn22RaBCTgpnAiG4QL3oQLkyrw3L6N", + "metadata": { + "name": "counter", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "increment", + "discriminator": [ + 11, + 18, + 104, + 9, + 104, + 174, + 59, + 33 + ], + "accounts": [ + { + "name": "counter", + "writable": true + } + ], + "args": [] + }, + { + "name": "initialize", + "discriminator": [ + 175, + 175, + 109, + 31, + 13, + 152, + 155, + 237 + ], + "accounts": [ + { + "name": "counter", + "writable": true, + "signer": true + }, + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [] + }, + { + "name": "initialize_pda", + "discriminator": [ + 178, + 254, + 136, + 212, + 127, + 85, + 171, + 210 + ], + "accounts": [ + { + "name": "state", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 115, + 116, + 97, + 116, + 101 + ] + }, + { + "kind": "account", + "path": "payer" + } + ] + } + }, + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "system_program", + "address": "11111111111111111111111111111111" + } + ], + "args": [] + }, + { + "name": "update_pda", + "discriminator": [ + 229, + 63, + 241, + 182, + 220, + 190, + 61, + 161 + ], + "accounts": [ + { + "name": "state", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 115, + 116, + 97, + 116, + 101 + ] + }, + { + "kind": "account", + "path": "payer" + } + ] + } + }, + { + "name": "payer", + "signer": true + } + ], + "args": [] + } + ], + "accounts": [ + { + "name": "Counter", + "discriminator": [ + 255, + 176, + 4, + 245, + 188, + 253, + 124, + 25 + ] + }, + { + "name": "State", + "discriminator": [ + 216, + 146, + 107, + 94, + 104, + 75, + 182, + 177 + ] + } + ], + "types": [ + { + "name": "Counter", + "type": { + "kind": "struct", + "fields": [ + { + "name": "count", + "type": "u64" + } + ] + } + }, + { + "name": "State", + "type": { + "kind": "struct", + "fields": [ + { + "name": "count", + "type": "u64" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/client/counter.ts b/courses/anchor-and-programs/workshop/anchor-3h/code/client/counter.ts new file mode 100644 index 0000000..4804887 --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/client/counter.ts @@ -0,0 +1,210 @@ +/** + * Program IDL in camelCase format in order to be used in JS/TS. + * + * Note that this is only a type helper and is not the actual IDL. The original + * IDL can be found at `target/idl/counter.json`. + */ +export type Counter = { + "address": "841TgnQsPTuTihCn22RaBCTgpnAiG4QL3oQLkyrw3L6N", + "metadata": { + "name": "counter", + "version": "0.1.0", + "spec": "0.1.0", + "description": "Created with Anchor" + }, + "instructions": [ + { + "name": "increment", + "discriminator": [ + 11, + 18, + 104, + 9, + 104, + 174, + 59, + 33 + ], + "accounts": [ + { + "name": "counter", + "writable": true + } + ], + "args": [] + }, + { + "name": "initialize", + "discriminator": [ + 175, + 175, + 109, + 31, + 13, + 152, + 155, + 237 + ], + "accounts": [ + { + "name": "counter", + "writable": true, + "signer": true + }, + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + } + ], + "args": [] + }, + { + "name": "initializePda", + "discriminator": [ + 178, + 254, + 136, + 212, + 127, + 85, + 171, + 210 + ], + "accounts": [ + { + "name": "state", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 115, + 116, + 97, + 116, + 101 + ] + }, + { + "kind": "account", + "path": "payer" + } + ] + } + }, + { + "name": "payer", + "writable": true, + "signer": true + }, + { + "name": "systemProgram", + "address": "11111111111111111111111111111111" + } + ], + "args": [] + }, + { + "name": "updatePda", + "discriminator": [ + 229, + 63, + 241, + 182, + 220, + 190, + 61, + 161 + ], + "accounts": [ + { + "name": "state", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "const", + "value": [ + 115, + 116, + 97, + 116, + 101 + ] + }, + { + "kind": "account", + "path": "payer" + } + ] + } + }, + { + "name": "payer", + "signer": true + } + ], + "args": [] + } + ], + "accounts": [ + { + "name": "counter", + "discriminator": [ + 255, + 176, + 4, + 245, + 188, + 253, + 124, + 25 + ] + }, + { + "name": "state", + "discriminator": [ + 216, + 146, + 107, + 94, + 104, + 75, + 182, + 177 + ] + } + ], + "types": [ + { + "name": "counter", + "type": { + "kind": "struct", + "fields": [ + { + "name": "count", + "type": "u64" + } + ] + } + }, + { + "name": "state", + "type": { + "kind": "struct", + "fields": [ + { + "name": "count", + "type": "u64" + } + ] + } + } + ] +}; diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/client/package.json b/courses/anchor-and-programs/workshop/anchor-3h/code/client/package.json new file mode 100644 index 0000000..a6e7804 --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/client/package.json @@ -0,0 +1,18 @@ +{ + "name": "anchor-3h-client", + "version": "1.0.0", + "description": "Minimal client script for Anchor 3h workshop", + "main": "callProgram.ts", + "scripts": { + "start": "ts-node callProgram.ts" + }, + "dependencies": { + "@coral-xyz/anchor": "^0.30.1", + "@solana/web3.js": "^1.87.6" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "typescript": "^5.3.0", + "ts-node": "^10.9.0" + } +} diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/client/tsconfig.json b/courses/anchor-and-programs/workshop/anchor-3h/code/client/tsconfig.json new file mode 100644 index 0000000..05f0e5b --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/client/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "include": [ + "*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/counter/Anchor.toml b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/Anchor.toml new file mode 100644 index 0000000..712485d --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/Anchor.toml @@ -0,0 +1,22 @@ +[toolchain] +package_manager = "yarn" + +[features] +resolution = true +skip-lint = false + +[programs.localnet] +counter = "841TgnQsPTuTihCn22RaBCTgpnAiG4QL3oQLkyrw3L6N" + +[programs.devnet] +counter = "841TgnQsPTuTihCn22RaBCTgpnAiG4QL3oQLkyrw3L6N" + +[registry] +url = "https://api.apr.dev" + +[provider] +cluster = "devnet" +wallet = "~/.config/solana/id.json" + +[scripts] +test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 \"tests/**/*.ts\"" diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/counter/Cargo.toml b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/Cargo.toml new file mode 100644 index 0000000..f397704 --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/Cargo.toml @@ -0,0 +1,14 @@ +[workspace] +members = [ + "programs/*" +] +resolver = "2" + +[profile.release] +overflow-checks = true +lto = "fat" +codegen-units = 1 +[profile.release.build-override] +opt-level = 3 +incremental = false +codegen-units = 1 diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/counter/package.json b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/package.json new file mode 100644 index 0000000..2c7baaa --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/package.json @@ -0,0 +1,26 @@ +{ + "license": "ISC", + "scripts": { + "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", + "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check", + "devnet:env": "solana config set --url https://api.devnet.solana.com", + "devnet:airdrop": "solana airdrop 2", + "devnet:build": "anchor build", + "devnet:deploy": "ANCHOR_PROVIDER_URL=https://api.devnet.solana.com ANCHOR_WALLET=$HOME/.config/solana/id.json anchor deploy --provider.cluster devnet", + "devnet:test": "ANCHOR_PROVIDER_URL=https://api.devnet.solana.com ANCHOR_WALLET=$HOME/.config/solana/id.json anchor test --provider.cluster devnet", + "devnet:test:skip-deploy": "ANCHOR_PROVIDER_URL=https://api.devnet.solana.com ANCHOR_WALLET=$HOME/.config/solana/id.json anchor test --provider.cluster devnet --skip-deploy" + }, + "dependencies": { + "@coral-xyz/anchor": "^0.31.1" + }, + "devDependencies": { + "chai": "^4.3.4", + "mocha": "^9.0.3", + "ts-mocha": "^10.0.0", + "@types/bn.js": "^5.1.0", + "@types/chai": "^4.3.0", + "@types/mocha": "^9.0.0", + "typescript": "^5.7.3", + "prettier": "^2.6.2" + } +} diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/counter/programs/counter/Cargo.toml b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/programs/counter/Cargo.toml new file mode 100644 index 0000000..6203215 --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/programs/counter/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "counter" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "counter" + +[features] +default = [] +cpi = ["no-entrypoint"] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +idl-build = ["anchor-lang/idl-build"] +anchor-debug = [] +custom-heap = [] +custom-panic = [] + + +[dependencies] +anchor-lang = "0.31.1" + + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] } diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/counter/programs/counter/src/lib.rs b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/programs/counter/src/lib.rs new file mode 100644 index 0000000..88f8bfc --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/programs/counter/src/lib.rs @@ -0,0 +1,83 @@ +use anchor_lang::prelude::*; + +declare_id!("841TgnQsPTuTihCn22RaBCTgpnAiG4QL3oQLkyrw3L6N"); + +#[program] +pub mod counter { + use super::*; + + pub fn initialize(ctx: Context) -> Result<()> { + let counter = &mut ctx.accounts.counter; + counter.count = 0; + Ok(()) + } + + pub fn increment(ctx: Context) -> Result<()> { + let counter = &mut ctx.accounts.counter; + counter.count = counter.count.checked_add(1).unwrap(); + Ok(()) + } + + pub fn initialize_pda(ctx: Context) -> Result<()> { + let state = &mut ctx.accounts.state; + state.count = 0; + Ok(()) + } + + pub fn update_pda(ctx: Context) -> Result<()> { + let state = &mut ctx.accounts.state; + state.count = state.count.checked_add(1).unwrap(); + Ok(()) + } +} + +#[account] +pub struct Counter { + pub count: u64, +} + +#[account] +pub struct State { + pub count: u64, +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + #[account(init, payer = payer, space = 8 + 8)] + pub counter: Account<'info, Counter>, + #[account(mut)] + pub payer: Signer<'info>, + pub system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct Increment<'info> { + #[account(mut)] + pub counter: Account<'info, Counter>, +} + +#[derive(Accounts)] +pub struct InitializePda<'info> { + #[account( + init, + payer = payer, + space = 8 + 8, + seeds = [b"state", payer.key().as_ref()], + bump + )] + pub state: Account<'info, State>, + #[account(mut)] + pub payer: Signer<'info>, + pub system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct UpdatePda<'info> { + #[account( + mut, + seeds = [b"state", payer.key().as_ref()], + bump + )] + pub state: Account<'info, State>, + pub payer: Signer<'info>, +} diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/counter/tests/counter.ts b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/tests/counter.ts new file mode 100644 index 0000000..9146430 --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/tests/counter.ts @@ -0,0 +1,133 @@ +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { Counter } from "../target/types/counter"; +import { expect } from "chai"; + +describe("counter", () => { + anchor.setProvider(anchor.AnchorProvider.env()); + + const program = anchor.workspace.counter as Program; + + it("initialize β†’ increment β†’ fetch", async () => { + const provider = program.provider as anchor.AnchorProvider; + const payer = provider.wallet as anchor.Wallet; + + const counterKeypair = anchor.web3.Keypair.generate(); + + // Initialize counter (starts at 0) + await program.methods + .initialize() + .accounts({ + counter: counterKeypair.publicKey, + payer: payer.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + }) + .signers([counterKeypair]) + .rpc(); + + // First increment: 0 β†’ 1 + await program.methods + .increment() + .accounts({ counter: counterKeypair.publicKey }) + .rpc(); + + let counterAcc = await program.account.counter.fetch( + counterKeypair.publicKey + ); + console.log("After 1st increment: count =", counterAcc.count.toString()); + + // Second increment: 1 β†’ 2 + await program.methods + .increment() + .accounts({ counter: counterKeypair.publicKey }) + .rpc(); + + counterAcc = await program.account.counter.fetch( + counterKeypair.publicKey + ); + console.log("After 2nd increment: count =", counterAcc.count.toString()); + + // Third increment: 2 β†’ 3 + await program.methods + .increment() + .accounts({ counter: counterKeypair.publicKey }) + .rpc(); + + counterAcc = await program.account.counter.fetch( + counterKeypair.publicKey + ); + console.log("After 3rd increment: count =", counterAcc.count.toString()); + }); + + it("PDA: derive β†’ initialize β†’ update β†’ fetch", async () => { + const provider = program.provider as anchor.AnchorProvider; + const payer = provider.wallet as anchor.Wallet; + + // Derive PDA using findProgramAddressSync + const [statePda, bump] = anchor.web3.PublicKey.findProgramAddressSync( + [Buffer.from("state"), payer.publicKey.toBuffer()], + program.programId + ); + console.log("Derived PDA:", statePda.toString()); + console.log("Bump:", bump); + + // Try to initialize PDA (starts at 0) - handle case where it already exists + try { + await program.methods + .initializePda() + .accounts({ + state: statePda, + payer: payer.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + }) + .rpc(); + } catch (error) { + // If PDA already exists, that's fine - we can still test updates + console.log("PDA already exists, continuing with updates..."); + } + + // Fetch initial state + let stateAcc = await program.account.state.fetch(statePda); + console.log("After PDA init: count =", stateAcc.count.toString()); + expect(stateAcc.count.toNumber()).to.equal(0); + + // First PDA update: 0 β†’ 1 + await program.methods + .updatePda() + .accounts({ + state: statePda, + payer: payer.publicKey, + }) + .rpc(); + + stateAcc = await program.account.state.fetch(statePda); + console.log("After 1st PDA update: count =", stateAcc.count.toString()); + expect(stateAcc.count.toNumber()).to.equal(1); + + // Second PDA update: 1 β†’ 2 + await program.methods + .updatePda() + .accounts({ + state: statePda, + payer: payer.publicKey, + }) + .rpc(); + + stateAcc = await program.account.state.fetch(statePda); + console.log("After 2nd PDA update: count =", stateAcc.count.toString()); + expect(stateAcc.count.toNumber()).to.equal(2); + + // Third PDA update: 2 β†’ 3 + await program.methods + .updatePda() + .accounts({ + state: statePda, + payer: payer.publicKey, + }) + .rpc(); + + stateAcc = await program.account.state.fetch(statePda); + console.log("After 3rd PDA update: count =", stateAcc.count.toString()); + expect(stateAcc.count.toNumber()).to.equal(3); + }); +}); diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/counter/tsconfig.json b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/tsconfig.json new file mode 100644 index 0000000..cd5d2e3 --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "types": ["mocha", "chai"], + "typeRoots": ["./node_modules/@types"], + "lib": ["es2015"], + "module": "commonjs", + "target": "es6", + "esModuleInterop": true + } +} From 33c9ba458c676a4e7891af6c7aed62a8d7bceb8e Mon Sep 17 00:00:00 2001 From: resourcefulmind Date: Tue, 21 Oct 2025 14:17:24 +0100 Subject: [PATCH 3/5] feat: Add finalized reference code and presenter prep materials - Add /final/ directory with deeply commented reference implementations - lib.rs: Comprehensive program logic documentation with technical precision - counter.ts: Detailed test suite explanations and client interaction patterns - callProgram.ts: Complete client script documentation with frontend integration notes - README.md: Presenter prep guide with troubleshooting and resource links All implementations include: - Factual, concise, technically accurate comments aligned with Anchor Book - Complete coverage of functions, structs, and instruction contexts - Verified functionality and deployment compatibility - Professional documentation suitable for workshop facilitators and self-learners --- .../anchor-3h/code/client/callProgram.ts | 129 +++++++++- .../anchor-3h/code/counter/tests/counter.ts | 132 ++++++++-- .../workshop/anchor-3h/code/final/README.md | 181 +++++++++++++ .../anchor-3h/code/final/callProgram.ts | 222 ++++++++++++++++ .../workshop/anchor-3h/code/final/counter.ts | 237 ++++++++++++++++++ .../workshop/anchor-3h/code/final/lib.rs | 217 ++++++++++++++++ 6 files changed, 1092 insertions(+), 26 deletions(-) create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/final/README.md create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/final/callProgram.ts create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/final/counter.ts create mode 100644 courses/anchor-and-programs/workshop/anchor-3h/code/final/lib.rs diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/client/callProgram.ts b/courses/anchor-and-programs/workshop/anchor-3h/code/client/callProgram.ts index 35f71c6..742843f 100644 --- a/courses/anchor-and-programs/workshop/anchor-3h/code/client/callProgram.ts +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/client/callProgram.ts @@ -1,24 +1,91 @@ +/** + * # Solana Anchor Counter Program - Client Interaction Demo + * + * This script demonstrates how to interact with an Anchor program from a client application. + * It mimics the patterns used in real frontend applications (React, Vue, etc.) when + * connecting to and interacting with Solana programs. + * + * Key concepts demonstrated: + * - Client-side provider setup and wallet connection + * - Program instantiation using IDL (Interface Definition Language) + * - PDA derivation and management from client perspective + * - Account state fetching and data parsing + * - Error handling and graceful failure management + * + * This script can be run independently to test program functionality + * without requiring a full test suite or UI framework. + */ + import * as anchor from "@coral-xyz/anchor"; import { Program } from "@coral-xyz/anchor"; import idl from "./counter.json"; +/** + * Main client interaction function + * + * This function demonstrates the complete client-side workflow: + * 1. Set up provider and program connection + * 2. Derive PDA for deterministic account management + * 3. Initialize PDA account (if needed) + * 4. Perform multiple state updates + * 5. Fetch and verify account state changes + */ async function main() { console.log("πŸš€ Starting Anchor 3h Workshop Client Demo"); console.log("=========================================="); - // Set up provider for localnet + // Set up the Anchor provider for localnet connection + // + // AnchorProvider.env() automatically reads environment variables: + // - ANCHOR_PROVIDER_URL: RPC endpoint (e.g., http://localhost:8899 for localnet) + // - ANCHOR_WALLET: Path to wallet keypair file + // + // This is the same pattern used in frontend applications where + // the provider is configured based on the target network const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); // Load the program using the generated IDL + // + // The IDL (Interface Definition Language) is automatically generated + // by Anchor during the build process. It contains: + // - All instruction definitions and their parameters + // - Account structures and their layouts + // - Program metadata and version information + // + // In a real frontend app, you would typically fetch the IDL from: + // - A CDN or static file server + // - The program's IDL account on-chain + // - A package registry like npm const program = new Program(idl as anchor.Idl, provider); console.log("πŸ“‹ Program ID:", program.programId.toString()); - // Get the payer wallet + // Get the payer wallet from the provider + // + // The wallet represents the user's keypair and is used for: + // - Signing transactions + // - Paying transaction fees + // - Providing seed material for PDA derivation + // + // In a real frontend app, this would be connected to: + // - Phantom, Solflare, or other wallet extensions + // - Hardware wallets like Ledger + // - Mobile wallet apps const payer = provider.wallet as anchor.Wallet; console.log("πŸ‘€ Payer:", payer.publicKey.toString()); - // Derive PDA using findProgramAddressSync + // Derive the PDA using findProgramAddressSync + // + // PDAs (Program Derived Addresses) are deterministic addresses + // that programs can own without requiring private keys. + // + // The seeds must exactly match what we defined in our Rust program: + // seeds = [b"state", payer.key().as_ref()] + // + // This ensures that: + // - The same payer always gets the same PDA address + // - The program can deterministically find and manage the account + // - No private key is needed to control the account const [statePda, bump] = anchor.web3.PublicKey.findProgramAddressSync( [Buffer.from("state"), payer.publicKey.toBuffer()], program.programId @@ -27,40 +94,58 @@ async function main() { console.log("πŸ“Š Bump:", bump); try { - // Initialize PDA (starts at 0) - handle case where it already exists + // Initialize the PDA account (starts at 0) + // + // We use nested try-catch blocks to handle the case where + // the PDA already exists from previous runs. This makes the + // script more resilient and allows for multiple executions. console.log("\nπŸ“ Initializing PDA..."); try { await program.methods .initializePda() .accounts({ - state: statePda, - payer: payer.publicKey, - systemProgram: anchor.web3.SystemProgram.programId, + state: statePda, // The derived PDA address + payer: payer.publicKey, // Payer and seed provider + systemProgram: anchor.web3.SystemProgram.programId, // Required for account creation }) - .rpc(); + .rpc(); // Send transaction and wait for confirmation } catch (error) { // If PDA already exists, that's fine - we can still test updates + // This is a common pattern in client applications to handle + // account reuse and idempotent operations console.log("βœ… PDA already exists, continuing with updates..."); } - // Fetch initial state using connection directly + // Fetch the initial state using direct connection access + // + // We use provider.connection.getAccountInfo() instead of + // program.account.state.fetch() to demonstrate manual data parsing. + // This approach gives us more control over the data extraction + // and is useful when you need to parse custom data formats. const stateAccountInfo = await provider.connection.getAccountInfo(statePda); if (stateAccountInfo) { const stateData = stateAccountInfo.data; + // Parse the account data manually: + // - First 8 bytes: Anchor discriminator (automatically added) + // - Next 8 bytes: Our u64 count value (little-endian) const count = stateData.readBigUInt64LE(8); // Skip 8-byte discriminator console.log("βœ… PDA initialized! Count =", count.toString()); } // First PDA update: 0 β†’ 1 + // + // This demonstrates how a frontend would trigger state changes + // in response to user interactions (button clicks, form submissions, etc.) console.log("\nπŸ“ˆ Updating PDA (1st time)..."); await program.methods .updatePda() .accounts({ - state: statePda, - payer: payer.publicKey, + state: statePda, // The PDA to be modified + payer: payer.publicKey, // Required for PDA seed validation }) .rpc(); + // Fetch and verify the first update const stateAccountInfo1 = await provider.connection.getAccountInfo(statePda); if (stateAccountInfo1) { const stateData = stateAccountInfo1.data; @@ -69,6 +154,9 @@ async function main() { } // Second PDA update: 1 β†’ 2 + // + // Multiple updates demonstrate how frontend applications + // can perform sequential operations and track state changes console.log("\nπŸ“ˆ Updating PDA (2nd time)..."); await program.methods .updatePda() @@ -78,6 +166,7 @@ async function main() { }) .rpc(); + // Fetch and verify the second update const stateAccountInfo2 = await provider.connection.getAccountInfo(statePda); if (stateAccountInfo2) { const stateData = stateAccountInfo2.data; @@ -86,6 +175,8 @@ async function main() { } // Third PDA update: 2 β†’ 3 + // + // Final update to demonstrate the complete workflow console.log("\nπŸ“ˆ Updating PDA (3rd time)..."); await program.methods .updatePda() @@ -95,6 +186,7 @@ async function main() { }) .rpc(); + // Fetch and verify the final state const stateAccountInfo3 = await provider.connection.getAccountInfo(statePda); if (stateAccountInfo3) { const stateData = stateAccountInfo3.data; @@ -106,12 +198,25 @@ async function main() { } } catch (error) { + // Handle any errors that occur during the demo + // + // In a real frontend application, you would typically: + // - Show user-friendly error messages + // - Log errors for debugging + // - Provide retry mechanisms + // - Handle specific error types differently console.error("❌ Error:", error); process.exit(1); } } +// Execute the main function with error handling +// +// This pattern ensures that any unhandled errors are caught +// and the process exits gracefully. In a real frontend app, +// you would handle errors by updating the UI state rather +// than exiting the process. main().catch((error) => { console.error("❌ Fatal error:", error); process.exit(1); -}); \ No newline at end of file +}); diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/counter/tests/counter.ts b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/tests/counter.ts index 9146430..9bb7e33 100644 --- a/courses/anchor-and-programs/workshop/anchor-3h/code/counter/tests/counter.ts +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/counter/tests/counter.ts @@ -1,36 +1,95 @@ +/** + * # Solana Anchor Counter Program - Comprehensive Test Suite + * + * This test file demonstrates how to interact with Anchor programs using TypeScript. + * It covers both traditional account management and Program Derived Addresses (PDAs). + * + * Key concepts demonstrated: + * - Provider setup and workspace configuration + * - Auto-generated client methods from IDL + * - Traditional account initialization and management + * - PDA derivation and deterministic addressing + * - Account state fetching and verification + * - Error handling and test resilience + */ + import * as anchor from "@coral-xyz/anchor"; import { Program } from "@coral-xyz/anchor"; import { Counter } from "../target/types/counter"; import { expect } from "chai"; +/** + * Main test suite for the Counter program + * + * Each test demonstrates different aspects of Anchor program interaction: + * 1. Traditional account management (initialize β†’ increment β†’ fetch) + * 2. PDA-based account management (derive β†’ initialize β†’ update β†’ fetch) + */ describe("counter", () => { + // Set up the Anchor provider using environment variables + // This automatically configures connection to localnet/devnet based on ANCHOR_PROVIDER_URL anchor.setProvider(anchor.AnchorProvider.env()); + // Get the program instance from Anchor's workspace + // The workspace contains all programs defined in Anchor.toml + // TypeScript types are auto-generated from the IDL for type safety const program = anchor.workspace.counter as Program; + /** + * Test: Traditional Account Management + * + * This test demonstrates the classic Anchor pattern: + * 1. Generate a new keypair for the account + * 2. Initialize the account with initial state + * 3. Perform operations on the account + * 4. Verify state changes through account fetching + * + * This pattern is used when you need full control over account addresses + * and don't require deterministic addressing. + */ it("initialize β†’ increment β†’ fetch", async () => { + // Get the provider and wallet from the program instance + // The provider handles RPC communication with the Solana cluster const provider = program.provider as anchor.AnchorProvider; const payer = provider.wallet as anchor.Wallet; + // Generate a new keypair for our counter account + // This creates a unique address that we'll use for the account const counterKeypair = anchor.web3.Keypair.generate(); - // Initialize counter (starts at 0) + // Initialize the counter account with count = 0 + // + // The .methods property contains auto-generated methods from the IDL + // Each method corresponds to an instruction in our Rust program + // + // .accounts() - Pass all required accounts for the instruction + // .signers() - Specify which keypairs must sign the transaction + // .rpc() - Send the transaction to the cluster and wait for confirmation await program.methods .initialize() .accounts({ - counter: counterKeypair.publicKey, - payer: payer.publicKey, - systemProgram: anchor.web3.SystemProgram.programId, + counter: counterKeypair.publicKey, // New account to be created + payer: payer.publicKey, // Account that pays for creation + systemProgram: anchor.web3.SystemProgram.programId, // Required for account creation }) - .signers([counterKeypair]) + .signers([counterKeypair]) // The keypair must sign to authorize account creation .rpc(); // First increment: 0 β†’ 1 + // + // Note: No signers needed here since we're only modifying an existing account + // The payer (from provider.wallet) automatically signs the transaction await program.methods .increment() - .accounts({ counter: counterKeypair.publicKey }) + .accounts({ + counter: counterKeypair.publicKey // Account to be modified + }) .rpc(); + // Fetch the account state to verify the increment + // + // program.account.counter.fetch() uses the auto-generated account deserializer + // It automatically handles Borsh deserialization and type conversion let counterAcc = await program.account.counter.fetch( counterKeypair.publicKey ); @@ -42,6 +101,7 @@ describe("counter", () => { .accounts({ counter: counterKeypair.publicKey }) .rpc(); + // Verify the second increment counterAcc = await program.account.counter.fetch( counterKeypair.publicKey ); @@ -53,17 +113,44 @@ describe("counter", () => { .accounts({ counter: counterKeypair.publicKey }) .rpc(); + // Final verification counterAcc = await program.account.counter.fetch( counterKeypair.publicKey ); console.log("After 3rd increment: count =", counterAcc.count.toString()); }); + /** + * Test: Program Derived Address (PDA) Management + * + * This test demonstrates PDA usage, which is crucial for many Solana applications: + * 1. Derive a deterministic address using seeds and program ID + * 2. Initialize the PDA account (if it doesn't exist) + * 3. Perform operations on the PDA + * 4. Verify state changes + * + * PDAs are essential for: + * - Creating accounts that programs can own without private keys + * - Ensuring consistent addressing across different clients + * - Building complex state management systems + */ it("PDA: derive β†’ initialize β†’ update β†’ fetch", async () => { + // Get the provider and wallet const provider = program.provider as anchor.AnchorProvider; const payer = provider.wallet as anchor.Wallet; - // Derive PDA using findProgramAddressSync + // Derive the PDA using findProgramAddressSync + // + // This function takes: + // 1. An array of seed buffers (must match the seeds in our Rust program) + // 2. The program ID + // + // It returns: + // 1. The derived PDA address + // 2. The canonical bump seed (ensures the address is off-curve) + // + // The seeds must exactly match what we defined in our Rust program: + // seeds = [b"state", payer.key().as_ref()] const [statePda, bump] = anchor.web3.PublicKey.findProgramAddressSync( [Buffer.from("state"), payer.publicKey.toBuffer()], program.programId @@ -71,35 +158,50 @@ describe("counter", () => { console.log("Derived PDA:", statePda.toString()); console.log("Bump:", bump); - // Try to initialize PDA (starts at 0) - handle case where it already exists + // Try to initialize the PDA (starts at 0) + // + // We use try-catch because the PDA might already exist from previous test runs + // This makes our tests more resilient and allows for multiple test executions try { await program.methods .initializePda() .accounts({ - state: statePda, - payer: payer.publicKey, - systemProgram: anchor.web3.SystemProgram.programId, + state: statePda, // The derived PDA address + payer: payer.publicKey, // Payer and seed provider + systemProgram: anchor.web3.SystemProgram.programId, // Required for account creation }) .rpc(); } catch (error) { // If PDA already exists, that's fine - we can still test updates + // This is a common pattern in Solana testing to handle account reuse console.log("PDA already exists, continuing with updates..."); } - // Fetch initial state + // Fetch the initial state of the PDA + // + // Even if initialization failed due to existing account, we can still fetch + // This demonstrates the resilience of PDA-based account management let stateAcc = await program.account.state.fetch(statePda); console.log("After PDA init: count =", stateAcc.count.toString()); + + // Verify the initial state is 0 + // expect() assertions ensure our program logic is working correctly expect(stateAcc.count.toNumber()).to.equal(0); // First PDA update: 0 β†’ 1 + // + // Note: No signers needed here since we're only modifying an existing PDA + // The payer automatically signs, and the PDA constraints ensure we're operating + // on the correct account await program.methods .updatePda() .accounts({ - state: statePda, - payer: payer.publicKey, + state: statePda, // The PDA to be modified + payer: payer.publicKey, // Required for PDA seed validation }) .rpc(); + // Verify the first update stateAcc = await program.account.state.fetch(statePda); console.log("After 1st PDA update: count =", stateAcc.count.toString()); expect(stateAcc.count.toNumber()).to.equal(1); @@ -113,6 +215,7 @@ describe("counter", () => { }) .rpc(); + // Verify the second update stateAcc = await program.account.state.fetch(statePda); console.log("After 2nd PDA update: count =", stateAcc.count.toString()); expect(stateAcc.count.toNumber()).to.equal(2); @@ -126,6 +229,7 @@ describe("counter", () => { }) .rpc(); + // Final verification stateAcc = await program.account.state.fetch(statePda); console.log("After 3rd PDA update: count =", stateAcc.count.toString()); expect(stateAcc.count.toNumber()).to.equal(3); diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/final/README.md b/courses/anchor-and-programs/workshop/anchor-3h/code/final/README.md new file mode 100644 index 0000000..b3e3896 --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/final/README.md @@ -0,0 +1,181 @@ +# Anchor 3h Workshop β€” Finalized Reference Code & Presenter Prep + +**Deeply Commented, Pedagogically Structured Reference Implementation** + +This directory contains the finalized reference code for the Solana Foundation's Anchor & dApp 3-Hour Workshop. These files provide comprehensive, deeply commented implementations designed for self-study, pre-session preparation, and consistent delivery quality across instructors worldwide. + +## Purpose & Audience + +### **Primary Audience** +- **Workshop Facilitators**: Instructors preparing to deliver the 3-hour Anchor workshop +- **Self-Learners**: Developers studying Anchor concepts independently +- **Code Reviewers**: Foundation staff ensuring educational content quality + +### **Secondary Audience** +- **Post-Workshop Reference**: Participants reinforcing concepts after live sessions +- **Curriculum Developers**: Teams creating related educational materials + +### **What This Provides** +- **Complete Logic Understanding**: Every line of code explained with technical precision +- **Conceptual Mapping**: Clear connections between code and Anchor framework concepts +- **Teaching Support**: Structured commentary that supports effective instruction +- **Quality Assurance**: Verified implementations that align with official documentation + +## File-by-File Overview + +### **`lib.rs` - Program Logic Reference** +**Purpose**: Core Solana program implementation with comprehensive technical commentary + +**Key Learning Areas**: +- **Program Module Structure**: How `#[program]` creates instruction handlers +- **Account Management**: Traditional accounts vs. Program Derived Addresses (PDAs) +- **Constraint Validation**: `init`, `mut`, `payer`, `space` constraints explained +- **PDA Mechanics**: Seed derivation, bump validation, deterministic addressing +- **Context Objects**: `Context` pattern and type-safe account access +- **Error Handling**: Overflow protection and security considerations + +**Teaching Value**: Instructors can reference specific lines to explain Anchor concepts during live coding sessions. + +### **`counter.ts` - Test Suite Reference** +**Purpose**: Comprehensive test implementation demonstrating client-side program interaction + +**Key Learning Areas**: +- **Provider Setup**: `AnchorProvider.env()` and workspace configuration +- **Program Instantiation**: Type-safe program access with auto-generated types +- **Data Derivation**: Keypair generation and PDA derivation patterns +- **Instruction Execution**: Method calls, account passing, RPC transactions +- **State Verification**: Account fetching, assertions, and logging patterns +- **Error Resilience**: Try-catch blocks and test robustness + +**Teaching Value**: Shows how tests validate program functionality and demonstrate expected behavior. + +### **`callProgram.ts` - Client Integration Reference** +**Purpose**: Minimal client script demonstrating real-world frontend interaction patterns + +**Key Learning Areas**: +- **Client Setup**: Provider configuration and wallet connection +- **IDL Usage**: Program instantiation using Interface Definition Language +- **PDA Operations**: Client-side PDA derivation and management +- **State Management**: Account fetching and manual data parsing +- **Error Handling**: Graceful failure management and user experience +- **Frontend Patterns**: How this translates to React, Vue, or other frameworks + +**Teaching Value**: Bridges the gap between program logic and real-world application development. + +## 5-Minute Presenter Prep + +### **Pre-Session Checklist** +- [ ] **Environment Verified**: Solana CLI β‰₯1.18, Anchor β‰₯0.30, Rust stable, Node.js β‰₯16 +- [ ] **Localnet Running**: `solana-test-validator --reset --quiet` active +- [ ] **Program Deployed**: Counter program deployed to localnet with correct program ID +- [ ] **Dependencies Installed**: All `yarn install` commands completed successfully +- [ ] **Tests Passing**: `anchor test` runs without errors + +### **Key Teaching Points** +1. **PDA Fundamentals**: Emphasize deterministic addressing and seed management +2. **Account Constraints**: Explain how Anchor validates account states +3. **Client Integration**: Show how IDL enables type-safe program interaction +4. **Error Patterns**: Demonstrate common failure modes and recovery strategies + +### **Common Questions & Answers** +- **Q**: "Why use PDAs instead of regular accounts?" +- **A**: PDAs provide deterministic addressing, program ownership, and eliminate key management complexity. + +- **Q**: "How does Anchor generate client methods?" +- **A**: The IDL (Interface Definition Language) is generated during build and provides type-safe client methods. + +- **Q**: "What happens if a PDA already exists?" +- **A**: The program handles this gracefully with proper error handling and account reuse patterns. + +## Localnet vs Devnet + +### **Localnet (Recommended for Workshops)** +```bash +# Start validator +solana-test-validator --reset --quiet + +# Deploy program +anchor deploy + +# Run tests +anchor test + +# Execute client +cd client && yarn start +``` + +### **Devnet (Production-Like Environment)** +```bash +# Configure for devnet +solana config set --url devnet + +# Deploy program +anchor deploy + +# Run tests (requires devnet SOL) +anchor test + +# Execute client (requires devnet configuration) +cd client && ANCHOR_PROVIDER_URL=https://api.devnet.solana.com yarn start +``` + +## Troubleshooting + +### **Program ID Mismatch** +**Symptoms**: `DeclaredProgramIdMismatch` error during deployment +**Solution**: Ensure `declare_id!` in `lib.rs` matches the program ID in `Anchor.toml` + +### **Localnet Validator Issues** +**Symptoms**: Connection refused, RPC errors +**Solution**: +```bash +# Kill existing validator +pkill solana-test-validator + +# Start fresh validator +solana-test-validator --reset --quiet +``` + +### **IDL Sync Problems** +**Symptoms**: Client can't find program methods +**Solution**: +```bash +# Rebuild and redeploy +anchor build +anchor deploy + +# Copy IDL to client +cp target/idl/counter.json client/counter.json +``` + +### **Account Already Exists** +**Symptoms**: `Account already in use` errors +**Solution**: This is expected behavior - the program handles existing accounts gracefully + +### **Dependency Issues** +**Symptoms**: `ts-mocha not found`, build failures +**Solution**: +```bash +# Install dependencies in all directories +cd counter && yarn install +cd ../client && yarn install +``` + +## Official Resources + +### **Core Documentation** +- **[Anchor Book](https://book.anchor-lang.com/)**: Official Anchor framework documentation +- **[Solana Docs](https://docs.solana.com/)**: Core Solana blockchain documentation +- **[Solana Cookbook](https://solanacookbook.com/)**: Practical examples and patterns + +### **Security & Best Practices** +- **[Sealevel Attacks](https://github.com/coral-xyz/sealevel-attacks)**: Security vulnerability analysis +- **[Anchor Security](https://book.anchor-lang.com/security/)**: Framework-specific security guidance + +### **Community Resources** +- **[Solana Discord](https://discord.gg/solana)**: Real-time community support +- **[Anchor GitHub](https://github.com/coral-xyz/anchor)**: Source code and issue tracking + +--- + +**This reference implementation ensures consistent, high-quality delivery of Anchor concepts across all workshop sessions. Use these files to prepare for instruction and provide participants with comprehensive post-workshop reference materials.** diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/final/callProgram.ts b/courses/anchor-and-programs/workshop/anchor-3h/code/final/callProgram.ts new file mode 100644 index 0000000..742843f --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/final/callProgram.ts @@ -0,0 +1,222 @@ +/** + * # Solana Anchor Counter Program - Client Interaction Demo + * + * This script demonstrates how to interact with an Anchor program from a client application. + * It mimics the patterns used in real frontend applications (React, Vue, etc.) when + * connecting to and interacting with Solana programs. + * + * Key concepts demonstrated: + * - Client-side provider setup and wallet connection + * - Program instantiation using IDL (Interface Definition Language) + * - PDA derivation and management from client perspective + * - Account state fetching and data parsing + * - Error handling and graceful failure management + * + * This script can be run independently to test program functionality + * without requiring a full test suite or UI framework. + */ + +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import idl from "./counter.json"; + +/** + * Main client interaction function + * + * This function demonstrates the complete client-side workflow: + * 1. Set up provider and program connection + * 2. Derive PDA for deterministic account management + * 3. Initialize PDA account (if needed) + * 4. Perform multiple state updates + * 5. Fetch and verify account state changes + */ +async function main() { + console.log("πŸš€ Starting Anchor 3h Workshop Client Demo"); + console.log("=========================================="); + + // Set up the Anchor provider for localnet connection + // + // AnchorProvider.env() automatically reads environment variables: + // - ANCHOR_PROVIDER_URL: RPC endpoint (e.g., http://localhost:8899 for localnet) + // - ANCHOR_WALLET: Path to wallet keypair file + // + // This is the same pattern used in frontend applications where + // the provider is configured based on the target network + const provider = anchor.AnchorProvider.env(); + anchor.setProvider(provider); + + // Load the program using the generated IDL + // + // The IDL (Interface Definition Language) is automatically generated + // by Anchor during the build process. It contains: + // - All instruction definitions and their parameters + // - Account structures and their layouts + // - Program metadata and version information + // + // In a real frontend app, you would typically fetch the IDL from: + // - A CDN or static file server + // - The program's IDL account on-chain + // - A package registry like npm + const program = new Program(idl as anchor.Idl, provider); + console.log("πŸ“‹ Program ID:", program.programId.toString()); + + // Get the payer wallet from the provider + // + // The wallet represents the user's keypair and is used for: + // - Signing transactions + // - Paying transaction fees + // - Providing seed material for PDA derivation + // + // In a real frontend app, this would be connected to: + // - Phantom, Solflare, or other wallet extensions + // - Hardware wallets like Ledger + // - Mobile wallet apps + const payer = provider.wallet as anchor.Wallet; + console.log("πŸ‘€ Payer:", payer.publicKey.toString()); + + // Derive the PDA using findProgramAddressSync + // + // PDAs (Program Derived Addresses) are deterministic addresses + // that programs can own without requiring private keys. + // + // The seeds must exactly match what we defined in our Rust program: + // seeds = [b"state", payer.key().as_ref()] + // + // This ensures that: + // - The same payer always gets the same PDA address + // - The program can deterministically find and manage the account + // - No private key is needed to control the account + const [statePda, bump] = anchor.web3.PublicKey.findProgramAddressSync( + [Buffer.from("state"), payer.publicKey.toBuffer()], + program.programId + ); + console.log("πŸ”‘ Derived PDA:", statePda.toString()); + console.log("πŸ“Š Bump:", bump); + + try { + // Initialize the PDA account (starts at 0) + // + // We use nested try-catch blocks to handle the case where + // the PDA already exists from previous runs. This makes the + // script more resilient and allows for multiple executions. + console.log("\nπŸ“ Initializing PDA..."); + try { + await program.methods + .initializePda() + .accounts({ + state: statePda, // The derived PDA address + payer: payer.publicKey, // Payer and seed provider + systemProgram: anchor.web3.SystemProgram.programId, // Required for account creation + }) + .rpc(); // Send transaction and wait for confirmation + } catch (error) { + // If PDA already exists, that's fine - we can still test updates + // This is a common pattern in client applications to handle + // account reuse and idempotent operations + console.log("βœ… PDA already exists, continuing with updates..."); + } + + // Fetch the initial state using direct connection access + // + // We use provider.connection.getAccountInfo() instead of + // program.account.state.fetch() to demonstrate manual data parsing. + // This approach gives us more control over the data extraction + // and is useful when you need to parse custom data formats. + const stateAccountInfo = await provider.connection.getAccountInfo(statePda); + if (stateAccountInfo) { + const stateData = stateAccountInfo.data; + // Parse the account data manually: + // - First 8 bytes: Anchor discriminator (automatically added) + // - Next 8 bytes: Our u64 count value (little-endian) + const count = stateData.readBigUInt64LE(8); // Skip 8-byte discriminator + console.log("βœ… PDA initialized! Count =", count.toString()); + } + + // First PDA update: 0 β†’ 1 + // + // This demonstrates how a frontend would trigger state changes + // in response to user interactions (button clicks, form submissions, etc.) + console.log("\nπŸ“ˆ Updating PDA (1st time)..."); + await program.methods + .updatePda() + .accounts({ + state: statePda, // The PDA to be modified + payer: payer.publicKey, // Required for PDA seed validation + }) + .rpc(); + + // Fetch and verify the first update + const stateAccountInfo1 = await provider.connection.getAccountInfo(statePda); + if (stateAccountInfo1) { + const stateData = stateAccountInfo1.data; + const count = stateData.readBigUInt64LE(8); + console.log("βœ… PDA updated! Count =", count.toString()); + } + + // Second PDA update: 1 β†’ 2 + // + // Multiple updates demonstrate how frontend applications + // can perform sequential operations and track state changes + console.log("\nπŸ“ˆ Updating PDA (2nd time)..."); + await program.methods + .updatePda() + .accounts({ + state: statePda, + payer: payer.publicKey, + }) + .rpc(); + + // Fetch and verify the second update + const stateAccountInfo2 = await provider.connection.getAccountInfo(statePda); + if (stateAccountInfo2) { + const stateData = stateAccountInfo2.data; + const count = stateData.readBigUInt64LE(8); + console.log("βœ… PDA updated! Count =", count.toString()); + } + + // Third PDA update: 2 β†’ 3 + // + // Final update to demonstrate the complete workflow + console.log("\nπŸ“ˆ Updating PDA (3rd time)..."); + await program.methods + .updatePda() + .accounts({ + state: statePda, + payer: payer.publicKey, + }) + .rpc(); + + // Fetch and verify the final state + const stateAccountInfo3 = await provider.connection.getAccountInfo(statePda); + if (stateAccountInfo3) { + const stateData = stateAccountInfo3.data; + const count = stateData.readBigUInt64LE(8); + console.log("βœ… PDA updated! Count =", count.toString()); + console.log("\nπŸŽ‰ Client demo completed successfully!"); + console.log("πŸ“Š Final PDA state: count =", count.toString()); + console.log("πŸ”— PDA address:", statePda.toString()); + } + + } catch (error) { + // Handle any errors that occur during the demo + // + // In a real frontend application, you would typically: + // - Show user-friendly error messages + // - Log errors for debugging + // - Provide retry mechanisms + // - Handle specific error types differently + console.error("❌ Error:", error); + process.exit(1); + } +} + +// Execute the main function with error handling +// +// This pattern ensures that any unhandled errors are caught +// and the process exits gracefully. In a real frontend app, +// you would handle errors by updating the UI state rather +// than exiting the process. +main().catch((error) => { + console.error("❌ Fatal error:", error); + process.exit(1); +}); diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/final/counter.ts b/courses/anchor-and-programs/workshop/anchor-3h/code/final/counter.ts new file mode 100644 index 0000000..9bb7e33 --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/final/counter.ts @@ -0,0 +1,237 @@ +/** + * # Solana Anchor Counter Program - Comprehensive Test Suite + * + * This test file demonstrates how to interact with Anchor programs using TypeScript. + * It covers both traditional account management and Program Derived Addresses (PDAs). + * + * Key concepts demonstrated: + * - Provider setup and workspace configuration + * - Auto-generated client methods from IDL + * - Traditional account initialization and management + * - PDA derivation and deterministic addressing + * - Account state fetching and verification + * - Error handling and test resilience + */ + +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { Counter } from "../target/types/counter"; +import { expect } from "chai"; + +/** + * Main test suite for the Counter program + * + * Each test demonstrates different aspects of Anchor program interaction: + * 1. Traditional account management (initialize β†’ increment β†’ fetch) + * 2. PDA-based account management (derive β†’ initialize β†’ update β†’ fetch) + */ +describe("counter", () => { + // Set up the Anchor provider using environment variables + // This automatically configures connection to localnet/devnet based on ANCHOR_PROVIDER_URL + anchor.setProvider(anchor.AnchorProvider.env()); + + // Get the program instance from Anchor's workspace + // The workspace contains all programs defined in Anchor.toml + // TypeScript types are auto-generated from the IDL for type safety + const program = anchor.workspace.counter as Program; + + /** + * Test: Traditional Account Management + * + * This test demonstrates the classic Anchor pattern: + * 1. Generate a new keypair for the account + * 2. Initialize the account with initial state + * 3. Perform operations on the account + * 4. Verify state changes through account fetching + * + * This pattern is used when you need full control over account addresses + * and don't require deterministic addressing. + */ + it("initialize β†’ increment β†’ fetch", async () => { + // Get the provider and wallet from the program instance + // The provider handles RPC communication with the Solana cluster + const provider = program.provider as anchor.AnchorProvider; + const payer = provider.wallet as anchor.Wallet; + + // Generate a new keypair for our counter account + // This creates a unique address that we'll use for the account + const counterKeypair = anchor.web3.Keypair.generate(); + + // Initialize the counter account with count = 0 + // + // The .methods property contains auto-generated methods from the IDL + // Each method corresponds to an instruction in our Rust program + // + // .accounts() - Pass all required accounts for the instruction + // .signers() - Specify which keypairs must sign the transaction + // .rpc() - Send the transaction to the cluster and wait for confirmation + await program.methods + .initialize() + .accounts({ + counter: counterKeypair.publicKey, // New account to be created + payer: payer.publicKey, // Account that pays for creation + systemProgram: anchor.web3.SystemProgram.programId, // Required for account creation + }) + .signers([counterKeypair]) // The keypair must sign to authorize account creation + .rpc(); + + // First increment: 0 β†’ 1 + // + // Note: No signers needed here since we're only modifying an existing account + // The payer (from provider.wallet) automatically signs the transaction + await program.methods + .increment() + .accounts({ + counter: counterKeypair.publicKey // Account to be modified + }) + .rpc(); + + // Fetch the account state to verify the increment + // + // program.account.counter.fetch() uses the auto-generated account deserializer + // It automatically handles Borsh deserialization and type conversion + let counterAcc = await program.account.counter.fetch( + counterKeypair.publicKey + ); + console.log("After 1st increment: count =", counterAcc.count.toString()); + + // Second increment: 1 β†’ 2 + await program.methods + .increment() + .accounts({ counter: counterKeypair.publicKey }) + .rpc(); + + // Verify the second increment + counterAcc = await program.account.counter.fetch( + counterKeypair.publicKey + ); + console.log("After 2nd increment: count =", counterAcc.count.toString()); + + // Third increment: 2 β†’ 3 + await program.methods + .increment() + .accounts({ counter: counterKeypair.publicKey }) + .rpc(); + + // Final verification + counterAcc = await program.account.counter.fetch( + counterKeypair.publicKey + ); + console.log("After 3rd increment: count =", counterAcc.count.toString()); + }); + + /** + * Test: Program Derived Address (PDA) Management + * + * This test demonstrates PDA usage, which is crucial for many Solana applications: + * 1. Derive a deterministic address using seeds and program ID + * 2. Initialize the PDA account (if it doesn't exist) + * 3. Perform operations on the PDA + * 4. Verify state changes + * + * PDAs are essential for: + * - Creating accounts that programs can own without private keys + * - Ensuring consistent addressing across different clients + * - Building complex state management systems + */ + it("PDA: derive β†’ initialize β†’ update β†’ fetch", async () => { + // Get the provider and wallet + const provider = program.provider as anchor.AnchorProvider; + const payer = provider.wallet as anchor.Wallet; + + // Derive the PDA using findProgramAddressSync + // + // This function takes: + // 1. An array of seed buffers (must match the seeds in our Rust program) + // 2. The program ID + // + // It returns: + // 1. The derived PDA address + // 2. The canonical bump seed (ensures the address is off-curve) + // + // The seeds must exactly match what we defined in our Rust program: + // seeds = [b"state", payer.key().as_ref()] + const [statePda, bump] = anchor.web3.PublicKey.findProgramAddressSync( + [Buffer.from("state"), payer.publicKey.toBuffer()], + program.programId + ); + console.log("Derived PDA:", statePda.toString()); + console.log("Bump:", bump); + + // Try to initialize the PDA (starts at 0) + // + // We use try-catch because the PDA might already exist from previous test runs + // This makes our tests more resilient and allows for multiple test executions + try { + await program.methods + .initializePda() + .accounts({ + state: statePda, // The derived PDA address + payer: payer.publicKey, // Payer and seed provider + systemProgram: anchor.web3.SystemProgram.programId, // Required for account creation + }) + .rpc(); + } catch (error) { + // If PDA already exists, that's fine - we can still test updates + // This is a common pattern in Solana testing to handle account reuse + console.log("PDA already exists, continuing with updates..."); + } + + // Fetch the initial state of the PDA + // + // Even if initialization failed due to existing account, we can still fetch + // This demonstrates the resilience of PDA-based account management + let stateAcc = await program.account.state.fetch(statePda); + console.log("After PDA init: count =", stateAcc.count.toString()); + + // Verify the initial state is 0 + // expect() assertions ensure our program logic is working correctly + expect(stateAcc.count.toNumber()).to.equal(0); + + // First PDA update: 0 β†’ 1 + // + // Note: No signers needed here since we're only modifying an existing PDA + // The payer automatically signs, and the PDA constraints ensure we're operating + // on the correct account + await program.methods + .updatePda() + .accounts({ + state: statePda, // The PDA to be modified + payer: payer.publicKey, // Required for PDA seed validation + }) + .rpc(); + + // Verify the first update + stateAcc = await program.account.state.fetch(statePda); + console.log("After 1st PDA update: count =", stateAcc.count.toString()); + expect(stateAcc.count.toNumber()).to.equal(1); + + // Second PDA update: 1 β†’ 2 + await program.methods + .updatePda() + .accounts({ + state: statePda, + payer: payer.publicKey, + }) + .rpc(); + + // Verify the second update + stateAcc = await program.account.state.fetch(statePda); + console.log("After 2nd PDA update: count =", stateAcc.count.toString()); + expect(stateAcc.count.toNumber()).to.equal(2); + + // Third PDA update: 2 β†’ 3 + await program.methods + .updatePda() + .accounts({ + state: statePda, + payer: payer.publicKey, + }) + .rpc(); + + // Final verification + stateAcc = await program.account.state.fetch(statePda); + console.log("After 3rd PDA update: count =", stateAcc.count.toString()); + expect(stateAcc.count.toNumber()).to.equal(3); + }); +}); diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/final/lib.rs b/courses/anchor-and-programs/workshop/anchor-3h/code/final/lib.rs new file mode 100644 index 0000000..f0c9cc8 --- /dev/null +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/final/lib.rs @@ -0,0 +1,217 @@ +//! # Solana Anchor Counter Program - Finalized Reference +//! +//! This program demonstrates core Anchor concepts including: +//! - Traditional account initialization and management +//! - Program Derived Addresses (PDAs) with deterministic seeds +//! - Account constraints and validation +//! - Cross-program invocations with the System Program +//! +//! The program ID below must match the deployed program address in Anchor.toml +//! Anchor automatically generates the IDL (Interface Definition Language) from this code. + +use anchor_lang::prelude::*; + +// Program ID declaration - this creates a unique identifier for our program +// Must match the deployed program address in Anchor.toml for both localnet and devnet +declare_id!("841TgnQsPTuTihCn22RaBCTgpnAiG4QL3oQLkyrw3L6N"); + +/// Main program module containing all instruction handlers +/// Each public function becomes an instruction that clients can invoke +#[program] +pub mod counter { + use super::*; + + /// Initialize a new Counter account with count = 0 + /// + /// This instruction creates a new account on-chain and sets its initial state. + /// The `init` constraint automatically handles account creation via the System Program. + /// + /// # Accounts + /// - `counter`: New account to be created (payer funds the account creation) + /// - `payer`: Signer who pays for account creation and rent + /// - `system_program`: Required for account creation (CPI to System Program) + pub fn initialize(ctx: Context) -> Result<()> { + // Get mutable reference to the counter account + // ctx.accounts provides type-safe access to all accounts in the instruction + let counter = &mut ctx.accounts.counter; + + // Initialize the count to 0 + counter.count = 0; + + Ok(()) + } + + /// Increment the counter by 1 with overflow protection + /// + /// This instruction modifies an existing Counter account. + /// Uses `checked_add` to prevent integer overflow attacks. + /// + /// # Accounts + /// - `counter`: Existing account to be modified (must be mutable) + pub fn increment(ctx: Context) -> Result<()> { + // Get mutable reference to the counter account + let counter = &mut ctx.accounts.counter; + + // Increment with overflow protection - panics if overflow occurs + // In production, consider using `checked_add` with proper error handling + counter.count = counter.count.checked_add(1).unwrap(); + + Ok(()) + } + + /// Initialize a new PDA (Program Derived Address) with count = 0 + /// + /// PDAs are deterministic addresses derived from seeds and the program ID. + /// They allow programs to own accounts without requiring a private key. + /// The seeds ensure the same payer always gets the same PDA address. + /// + /// # Accounts + /// - `state`: PDA to be created with deterministic seeds + /// - `payer`: Signer who pays for account creation and provides seed material + /// - `system_program`: Required for PDA account creation + pub fn initialize_pda(ctx: Context) -> Result<()> { + // Get mutable reference to the PDA state account + let state = &mut ctx.accounts.state; + + // Initialize the count to 0 + state.count = 0; + + Ok(()) + } + + /// Update the PDA counter by 1 with overflow protection + /// + /// This instruction modifies an existing PDA account. + /// The PDA constraints ensure we're operating on the correct account. + /// + /// # Accounts + /// - `state`: Existing PDA to be modified + /// - `payer`: Signer who provides seed material for PDA derivation + pub fn update_pda(ctx: Context) -> Result<()> { + // Get mutable reference to the PDA state account + let state = &mut ctx.accounts.state; + + // Increment with overflow protection + state.count = state.count.checked_add(1).unwrap(); + + Ok(()) + } +} + +/// Counter account structure for traditional account management +/// +/// This struct defines the data layout for accounts created with the `init` constraint. +/// Anchor automatically handles serialization/deserialization using Borsh. +/// The 8-byte discriminator is automatically prepended by Anchor. +#[account] +pub struct Counter { + /// Current count value - stored as 8-byte unsigned integer + pub count: u64, +} + +/// State account structure for PDA-based storage +/// +/// Identical structure to Counter but used for PDA accounts. +/// Demonstrates that the same data structure can be used for both +/// traditional accounts and PDAs. +#[account] +pub struct State { + /// Current count value - stored as 8-byte unsigned integer + pub count: u64, +} + +/// Account validation context for the initialize instruction +/// +/// The `#[derive(Accounts)]` macro generates validation logic that runs +/// before the instruction handler. This ensures all accounts meet the +/// specified constraints. +#[derive(Accounts)] +pub struct Initialize<'info> { + /// New counter account to be created + /// + /// Constraints: + /// - `init`: Creates a new account (fails if account already exists) + /// - `payer = payer`: The payer account pays for account creation + /// - `space = 8 + 8`: 8 bytes for discriminator + 8 bytes for u64 count + #[account(init, payer = payer, space = 8 + 8)] + pub counter: Account<'info, Counter>, + + /// Account that signs the transaction and pays for account creation + /// + /// Constraints: + /// - `mut`: Account must be mutable (required for paying rent) + #[account(mut)] + pub payer: Signer<'info>, + + /// System Program required for account creation + /// + /// Anchor automatically validates this is the correct System Program ID + pub system_program: Program<'info, System>, +} + +/// Account validation context for the increment instruction +/// +/// Simpler context since we're only modifying an existing account. +#[derive(Accounts)] +pub struct Increment<'info> { + /// Existing counter account to be modified + /// + /// Constraints: + /// - `mut`: Account must be mutable to allow state changes + #[account(mut)] + pub counter: Account<'info, Counter>, +} + +/// Account validation context for PDA initialization +/// +/// Demonstrates PDA creation with deterministic seeds and bump validation. +#[derive(Accounts)] +pub struct InitializePda<'info> { + /// PDA state account to be created + /// + /// Constraints: + /// - `init`: Creates a new account + /// - `payer = payer`: Payer funds the account creation + /// - `space = 8 + 8`: Account size (discriminator + data) + /// - `seeds = [b"state", payer.key().as_ref()]`: Deterministic seeds + /// - `bump`: Anchor automatically finds and validates the canonical bump + #[account( + init, + payer = payer, + space = 8 + 8, + seeds = [b"state", payer.key().as_ref()], + bump + )] + pub state: Account<'info, State>, + + /// Payer account that provides seed material and funds creation + #[account(mut)] + pub payer: Signer<'info>, + + /// System Program for PDA account creation + pub system_program: Program<'info, System>, +} + +/// Account validation context for PDA updates +/// +/// Demonstrates PDA access using the same seeds used for creation. +#[derive(Accounts)] +pub struct UpdatePda<'info> { + /// Existing PDA state account to be modified + /// + /// Constraints: + /// - `mut`: Account must be mutable + /// - `seeds = [b"state", payer.key().as_ref()]`: Must match creation seeds + /// - `bump`: Validates the canonical bump for security + #[account( + mut, + seeds = [b"state", payer.key().as_ref()], + bump + )] + pub state: Account<'info, State>, + + /// Payer account that provides seed material for PDA derivation + /// + /// Note: No `mut` constraint needed since we're not modifying the payer + pub payer: Signer<'info>, +} From a96e772f2bbded1d9d174ea69d6b43f2be039be1 Mon Sep 17 00:00:00 2001 From: resourcefulmind Date: Tue, 21 Oct 2025 16:53:44 +0100 Subject: [PATCH 4/5] style: Improve presentation and formatting across codebase - Enhanced documentation formatting for professional presentation - Standardized console output formatting in client scripts - Improved readability of workshop materials and guides - Maintained all functionality while improving visual presentation --- .../workshop/anchor-3h/README.md | 68 +++++++++---------- .../workshop/anchor-3h/code/README.md | 54 +++++++-------- .../anchor-3h/code/client/callProgram.ts | 38 +++++------ .../anchor-3h/code/final/callProgram.ts | 38 +++++------ 4 files changed, 99 insertions(+), 99 deletions(-) diff --git a/courses/anchor-and-programs/workshop/anchor-3h/README.md b/courses/anchor-and-programs/workshop/anchor-3h/README.md index bca35e9..abb7640 100644 --- a/courses/anchor-and-programs/workshop/anchor-3h/README.md +++ b/courses/anchor-and-programs/workshop/anchor-3h/README.md @@ -1,11 +1,11 @@ -# 🧭 Solana Anchor & dApp Workshop (3-Hour) +# Solana Anchor & dApp Workshop (3-Hour) -## 🎯 Purpose +## Purpose This workshop introduces developers to Solana’s on-chain programming model using the **Anchor framework**, guiding them from setup to deployment and client interaction. It is designed for university builders, hackathon participants, and developers transitioning from Web2 or EVM backgrounds. -## 🧩 Scope +## Scope Participants will build, test, and interact with a simple on-chain program (counter example) while learning Solana’s account model, PDAs, and IDL-based client interaction. The session combines live coding, conceptual slides, and quick quizzes for reinforcement. @@ -32,24 +32,24 @@ By the end of the workshop, learners will be able to: --- -### 🎞️ Slides & Facilitator Notes Integration +### Slides & Facilitator Notes Integration This workshop includes a **Google Slides deck** with embedded speaker notes that guide facilitators through each concept, code demo, and discussion point. -#### πŸ“˜ Workshop Slides +#### Workshop Slides - Access the official Solana-branded slides here: - πŸ‘‰ [View Slides on Google Drive](https://docs.google.com/presentation/d/1b-Bb9AJESqjWV7A7p9I2AzLEQsu95GIgGO2frUxQ3BI/edit?usp=sharing) + [View Slides on Google Drive](https://docs.google.com/presentation/d/1b-Bb9AJESqjWV7A7p9I2AzLEQsu95GIgGO2frUxQ3BI/edit?usp=sharing) - The deck is structured into six teaching blocks that align with this README: - - 🟒 Block A β€” On-Ramp & Environment - - 🟣 Block B β€” Program Skeleton & Accounts - - πŸ”΅ Block C β€” PDA Lite - - 🟠 Block D β€” Build β†’ Deploy β†’ Test - - 🟑 Block E β€” IDL & Client Interaction - - 🟀 Block F β€” Wrap-Up & Next Steps + - Block A β€” On-Ramp & Environment + - Block B β€” Program Skeleton & Accounts + - Block C β€” PDA Lite + - Block D β€” Build β†’ Deploy β†’ Test + - Block E β€” IDL & Client Interaction + - Block F β€” Wrap-Up & Next Steps - Each block maps directly to the workshop flow and learning objectives. -#### πŸ—’οΈ Speaker Notes as Facilitator Guide +#### Speaker Notes as Facilitator Guide - The **speaker notes within the slides** contain: - Purpose and learning outcome for each slide. @@ -64,13 +64,13 @@ This workshop includes a **Google Slides deck** with embedded speaker notes that 2. **During delivery:** Present from Google Slides in *Presenter View* to see your private notes. 3. **After delivery:** Export the deck as PDF with notes to share a recap or upload to your LMS. -> πŸ’‘ *Tip:* The slides carry the visuals, the speaker notes carry your voice, and the curriculum ensures every session feels unified across the Solana Foundation’s learning network. +> *Tip:* The slides carry the visuals, the speaker notes carry your voice, and the curriculum ensures every session feels unified across the Solana Foundation's learning network. --- -## 🧱 Workshop Flow +## Workshop Flow -### 🟒 Block A β€” On-Ramp & Environment +### Block A β€” On-Ramp & Environment **Purpose:** Equip learners with the mindset and tools to begin Solana development. **Slides:** 1–6 **Learning Outcome Alignment:** (1, 3, 7) @@ -84,7 +84,7 @@ This workshop includes a **Google Slides deck** with embedded speaker notes that --- -### 🟣 Block B β€” Program Skeleton & Accounts +### Block B β€” Program Skeleton & Accounts **Purpose:** Introduce Anchor’s workspace, macros, and the Solana account model. **Slides:** 7–13 **Learning Outcome Alignment:** (2, 3, 5) @@ -98,7 +98,7 @@ This workshop includes a **Google Slides deck** with embedded speaker notes that --- -### πŸ”΅ Block C β€” PDA Lite (Program Derived Addresses) +### Block C β€” PDA Lite (Program Derived Addresses) **Purpose:** Teach secure, deterministic account creation via PDAs. **Slides:** 14–19 **Learning Outcome Alignment:** (4, 7) @@ -112,7 +112,7 @@ This workshop includes a **Google Slides deck** with embedded speaker notes that --- -### 🟠 Block D β€” Build β†’ Deploy β†’ Test +### Block D β€” Build β†’ Deploy β†’ Test **Purpose:** Compile, deploy, and validate program logic. **Slides:** 20–25 **Learning Outcome Alignment:** (5, 8) @@ -126,7 +126,7 @@ This workshop includes a **Google Slides deck** with embedded speaker notes that --- -### 🟑 Block E β€” IDL & Client Interaction +### Block E β€” IDL & Client Interaction **Purpose:** Bridge on-chain logic to a frontend client via the IDL. **Slides:** 26–31 **Learning Outcome Alignment:** (6, 7) @@ -147,7 +147,7 @@ This workshop includes a **Google Slides deck** with embedded speaker notes that βΈ» -### 🟀 Block F β€” Wrap-Up & Next Steps +### Block F β€” Wrap-Up & Next Steps **Purpose**: Reinforce learning and connect to the broader Solana curriculum. **Slides**: 32–36 @@ -163,7 +163,7 @@ This workshop includes a **Google Slides deck** with embedded speaker notes that βΈ» -### βš™οΈ Presenter Prep & Environment Setup +### Presenter Prep & Environment Setup Pre-Session Checklist: @@ -185,11 +185,11 @@ anchor test βΈ» -### 🧰 Troubleshooting +### Troubleshooting -### βš™οΈ Troubleshooting Guide +### Troubleshooting Guide -| 🧩 **Issue** | πŸ’‘ **Likely Cause** | πŸ› οΈ **Fix** | +| **Issue** | **Likely Cause** | **Fix** | |--------------|--------------------|-------------| | **Program is not deployed** | Mismatch between `declare_id!()` and `Anchor.toml` | Copy the correct key from `target/deploy/-keypair.json` and redeploy. | | **Account does not exist** | Local validator was restarted or wiped | Restart validator and re-run `anchor deploy`. | @@ -199,19 +199,19 @@ anchor test βΈ» -### πŸ”— Resources & Further Learning +### Resources & Further Learning -- πŸ“˜ [The Anchor Book](https://www.anchor-lang.com/docs) -- 🧭 [Solana Docs](https://solana.com/docs) -- 🧩 [Solana Cookbook](https://solanacookbook.com) -- πŸ” [Solana Security Guidelines](https://www.helius.dev/blog/a-hitchhikers-guide-to-solana-program-security) -- 🧠 [Solana Playground](https://beta.solpg.io) -- πŸ’¬ [Solana Discord](https://discord.gg/solana) -- πŸŽ“ [Solana Bootcamp](https://github.com/solana-developers/developer-bootcamp-2024) +- [The Anchor Book](https://www.anchor-lang.com/docs) +- [Solana Docs](https://solana.com/docs) +- [Solana Cookbook](https://solanacookbook.com) +- [Solana Security Guidelines](https://www.helius.dev/blog/a-hitchhikers-guide-to-solana-program-security) +- [Solana Playground](https://beta.solpg.io) +- [Solana Discord](https://discord.gg/solana) +- [Solana Bootcamp](https://github.com/solana-developers/developer-bootcamp-2024) --- -### 🏁 Summary +### Summary This README provides facilitators and learners with a complete guide to the **Solana Anchor & dApp Workshop** β€” aligning slides, demos, and learning objectives in one document. diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/README.md b/courses/anchor-and-programs/workshop/anchor-3h/code/README.md index 980bf81..ac2ea92 100644 --- a/courses/anchor-and-programs/workshop/anchor-3h/code/README.md +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/README.md @@ -4,7 +4,7 @@ Welcome to the live coding scaffold for the 3-Hour Solana Anchor & dApp Workshop! This is your hands-on environment where we'll build upon the 1-hour counter program and add powerful PDA (Program Derived Address) functionality with real client interaction. -## 🎯 What's New in 3h vs 1h +## What's New in 3h vs 1h ### **PDA Mastery** - **Deterministic Addresses**: Learn how PDAs create predictable, program-controlled accounts @@ -21,7 +21,7 @@ Welcome to the live coding scaffold for the 3-Hour Solana Anchor & dApp Workshop - **Error Handling**: Robust client-side error management - **Type Safety**: Leverage TypeScript for bulletproof program interactions -## πŸš€ Quick Start +## Quick Start ### Prerequisites - **Solana CLI**: `solana --version β‰₯ 1.18` @@ -60,7 +60,7 @@ solana config set --url localhost solana cluster-version ``` -## πŸ—οΈ Workshop Structure +## Workshop Structure ### **Hour 1-2: Program Development** Building on the 1h counter program (if you haven't completed it, see the [1h Workshop](../anchor-1h/) first), we'll extend it with PDA functionality: @@ -108,7 +108,7 @@ await program.methods .rpc(); ``` -## πŸ› οΈ Build & Deploy +## Build & Deploy ### **Step 1: Build the Program** ```bash @@ -140,11 +140,11 @@ anchor test **Expected Output:** ``` -βœ… initialize β†’ increment β†’ fetch -βœ… PDA: derive β†’ initialize β†’ update β†’ fetch +- initialize β†’ increment β†’ fetch +- PDA: derive β†’ initialize β†’ update β†’ fetch ``` -## πŸ–₯️ Client Demo +## Client Demo ### **Step 1: Install Dependencies** ```bash @@ -160,31 +160,31 @@ ANCHOR_PROVIDER_URL=http://localhost:8899 ANCHOR_WALLET=~/.config/solana/id.json **Expected Output:** ``` -πŸš€ Starting Anchor 3h Workshop Client Demo +Starting Anchor 3h Workshop Client Demo ========================================== -πŸ“‹ Program ID: [YOUR_PROGRAM_ID] -πŸ‘€ Payer: [YOUR_WALLET_ADDRESS] -πŸ”‘ Derived PDA: [YOUR_PDA_ADDRESS] -πŸ“Š Bump: [YOUR_BUMP_VALUE] +Program ID: [YOUR_PROGRAM_ID] +Payer: [YOUR_WALLET_ADDRESS] +Derived PDA: [YOUR_PDA_ADDRESS] +Bump: [YOUR_BUMP_VALUE] -πŸ“ Initializing PDA... -βœ… PDA initialized! Count = 0 +Initializing PDA... +PDA initialized! Count = 0 -πŸ“ˆ Updating PDA (1st time)... -βœ… PDA updated! Count = 1 +Updating PDA (1st time)... +PDA updated! Count = 1 -πŸ“ˆ Updating PDA (2nd time)... -βœ… PDA updated! Count = 2 +Updating PDA (2nd time)... +PDA updated! Count = 2 -πŸ“ˆ Updating PDA (3rd time)... -βœ… PDA updated! Count = 3 +Updating PDA (3rd time)... +PDA updated! Count = 3 -πŸŽ‰ Client demo completed successfully! -πŸ“Š Final PDA state: count = 3 -πŸ”— PDA address: [YOUR_PDA_ADDRESS] +Client demo completed successfully! +Final PDA state: count = 3 +PDA address: [YOUR_PDA_ADDRESS] ``` -## βœ… Workshop Completion Checklist +## Workshop Completion Checklist Mark these off as you complete them: @@ -297,14 +297,14 @@ export ANCHOR_WALLET=~/.config/solana/id.json yarn ts-node callProgram.ts ``` -## πŸ“š Resources +## Resources - **[Anchor Book](https://book.anchor-lang.com/)**: Comprehensive Anchor documentation - **[Solana Docs - PDAs](https://solana.com/docs/core/pda)**: Official PDA documentation - **[Solana Cookbook](https://solanacookbook.com/core-concepts/pdas.html)**: Practical PDA examples - **[1h Workshop](../anchor-1h/)**: Foundation concepts -## 🎯 Next Steps +## Next Steps After completing this workshop: 1. **Explore Advanced PDAs**: Multi-seed PDAs, cross-program invocations @@ -314,6 +314,6 @@ After completing this workshop: --- -**Ready to build the future of decentralized applications? Let's dive in!** πŸš€ +**Ready to build the future of decentralized applications? Let's dive in!** *This scaffold is designed for live coding sessions. All commands are tested and ready to run.* diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/client/callProgram.ts b/courses/anchor-and-programs/workshop/anchor-3h/code/client/callProgram.ts index 742843f..ed15bcc 100644 --- a/courses/anchor-and-programs/workshop/anchor-3h/code/client/callProgram.ts +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/client/callProgram.ts @@ -31,7 +31,7 @@ import idl from "./counter.json"; * 5. Fetch and verify account state changes */ async function main() { - console.log("πŸš€ Starting Anchor 3h Workshop Client Demo"); + console.log("Starting Anchor 3h Workshop Client Demo"); console.log("=========================================="); // Set up the Anchor provider for localnet connection @@ -58,7 +58,7 @@ async function main() { // - The program's IDL account on-chain // - A package registry like npm const program = new Program(idl as anchor.Idl, provider); - console.log("πŸ“‹ Program ID:", program.programId.toString()); + console.log("Program ID:", program.programId.toString()); // Get the payer wallet from the provider // @@ -72,7 +72,7 @@ async function main() { // - Hardware wallets like Ledger // - Mobile wallet apps const payer = provider.wallet as anchor.Wallet; - console.log("πŸ‘€ Payer:", payer.publicKey.toString()); + console.log("Payer:", payer.publicKey.toString()); // Derive the PDA using findProgramAddressSync // @@ -90,8 +90,8 @@ async function main() { [Buffer.from("state"), payer.publicKey.toBuffer()], program.programId ); - console.log("πŸ”‘ Derived PDA:", statePda.toString()); - console.log("πŸ“Š Bump:", bump); + console.log("Derived PDA:", statePda.toString()); + console.log("Bump:", bump); try { // Initialize the PDA account (starts at 0) @@ -99,7 +99,7 @@ async function main() { // We use nested try-catch blocks to handle the case where // the PDA already exists from previous runs. This makes the // script more resilient and allows for multiple executions. - console.log("\nπŸ“ Initializing PDA..."); + console.log("\nInitializing PDA..."); try { await program.methods .initializePda() @@ -113,7 +113,7 @@ async function main() { // If PDA already exists, that's fine - we can still test updates // This is a common pattern in client applications to handle // account reuse and idempotent operations - console.log("βœ… PDA already exists, continuing with updates..."); + console.log("PDA already exists, continuing with updates..."); } // Fetch the initial state using direct connection access @@ -129,14 +129,14 @@ async function main() { // - First 8 bytes: Anchor discriminator (automatically added) // - Next 8 bytes: Our u64 count value (little-endian) const count = stateData.readBigUInt64LE(8); // Skip 8-byte discriminator - console.log("βœ… PDA initialized! Count =", count.toString()); + console.log("PDA initialized! Count =", count.toString()); } // First PDA update: 0 β†’ 1 // // This demonstrates how a frontend would trigger state changes // in response to user interactions (button clicks, form submissions, etc.) - console.log("\nπŸ“ˆ Updating PDA (1st time)..."); + console.log("\nUpdating PDA (1st time)..."); await program.methods .updatePda() .accounts({ @@ -150,14 +150,14 @@ async function main() { if (stateAccountInfo1) { const stateData = stateAccountInfo1.data; const count = stateData.readBigUInt64LE(8); - console.log("βœ… PDA updated! Count =", count.toString()); + console.log("PDA updated! Count =", count.toString()); } // Second PDA update: 1 β†’ 2 // // Multiple updates demonstrate how frontend applications // can perform sequential operations and track state changes - console.log("\nπŸ“ˆ Updating PDA (2nd time)..."); + console.log("\nUpdating PDA (2nd time)..."); await program.methods .updatePda() .accounts({ @@ -171,13 +171,13 @@ async function main() { if (stateAccountInfo2) { const stateData = stateAccountInfo2.data; const count = stateData.readBigUInt64LE(8); - console.log("βœ… PDA updated! Count =", count.toString()); + console.log("PDA updated! Count =", count.toString()); } // Third PDA update: 2 β†’ 3 // // Final update to demonstrate the complete workflow - console.log("\nπŸ“ˆ Updating PDA (3rd time)..."); + console.log("\nUpdating PDA (3rd time)..."); await program.methods .updatePda() .accounts({ @@ -191,10 +191,10 @@ async function main() { if (stateAccountInfo3) { const stateData = stateAccountInfo3.data; const count = stateData.readBigUInt64LE(8); - console.log("βœ… PDA updated! Count =", count.toString()); - console.log("\nπŸŽ‰ Client demo completed successfully!"); - console.log("πŸ“Š Final PDA state: count =", count.toString()); - console.log("πŸ”— PDA address:", statePda.toString()); + console.log("PDA updated! Count =", count.toString()); + console.log("\nClient demo completed successfully!"); + console.log("Final PDA state: count =", count.toString()); + console.log("PDA address:", statePda.toString()); } } catch (error) { @@ -205,7 +205,7 @@ async function main() { // - Log errors for debugging // - Provide retry mechanisms // - Handle specific error types differently - console.error("❌ Error:", error); + console.error("Error:", error); process.exit(1); } } @@ -217,6 +217,6 @@ async function main() { // you would handle errors by updating the UI state rather // than exiting the process. main().catch((error) => { - console.error("❌ Fatal error:", error); + console.error("Fatal error:", error); process.exit(1); }); diff --git a/courses/anchor-and-programs/workshop/anchor-3h/code/final/callProgram.ts b/courses/anchor-and-programs/workshop/anchor-3h/code/final/callProgram.ts index 742843f..ed15bcc 100644 --- a/courses/anchor-and-programs/workshop/anchor-3h/code/final/callProgram.ts +++ b/courses/anchor-and-programs/workshop/anchor-3h/code/final/callProgram.ts @@ -31,7 +31,7 @@ import idl from "./counter.json"; * 5. Fetch and verify account state changes */ async function main() { - console.log("πŸš€ Starting Anchor 3h Workshop Client Demo"); + console.log("Starting Anchor 3h Workshop Client Demo"); console.log("=========================================="); // Set up the Anchor provider for localnet connection @@ -58,7 +58,7 @@ async function main() { // - The program's IDL account on-chain // - A package registry like npm const program = new Program(idl as anchor.Idl, provider); - console.log("πŸ“‹ Program ID:", program.programId.toString()); + console.log("Program ID:", program.programId.toString()); // Get the payer wallet from the provider // @@ -72,7 +72,7 @@ async function main() { // - Hardware wallets like Ledger // - Mobile wallet apps const payer = provider.wallet as anchor.Wallet; - console.log("πŸ‘€ Payer:", payer.publicKey.toString()); + console.log("Payer:", payer.publicKey.toString()); // Derive the PDA using findProgramAddressSync // @@ -90,8 +90,8 @@ async function main() { [Buffer.from("state"), payer.publicKey.toBuffer()], program.programId ); - console.log("πŸ”‘ Derived PDA:", statePda.toString()); - console.log("πŸ“Š Bump:", bump); + console.log("Derived PDA:", statePda.toString()); + console.log("Bump:", bump); try { // Initialize the PDA account (starts at 0) @@ -99,7 +99,7 @@ async function main() { // We use nested try-catch blocks to handle the case where // the PDA already exists from previous runs. This makes the // script more resilient and allows for multiple executions. - console.log("\nπŸ“ Initializing PDA..."); + console.log("\nInitializing PDA..."); try { await program.methods .initializePda() @@ -113,7 +113,7 @@ async function main() { // If PDA already exists, that's fine - we can still test updates // This is a common pattern in client applications to handle // account reuse and idempotent operations - console.log("βœ… PDA already exists, continuing with updates..."); + console.log("PDA already exists, continuing with updates..."); } // Fetch the initial state using direct connection access @@ -129,14 +129,14 @@ async function main() { // - First 8 bytes: Anchor discriminator (automatically added) // - Next 8 bytes: Our u64 count value (little-endian) const count = stateData.readBigUInt64LE(8); // Skip 8-byte discriminator - console.log("βœ… PDA initialized! Count =", count.toString()); + console.log("PDA initialized! Count =", count.toString()); } // First PDA update: 0 β†’ 1 // // This demonstrates how a frontend would trigger state changes // in response to user interactions (button clicks, form submissions, etc.) - console.log("\nπŸ“ˆ Updating PDA (1st time)..."); + console.log("\nUpdating PDA (1st time)..."); await program.methods .updatePda() .accounts({ @@ -150,14 +150,14 @@ async function main() { if (stateAccountInfo1) { const stateData = stateAccountInfo1.data; const count = stateData.readBigUInt64LE(8); - console.log("βœ… PDA updated! Count =", count.toString()); + console.log("PDA updated! Count =", count.toString()); } // Second PDA update: 1 β†’ 2 // // Multiple updates demonstrate how frontend applications // can perform sequential operations and track state changes - console.log("\nπŸ“ˆ Updating PDA (2nd time)..."); + console.log("\nUpdating PDA (2nd time)..."); await program.methods .updatePda() .accounts({ @@ -171,13 +171,13 @@ async function main() { if (stateAccountInfo2) { const stateData = stateAccountInfo2.data; const count = stateData.readBigUInt64LE(8); - console.log("βœ… PDA updated! Count =", count.toString()); + console.log("PDA updated! Count =", count.toString()); } // Third PDA update: 2 β†’ 3 // // Final update to demonstrate the complete workflow - console.log("\nπŸ“ˆ Updating PDA (3rd time)..."); + console.log("\nUpdating PDA (3rd time)..."); await program.methods .updatePda() .accounts({ @@ -191,10 +191,10 @@ async function main() { if (stateAccountInfo3) { const stateData = stateAccountInfo3.data; const count = stateData.readBigUInt64LE(8); - console.log("βœ… PDA updated! Count =", count.toString()); - console.log("\nπŸŽ‰ Client demo completed successfully!"); - console.log("πŸ“Š Final PDA state: count =", count.toString()); - console.log("πŸ”— PDA address:", statePda.toString()); + console.log("PDA updated! Count =", count.toString()); + console.log("\nClient demo completed successfully!"); + console.log("Final PDA state: count =", count.toString()); + console.log("PDA address:", statePda.toString()); } } catch (error) { @@ -205,7 +205,7 @@ async function main() { // - Log errors for debugging // - Provide retry mechanisms // - Handle specific error types differently - console.error("❌ Error:", error); + console.error("Error:", error); process.exit(1); } } @@ -217,6 +217,6 @@ async function main() { // you would handle errors by updating the UI state rather // than exiting the process. main().catch((error) => { - console.error("❌ Fatal error:", error); + console.error("Fatal error:", error); process.exit(1); }); From d86bee01d9b8cc3e623c811cac0d69a7101abb96 Mon Sep 17 00:00:00 2001 From: resourcefulmind Date: Tue, 21 Oct 2025 17:00:29 +0100 Subject: [PATCH 5/5] style: Improve presentation and formatting across codebase --- .../workshop/anchor-1h/README.md | 6 ++-- .../workshop/anchor-1h/code/final/Anchor.toml | 4 +-- .../workshop/anchor-1h/code/final/README.md | 4 +-- .../code/final/programs/counter/src/lib.rs | 30 +++++++++---------- .../anchor-1h/code/final/tests/counter.ts | 28 ++++++++--------- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/courses/anchor-and-programs/workshop/anchor-1h/README.md b/courses/anchor-and-programs/workshop/anchor-1h/README.md index 4400c1e..35b6ecf 100644 --- a/courses/anchor-and-programs/workshop/anchor-1h/README.md +++ b/courses/anchor-and-programs/workshop/anchor-1h/README.md @@ -224,9 +224,9 @@ Expected: Before teaching this workshop, review the **reference implementation** in `/code/final/`: -- πŸ“– [final/README.md](code/final/README.md) - Teaching notes, troubleshooting, FAQs -- πŸ’» [final/programs/counter/src/lib.rs](code/final/programs/counter/src/lib.rs) - Deeply commented program -- πŸ§ͺ [final/tests/counter.ts](code/final/tests/counter.ts) - Annotated test suite +- [final/README.md](code/final/README.md) - Teaching notes, troubleshooting, FAQs +- [final/programs/counter/src/lib.rs](code/final/programs/counter/src/lib.rs) - Deeply commented program +- [final/tests/counter.ts](code/final/tests/counter.ts) - Annotated test suite ### Official Resources diff --git a/courses/anchor-and-programs/workshop/anchor-1h/code/final/Anchor.toml b/courses/anchor-and-programs/workshop/anchor-1h/code/final/Anchor.toml index 74fb8e2..d12928f 100644 --- a/courses/anchor-and-programs/workshop/anchor-1h/code/final/Anchor.toml +++ b/courses/anchor-and-programs/workshop/anchor-1h/code/final/Anchor.toml @@ -8,8 +8,8 @@ # 3. [provider] sets default cluster and wallet # 4. [scripts] defines test commands # -# πŸ”— Why separate clusters? Different environments (local vs devnet vs mainnet) -# πŸ”— Why same Program ID? Consistency across environments +# Why separate clusters? Different environments (local vs devnet vs mainnet) +# Why same Program ID? Consistency across environments # πŸ“– https://www.anchor-lang.com/docs [toolchain] diff --git a/courses/anchor-and-programs/workshop/anchor-1h/code/final/README.md b/courses/anchor-and-programs/workshop/anchor-1h/code/final/README.md index fd78e4d..c978294 100644 --- a/courses/anchor-and-programs/workshop/anchor-1h/code/final/README.md +++ b/courses/anchor-and-programs/workshop/anchor-1h/code/final/README.md @@ -4,7 +4,7 @@ **Audience:** Instructors preparing to teach the 1-hour Anchor workshop. **Not for students:** Students work in `/code/counter/` with minimal comments. -## ⚠️ Important: This is Reference Only +## Important: This is Reference Only **Students work in `/code/counter/`** - this directory is for presenter study only. @@ -82,7 +82,7 @@ A: No - Rust doesn't have ++ operator. But more importantly, we want checked_add ### Wallet Hygiene -⚠️ **Critical Safety Message** +**Critical Safety Message** - **Dev wallet only**: Never use production keys in workshops - **Devnet β‰  Mainnet**: Make this distinction crystal clear diff --git a/courses/anchor-and-programs/workshop/anchor-1h/code/final/programs/counter/src/lib.rs b/courses/anchor-and-programs/workshop/anchor-1h/code/final/programs/counter/src/lib.rs index b26739c..33e6cc9 100644 --- a/courses/anchor-and-programs/workshop/anchor-1h/code/final/programs/counter/src/lib.rs +++ b/courses/anchor-and-programs/workshop/anchor-1h/code/final/programs/counter/src/lib.rs @@ -26,8 +26,8 @@ use anchor_lang::prelude::*; // 2. programs/counter/keys/counter-keypair.json // 3. The deployed program address // -// πŸ”— Why? Solana uses this ID to route transactions to your program. -// πŸ”— PDAs (covered in Week 4) derive from this ID. +// Why? Solana uses this ID to route transactions to your program. +// PDAs (covered in Week 4) derive from this ID. // πŸ“– https://solana.com/docs/core/programs#program-derived-addresses declare_id!("FoKCfkWjCJxuHLxHGkzdQ3VXrFgrnGdq4xNsZLnjJLbK"); @@ -40,8 +40,8 @@ declare_id!("FoKCfkWjCJxuHLxHGkzdQ3VXrFgrnGdq4xNsZLnjJLbK"); // Think of this as your program's "API surface" - what external // clients can call. Each pub fn becomes an instruction handler. // -// πŸ”— Context: Anchor's account safety system -// πŸ”— Result<()>: Rust error handling (vs panics in production) +// Context: Anchor's account safety system +// Result<()>: Rust error handling (vs panics in production) // πŸ“– https://www.anchor-lang.com/docs #[program] pub mod counter { @@ -55,8 +55,8 @@ pub mod counter { // - Setting count = 0: explicit initialization (vs undefined behavior) // - Space calculation: 8 (discriminator) + 8 (u64) = 16 bytes // - // πŸ”— Why &mut? Solana enforces exclusive access for data integrity - // πŸ”— Why explicit init? Prevents reading uninitialized memory + // Why &mut? Solana enforces exclusive access for data integrity + // Why explicit init? Prevents reading uninitialized memory // πŸ“– https://www.anchor-lang.com/docs pub fn initialize(ctx: Context) -> Result<()> { let counter = &mut ctx.accounts.counter; @@ -72,8 +72,8 @@ pub mod counter { // - unwrap(): safe here because we're adding 1 to a u64 // - State persists: Solana stores account data on-chain // - // πŸ”— Why checked_add? Prevents integer overflow attacks - // πŸ”— Why unwrap? In production, handle Result properly + // Why checked_add? Prevents integer overflow attacks + // Why unwrap? In production, handle Result properly // πŸ“– https://doc.rust-lang.org/std/primitive.u64.html#method.checked_add pub fn increment(ctx: Context) -> Result<()> { let counter = &mut ctx.accounts.counter; @@ -93,8 +93,8 @@ pub mod counter { // - Why not Vec or String? Complexity vs learning goals // - Discriminator: hash of "account:Counter" for type safety // -// πŸ”— Borsh: Binary Object Representation Serializer for Hashing -// πŸ”— Discriminator: prevents account type confusion +// Borsh: Binary Object Representation Serializer for Hashing +// Discriminator: prevents account type confusion // πŸ“– https://www.anchor-lang.com/docs #[account] pub struct Counter { @@ -111,9 +111,9 @@ pub struct Counter { // - System program: Solana's "account factory" (creates accounts) // - Lifetimes ('info): Rust memory safety (brief note) // -// πŸ”— init constraint: creates account if it doesn't exist -// πŸ”— space = 8 + 8: discriminator (8) + u64 (8) = 16 bytes -// πŸ”— Signer: proves caller owns the private key +// init constraint: creates account if it doesn't exist +// space = 8 + 8: discriminator (8) + u64 (8) = 16 bytes +// Signer: proves caller owns the private key // πŸ“– https://www.anchor-lang.com/docs // πŸ“– https://solana.com/docs/core/transactions#signatures #[derive(Accounts)] @@ -134,8 +134,8 @@ pub struct Initialize<'info> { // - Minimal constraints: show evolution to complex checks // - Preview: add ownership checks, PDAs in advanced lessons // -// πŸ”— mut constraint: account must be mutable for writes -// πŸ”— No init: account must already exist +// mut constraint: account must be mutable for writes +// No init: account must already exist // πŸ“– https://www.anchor-lang.com/docs #[derive(Accounts)] pub struct Increment<'info> { diff --git a/courses/anchor-and-programs/workshop/anchor-1h/code/final/tests/counter.ts b/courses/anchor-and-programs/workshop/anchor-1h/code/final/tests/counter.ts index 8f5cff3..91fa6bb 100644 --- a/courses/anchor-and-programs/workshop/anchor-1h/code/final/tests/counter.ts +++ b/courses/anchor-and-programs/workshop/anchor-1h/code/final/tests/counter.ts @@ -30,8 +30,8 @@ describe("counter", () => { // This is the "connection + wallet" bundle that Anchor needs // to send transactions and sign them. // - // πŸ”— Why .env()? Consistent environment across team members - // πŸ”— Why not manual setup? Reduces boilerplate and errors + // Why .env()? Consistent environment across team members + // Why not manual setup? Reduces boilerplate and errors // πŸ“– https://www.anchor-lang.com/docs/testing#provider anchor.setProvider(anchor.AnchorProvider.env()); @@ -45,8 +45,8 @@ describe("counter", () => { // - workspace: Anchor's program registry // - .methods.*: instruction handlers become method calls // - // πŸ”— IDL: JSON schema of your program's interface - // πŸ”— Type safety: TypeScript knows your program's shape + // IDL: JSON schema of your program's interface + // Type safety: TypeScript knows your program's shape // πŸ“– https://www.anchor-lang.com/docs/testing#program const program = anchor.workspace.counter as Program; @@ -57,8 +57,8 @@ describe("counter", () => { // 1. Sign transactions (cryptographic authorization) // 2. Pay for account creation (rent-exempt minimum balance) // - // πŸ”— Signer: proves ownership of private key - // πŸ”— Payer: pays transaction fees + account creation costs + // Signer: proves ownership of private key + // Payer: pays transaction fees + account creation costs // πŸ“– https://solana.com/docs/core/transactions#signatures const provider = program.provider as anchor.AnchorProvider; const payer = provider.wallet as anchor.Wallet; @@ -72,8 +72,8 @@ describe("counter", () => { // Alternative: reuse a PDA (Program Derived Address) // but that's advanced and requires seeds. // - // πŸ”— Keypair: public key + private key pair - // πŸ”— .generate(): cryptographically secure random generation + // Keypair: public key + private key pair + // .generate(): cryptographically secure random generation // πŸ“– https://solana-labs.github.io/solana-web3.js/classes/Keypair.html const counterKeypair = anchor.web3.Keypair.generate(); @@ -86,8 +86,8 @@ describe("counter", () => { // - .signers(): who authorizes this transaction // - .rpc(): send transaction + wait for confirmation // - // πŸ”— Transaction = instruction + accounts + signers - // πŸ”— systemProgram: Solana's account creation program + // Transaction = instruction + accounts + signers + // systemProgram: Solana's account creation program // πŸ“– https://www.anchor-lang.com/docs/testing#calling-instructions await program.methods .initialize() @@ -107,8 +107,8 @@ describe("counter", () => { // - No systemProgram: no account creation // - Just the counter account: matches Increment<'info> struct // - // πŸ”— State persistence: Solana stores account data on-chain - // πŸ”— Multiple calls: prove state survives across transactions + // State persistence: Solana stores account data on-chain + // Multiple calls: prove state survives across transactions await program.methods .increment() .accounts({ counter: counterKeypair.publicKey }) @@ -121,8 +121,8 @@ describe("counter", () => { // 2. The increment worked (count = 1) // 3. State persists between transactions // - // πŸ”— .fetch(): deserialize on-chain data to TypeScript - // πŸ”— .toString(): convert BigNum to readable string + // .fetch(): deserialize on-chain data to TypeScript + // .toString(): convert BigNum to readable string // πŸ“– https://www.anchor-lang.com/docs/testing#fetching-accounts let counterAcc = await program.account.counter.fetch( counterKeypair.publicKey