Sol.College

Full lesson

Apis

What it does

From the project README:

Pay USDC, get IPFS results, settled on-chain via an open Anchor program. No accounts, no middleman, no vendor lock-in. Buyers post jobs, registered workers pick them up, escrow releases on proof of completion.
Original
hu-oscar/Apis
SuperStack fork
Blockchains/Apis @ 468247b
Stars
0
Last push (original)
2026-07-21
Languages
TypeScript, Python, Rust, CSS, JavaScript, HTML
Detected stack
@solana/kitAnchorNext.jssolana-pyweb3.js
Categories
Other
Licence
Apache-2.0
Homepage
https://apis-web-five.vercel.app

Architecture

192 source files. By extension: .ts ×121, .tsx ×25, .rs ×24, .py ×21, .js ×1.

Top-level directorySource files
packages192

On-chain programs: packages/program/programs/apis_program

Dapp: devnet deploy pending

⏳ Devnet deploy pending — goes live when the devnet wallet is topped up. This fork's own dapp is not deployed yet, so there is no program address or transaction to show. It is queued in dapp-queue.txt; once devnet SOL is available it is deployed, exercised with real devnet transactions, and this section switches to the live dapp at /dapps/apis/. Until then, the lesson and step-by-step guide below are complete, and you can run it locally with the commands below.

Key code, explained

Excerpts chosen automatically where the code uses the most Solana concepts, pinned to the fork's commit.

