Sol.College

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 directorySource files
tests22
src21
landing17
programs12
scripts8
demo2
(root)1
examples1

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.

keliq-wq/AI_Firewall · programs/firewall/src/instructions/initialize.rs L1–24 @ e88c982 · MIT · original
use 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,
  1. Line 10 pub authority: Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 12 init,: Creates and pays for a new account. Space and payer must be specified.
  3. 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.
  4. Line 16 bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
keliq-wq/AI_Firewall · programs/firewall/src/instructions/execute.rs L1–24 @ e88c982 · MIT · original
use 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 金库(作为签名者参与目标协议调用)
  1. 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.
  2. Line 15 pub agent: Signer<'info>,: Requires this account to have signed the transaction.
  3. 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.
  4. 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.
keliq-wq/AI_Firewall · programs/firewall/src/instructions/deposit.rs L1–24 @ e88c982 · MIT · original
use 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,
  1. Line 12 pub depositor: Signer<'info>,: Requires this account to have signed the transaction.
  2. 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.
  3. 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.
  4. Line 22 require_keys_eq!(: An explicit validation that aborts the instruction with an error.
keliq-wq/AI_Firewall · 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;
  1. Line 32 pub bump: u8,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  2. Line 45 .checked_add(amount): Uses checked arithmetic so overflow fails instead of wrapping.
  3. 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.

DirectoryCommandRuns
.npm run buildtsc
.npm run testvitest run
landingnpm run devnext dev -p 3100
landingnpm run startnext start
landingnpm run buildnext build
landingnpm run lintnext lint
.anchor buildcompile programs and generate IDLs
.anchor testlocal validator + test suite
.anchor run testyarn run ts-mocha -t 1000000 programs/firewall/tests/**/*.ts
.cargo buildRust workspace build
.cargo testRust tests

What you could build from it

Used in lessons