Full lesson
accel-AgentAuth
What it does
From the project README:
Solana authenticates keys; it does not say what a delegated key is allowed to do. An agent that holds a key can do whatever the key can do, including what a prompt injection tells it to.
- Original
- Turbin3/accel-AgentAuth
- SuperStack fork
- Blockchains/accel-AgentAuth @
5a74154 - Stars
- 2
- Last push (original)
- 2026-10-13
- Languages
- TypeScript, Rust, Shell, JavaScript, Python, CSS
- Detected stack
- @solana/kitNext.jsPinocchio
- Categories
- AI & AgentsWallets & Identity
- Licence
- MIT
Architecture
374 source files. By extension: .ts ×215, .rs ×106, .tsx ×43, .js ×7, .py ×3.
| Top-level directory | Source files |
|---|---|
packages | 150 |
apps | 115 |
programs | 65 |
tests | 18 |
src | 12 |
crates | 8 |
security | 6 |
On-chain programs: programs/evil-token, programs/gated-example, programs/mock-amm, programs/relay, programs/relay/tests/common/stubs/always_ok
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/accel-agentauth/. Until then, the lesson and step-by-step guide below are complete, and you can run it locally with the commands below.
Key code, explained
Excerpts chosen automatically where the code uses the most Solana concepts, pinned to the fork's commit.
programs/mock-amm/src/swap.rs L109–132 @ 5a74154 · MIT · original token_program: s.in_token_program, from: s.in_account, mint: s.in_mint, to: s.in_vault, authority: s.payer, amount: pull, decimals: s.in_decimals, } .invoke_signed(&[])?; // 2. pay the output from the output vault, authority = pool PDA. let destination = if s.pool.mode == Mode::RedirectOutput { s.in_account } else { s.out_account }; let bump = [POOL_AUTHORITY_BUMP]; let seeds = [Seed::from(POOL_AUTHORITY_SEED), Seed::from(&bump)]; TransferChecked { token_program: s.out_token_program, from: s.out_vault, mint: s.out_mint, to: destination, authority: s.authority,- Line 117
.invoke_signed(&[])?;: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address. - Line 125
let bump = [POOL_AUTHORITY_BUMP];: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 126
let seeds = [Seed::from(POOL_AUTHORITY_SEED), Seed::from(&bump)];: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 127
TransferChecked {: A checked token transfer, which also verifies the mint and decimals.
src/instructions/create_mandate.rs L37–60 @ 5a74154 · MIT · original }; if !owner.is_signer() { return Err(MandateError::InvalidSigner.into()); } if owner.key() == agent.key() { return Err(MandateError::InvalidAgent.into()); } let (expiry, rest) = take_u64(data)?; expect_end(rest)?; let expiry = expiry as i64; let (expected, bump) = find_program_address( &[MANDATE_SEED, owner.key().as_ref(), agent.key().as_ref()], program_id, ); if mandate_account.key() != &expected { return Err(MandateError::InvalidAccountType.into()); } if expiry <= Clock::get()?.unix_timestamp { return Err(MandateError::InvalidExpiry.into()); } if !mandate_account.is_writable() { return Err(ProgramError::InvalidAccountData); }- Line 48
let (expected, bump) = find_program_address(: Derives a PDA from seeds and the program ID and returns the address with its canonical bump. - Line 48
let (expected, bump) = find_program_address(: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 55
if expiry <= Clock::get()?.unix_timestamp {: Reads on-chain time from the Clock sysvar.
programs/relay/src/cpi.rs L61–84 @ 5a74154 · MIT · original AccountMeta::readonly(owner.key()), AccountMeta::readonly_signer(agent.key()), ]; let ix = Instruction { program_id: &AGENT_AUTH_ID, accounts: &metas, data: &data, }; invoke_signed_with_bounds::<3>(&ix, &[mandate, owner, agent], &[])} /// Accounts of the `TransferChecked` CPI.pub struct TransferChecked<'a> { /// SPL Token or Token-2022 program. pub token_program: &'a AccountInfo, /// Source token account (writable). pub source: &'a AccountInfo, /// Mint (read-only). pub mint: &'a AccountInfo, /// Destination token account (writable). pub destination: &'a AccountInfo, /// Authority PDA, the SPL delegate; signs with the stored bump. pub authority: &'a AccountInfo, /// Mandate key, the second seed of the authority PDA.- Line 69
invoke_signed_with_bounds::<3>(&ix, &[mandate, owner, agent], &[]): Makes a CPI signed by a PDA. The signer seeds prove the program controls that address. - Line 72
/// Accounts of the `TransferChecked` CPI.: A checked token transfer, which also verifies the mint and decimals. - Line 82
/// Authority PDA, the SPL delegate; signs with the stored bump.: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
programs/relay/src/ix/swap.rs L313–336 @ 5a74154 · MIT · original AccountMeta::writable(a.output_vault.key()), AccountMeta::readonly(a.in_token_program.key()), AccountMeta::readonly(a.out_token_program.key()), AccountMeta::readonly(a.in_mint.key()), AccountMeta::readonly(a.out_mint.key()), AccountMeta::writable(a.observation_state.key()), ]; let ix = Instruction { program_id: a.amm_program.key(), accounts: &metas, data: &data, }; let bump = [authority_bump]; let seeds = [ Seed::from(AUTHORITY_SEED), Seed::from(a.mandate.key()), Seed::from(&bump), ]; let signer = Signer::from(&seeds); invoke_signed_with_bounds::<13>( &ix, &[ a.authority, a.amm_authority,- Line 325
let bump = [authority_bump];: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 326
let seeds = [: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 332
invoke_signed_with_bounds::<13>(: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address.
How to run, build and test
These commands come from the repo's own manifests (package.json scripts, Anchor.toml, Cargo, go.mod, Makefile). Check the README for prerequisites and environment variables.
| Directory | Command | Runs |
|---|---|---|
. | npm run build | pnpm -r --if-present run build |
. | npm run test | pnpm -r --if-present run test |
. | npm run lint | pnpm -r --if-present run lint |
apps/dashboard | npm run dev | next dev |
apps/dashboard | npm run start | next start |
apps/dashboard | npm run build | next build |
apps/dashboard | npm run test | vitest run |
apps/dashboard | npm run lint | eslint . && tsc --noEmit && prettier --check "src/**/*.{ts,tsx,css}" "tests/**/*.{ts,tsx}" "e2e/**/*.ts" "*.ts" "*.mjs" |
apps/demo | npm run test | vitest run |
apps/demo | npm run lint | tsc --noEmit && prettier --check "src/**/*.ts" "test/**/*.ts" "live-agent/**/*.ts" vitest.config.ts |
packages/cli | npm run build | tsup |
packages/cli | npm run test | vitest run |
packages/cli | npm run lint | tsc --noEmit && prettier --check "src/**/*.ts" "test/**/*.ts" tsup.config.ts vitest.config.ts |
packages/mcp | npm run build | tsup |
packages/mcp | npm run test | vitest run |
packages/mcp | npm run lint | tsc --noEmit && prettier --check "src/**/*.ts" "test/**/*.ts" "scripts/**/*.ts" tsup.config.ts vitest.config.ts |
What you could build from it
- Fork the SuperStack copy and extend it within the AI & Agents gaps listed on the idea page.
- Learn the pattern first: AI agents on Solana
- Learn the pattern first: Building and sending transactions from a client