hu-oscar/Apis · packages/program/programs/apis_program/src/instructions/confirm_completion.rs L145–168 @ 468247b · Apache-2.0 · original
        .checked_sub(fee)        .ok_or(ApisError::ArithmeticOverflow)?;     // Pre-CPI invariant — vault must hold exactly the locked price.    require!(        ctx.accounts.escrow_vault.amount == price,        ApisError::VaultAmountMismatch    );     // Build PDA signer seeds for the vault transfers + close.    let buyer_key = ctx.accounts.job.buyer;    let id_le = ctx.accounts.job.id.to_le_bytes();    let job_bump = ctx.accounts.job.bump;    let job_seeds: &[&[u8]] = &[b"job", buyer_key.as_ref(), &id_le, &[job_bump]];    let signer_seeds = &[job_seeds];     let token_program = ctx.accounts.token_program.key();    let mint_decimals = ctx.accounts.usdc_mint.decimals;     // Payout to provider (omit if 0 — happens only at fee_bps == 10_000).    if payout > 0 {        let cpi_accounts = TransferChecked {            from: ctx.accounts.escrow_vault.to_account_info(),            mint: ctx.accounts.usdc_mint.to_account_info(),
  1. Line 145 .checked_sub(fee): Uses checked arithmetic so overflow fails instead of wrapping.
  2. Line 149 require!(: An explicit validation that aborts the instruction with an error.
  3. Line 157 let job_bump = ctx.accounts.job.bump;: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  4. Line 159 let signer_seeds = &[job_seeds];: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  5. Line 166 let cpi_accounts = TransferChecked {: A checked token transfer, which also verifies the mint and decimals.
hu-oscar/Apis · packages/program/programs/apis_program/src/instructions/create_job.rs L109–132 @ 468247b · Apache-2.0 · original
        to: ctx.accounts.escrow_vault.to_account_info(),        authority: ctx.accounts.buyer.to_account_info(),    };    let cpi_ctx = CpiContext::new(ctx.accounts.token_program.key(), cpi_accounts);    transfer_checked(cpi_ctx, price_lamports_usdc, ctx.accounts.usdc_mint.decimals)?;     // Re-read the vault after the CPI; assert the exact amount we asked    // to transfer landed (catches partial-fill edge cases under    // Token-2022 transfer hooks, etc.).    ctx.accounts.escrow_vault.reload()?;    require!(        ctx.accounts.escrow_vault.amount == price_lamports_usdc,        ApisError::VaultAmountMismatch    );     let now = Clock::get()?.unix_timestamp;    let deadline = now        .checked_add(deadline_offset_secs)        .ok_or(ApisError::InvalidDeadline)?;     let job = &mut ctx.accounts.job;    job.set_inner(Job {        id,        buyer: ctx.accounts.buyer.key(),
  1. Line 112 let cpi_ctx = CpiContext::new(ctx.accounts.token_program.key(), cpi_accounts);: Makes a cross-program invocation into another program.
  2. Line 113 transfer_checked(cpi_ctx, price_lamports_usdc, ctx.accounts.usdc_mint.decimals)?;: A checked token transfer, which also verifies the mint and decimals.
  3. Line 119 require!(: An explicit validation that aborts the instruction with an error.
  4. Line 124 let now = Clock::get()?.unix_timestamp;: Reads on-chain time from the Clock sysvar.
  5. Line 126 .checked_add(deadline_offset_secs): Uses checked arithmetic so overflow fails instead of wrapping.
hu-oscar/Apis · packages/program/programs/apis_program/src/instructions/cancel_job.rs L25–47 @ 468247b · Apache-2.0 · original
    /// Buyer cancelling. Receives the USDC refund + vault/Job rent.    #[account(mut)]    pub buyer: Signer<'info>,     /// Singleton config — only consulted for `usdc_mint`.    #[account(seeds = [b"config"], bump = config.bump)]    pub config: Account<'info, GlobalConfig>,     /// Job to cancel. Must be the buyer's, Funded, and gets closed    /// (rent → buyer) via Anchor's `close = buyer`.    #[account(        mut,        close = buyer,        seeds = [b"job", buyer.key().as_ref(), job.id.to_le_bytes().as_ref()],        bump = job.bump,        has_one = buyer @ ApisError::WrongBuyer,        constraint = job.status == JobStatus::Funded @ ApisError::JobNotFunded,    )]    pub job: Account<'info, Job>,     /// USDC mint — matches config.usdc_mint.    #[account(address = config.usdc_mint @ ApisError::WrongMint)]    pub usdc_mint: Account<'info, Mint>,
  1. Line 27 pub buyer: Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 30 #[account(seeds = [b"config"], bump = config.bump)]: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  3. Line 30 #[account(seeds = [b"config"], bump = config.bump)]: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  4. Line 34 /// (rent → buyer) via Anchor's `close = buyer`.: Closes the account at the end of the instruction and refunds its lamports.
  5. Line 40 has_one = buyer @ ApisError::WrongBuyer,: Checks that a stored pubkey field matches the passed account.
hu-oscar/Apis · packages/program/programs/apis_program/src/instructions/accept_job.rs L13–36 @ 468247b · Apache-2.0 · original
/// - `job.provider == provider.key()` (the job was targeted to this///   provider, not someone else)/// - `job.status == Funded` (no double-accept; no jumping a non-funded///   job)#[derive(Accounts)]pub struct AcceptJob<'info> {    /// Provider's authority — must match `provider.authority`.    pub authority: Signer<'info>,     /// Provider PDA the signer owns.    #[account(        seeds = [b"provider", authority.key().as_ref()],        bump = provider.bump,        has_one = authority @ ApisError::WrongProviderAuthority,        constraint = provider.status == ProviderStatus::Active            @ ApisError::ProviderNotActive,    )]    pub provider: Account<'info, Provider>,     /// Job to accept. Must target this provider and be in `Funded`    /// status. We rederive the PDA from `job.buyer` / `job.id` to make    /// sure the caller hasn't substituted a fake account at the same    /// type — Anchor will deserialise + then verify seeds match.    #[account(
  1. Line 20 pub authority: Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 24 seeds = [b"provider", authority.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  3. Line 25 bump = provider.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  4. Line 26 has_one = authority @ ApisError::WrongProviderAuthority,: Checks that a stored pubkey field matches the passed account.

How to run, build and test

These commands come from the repo's own manifests (package.json scripts, Anchor.toml, Cargo, go.mod, Makefile). Check the README for prerequisites and environment variables.

DirectoryCommandRuns
.npm run buildpnpm -r --parallel build
.npm run testpnpm -r --parallel test
.npm run lintpnpm -r --parallel lint
packages/programnpm run lintprettier */*.js "*/**/*{.js,.ts}" --check
packages/webnpm run devnext dev
packages/webnpm run startnext start
packages/webnpm run buildnext build
packages/webnpm run testecho "web: tests will be added in W2" && exit 0
packages/webnpm run linteslint
packages/mcpnpm run devtsx watch src/index.ts
packages/mcpnpm run starttsx src/index.ts
packages/mcpnpm run buildtsc
packages/mcpnpm run testecho "mcp: no tests yet" && exit 0
packages/apis-providernpm run devvite
packages/apis-providernpm run buildtsc && vite build
packages/agentnpm run buildtsc

What you could build from it

Used in lessons