Full lesson
AI_Firewall
AI Agent Firewall
What it does
From the project README:
Tools exposed: `validate_transaction`, `explain_decision` (natural-language risk narration, zh/en), `get_policy`.
- Original
- keliq-wq/AI_Firewall
- SuperStack fork
- Blockchains/AI_Firewall @
e88c982 - Stars
- 0
- Last push (original)
- 2026-10-11
- Languages
- TypeScript, Rust, CSS, JavaScript, Batchfile
- Detected stack
- AnchorNext.jsSolana Agent Kitweb3.js
- Categories
- AI & Agents
- Licence
- MIT
Architecture
84 source files. By extension: .ts ×59, .tsx ×13, .rs ×11, .js ×1.
| Top-level directory | Source files |
|---|---|
tests | 22 |
src | 21 |
landing | 17 |
programs | 12 |
scripts | 8 |
demo | 2 |
(root) | 1 |
examples | 1 |
On-chain programs: programs/firewall
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/ai-firewall/. 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/firewall/src/instructions/initialize.rs L1–24 @ e88c982 · MIT · originaluse anchor_lang::prelude::*; use crate::constants;use crate::state::{PolicyConfig, VaultState}; /// 初始化策略:登记 Agent 钱包、限额与协议白名单,同时创建金库与记账账户#[derive(Accounts)]pub struct Initialize<'info> { #[account(mut)] pub authority: Signer<'info>, #[account( init, payer = authority, space = 8 + PolicyConfig::INIT_SPACE, seeds = [constants::POLICY_SEED, authority.key().as_ref()], bump, )] pub policy: Account<'info, PolicyConfig>, #[account( init, payer = authority, space = 8 + VaultState::INIT_SPACE, seeds = [constants::VAULT_STATE_SEED, authority.key().as_ref()], bump,- Line 10
pub authority: Signer<'info>,: Requires this account to have signed the transaction. - Line 12
init,: Creates and pays for a new account. Space and payer must be specified. - Line 15
seeds = [constants::POLICY_SEED, authority.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 16
bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
programs/firewall/src/instructions/execute.rs L1–24 @ e88c982 · MIT · originaluse anchor_lang::prelude::*;use anchor_lang::solana_program::instruction::{AccountMeta, Instruction};use anchor_lang::solana_program::program::invoke_signed; use crate::constants;use crate::errors::FirewallError;use crate::invariants::{is_token_account, token_close_authority_tag, token_delegate_tag};use crate::state::{PolicyConfig, VaultState}; /// 协议调用:金库向白名单协议付款(如 swap / 质押)。/// 与 withdraw 相同的链上强制检查 + 目标程序白名单,data 为目标协议指令字节。#[derive(Accounts)]pub struct Execute<'info> { #[account(mut)] pub agent: Signer<'info>, #[account(seeds = [constants::POLICY_SEED, policy.authority.key().as_ref()], bump = policy.bump)] pub policy: Account<'info, PolicyConfig>, #[account( mut, seeds = [constants::VAULT_STATE_SEED, policy.authority.key().as_ref()], bump = vault_state.bump, )] pub vault_state: Account<'info, VaultState>, /// CHECK: PDA 金库(作为签名者参与目标协议调用)- Line 3
use anchor_lang::solana_program::program::invoke_signed;: Makes a CPI signed by a PDA. The signer seeds prove the program controls that address. - Line 15
pub agent: Signer<'info>,: Requires this account to have signed the transaction. - Line 16
#[account(seeds = [constants::POLICY_SEED, policy.authority.key().as_ref()], bump = policy: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 16
#[account(seeds = [constants::POLICY_SEED, policy.authority.key().as_ref()], bump = policy: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
programs/firewall/src/instructions/deposit.rs L1–24 @ e88c982 · MIT · originaluse anchor_lang::prelude::*;use anchor_lang::system_program::{transfer, Transfer}; use crate::constants;use crate::errors::FirewallError;use crate::state::PolicyConfig; /// 入金:仅登记的 Agent 钱包可存入金库#[derive(Accounts)]pub struct Deposit<'info> { #[account(mut)] pub depositor: Signer<'info>, #[account(seeds = [constants::POLICY_SEED, policy.authority.key().as_ref()], bump = policy.bump)] pub policy: Account<'info, PolicyConfig>, /// CHECK: PDA 金库(无数据,仅持有 lamports) #[account(mut, seeds = [constants::VAULT_SEED, policy.authority.key().as_ref()], bump)] pub vault: UncheckedAccount<'info>, pub system_program: Program<'info, System>,} pub fn handler(ctx: Context<Deposit>, amount: u64) -> Result<()> { require_keys_eq!( ctx.accounts.depositor.key(), ctx.accounts.policy.agent,- Line 12
pub depositor: Signer<'info>,: Requires this account to have signed the transaction. - Line 13
#[account(seeds = [constants::POLICY_SEED, policy.authority.key().as_ref()], bump = policy: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 13
#[account(seeds = [constants::POLICY_SEED, policy.authority.key().as_ref()], bump = policy: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 22
require_keys_eq!(: An explicit validation that aborts the instruction with an error.
programs/firewall/src/state.rs L25–48 @ e88c982 · MIT · original#[account]#[derive(InitSpace)]pub struct VaultState { /// 当前 24h 窗口起点(unix 秒) pub window_start: i64, /// 当前窗口内累计支出 pub spent_in_window: u64, pub bump: u8,} impl VaultState { /// 滚动窗口推进:跨天自动重置,随后做累计上限校验。 /// 这是链上强制层——即使客户端策略(第 1/2 层)被绕过,超支依然会被拒绝。 pub fn advance_window(&mut self, now: i64, daily_limit: u64, amount: u64) -> Result<()> { if now - self.window_start >= constants::DAY_SECONDS { self.window_start = now; self.spent_in_window = 0; } let projected = self .spent_in_window .checked_add(amount) .ok_or(FirewallError::ArithmeticOverflow)?; require!(projected <= daily_limit, FirewallError::DailyLimitExceeded); self.spent_in_window = projected;- Line 32
pub bump: u8,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 45
.checked_add(amount): Uses checked arithmetic so overflow fails instead of wrapping. - Line 47
require!(projected <= daily_limit, FirewallError::DailyLimitExceeded);: An explicit validation that aborts the instruction with an error.
How to run, build and test
These commands come from the repo's own manifests (package.json scripts, Anchor.toml, Cargo, go.mod, Makefile). Check the README for prerequisites and environment variables.
| Directory | Command | Runs |
|---|---|---|
. | npm run build | tsc |
. | npm run test | vitest run |
landing | npm run dev | next dev -p 3100 |
landing | npm run start | next start |
landing | npm run build | next build |
landing | npm run lint | next lint |
. | anchor build | compile programs and generate IDLs |
. | anchor test | local validator + test suite |
. | anchor run test | yarn run ts-mocha -t 1000000 programs/firewall/tests/**/*.ts |
. | cargo build | Rust workspace build |
. | cargo test | Rust tests |
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