Full lesson
X402Guard
Solana-native spending guardrails for AI agents. Built for Colosseum Frontier 2026.
What it does
From the project README:
[](LICENSE) [](https://colosseum.com/frontier) [](https://x402guard.acedata.cloud/.well-known/x402guard)
- Original
- AceDataCloud/X402Guard
- SuperStack fork
- Blockchains/X402Guard @
159b9c0 - Stars
- 0
- Last push (original)
- 2026-06-06
- Languages
- Python, TypeScript, Vue, Rust, Shell, Dockerfile
- Detected stack
- Anchorsolana-pyweb3.js
- Categories
- AI & AgentsPayments
- Licence
- MIT
- Homepage
- https://x402guard.acedata.cloud
Architecture
51 source files. By extension: .py ×28, .ts ×15, .rs ×7, .js ×1.
| Top-level directory | Source files |
|---|---|
api | 27 |
web | 13 |
programs | 7 |
tests | 3 |
scripts | 1 |
On-chain programs: programs/agent_vault
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/x402guard/. 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/agent_vault/src/instructions/spend.rs L49–72 @ 159b9c0 · MIT · original #[account( mut, seeds = [SEED_POLICY, vault.key().as_ref()], bump = policy.bump, has_one = vault, )] pub policy: Box<Account<'info, Policy>>, #[account(address = vault.usdc_mint)] pub usdc_mint: Box<Account<'info, Mint>>, #[account( mut, associated_token::mint = usdc_mint, associated_token::authority = vault, )] pub vault_usdc_ata: Box<Account<'info, TokenAccount>>, /// Recipient's USDC ATA. Must already exist (the upstream x402 facilitator /// always has one — for now we don't auto-create here to keep the spend /// instruction lean. Backend tx builder pre-flights this and adds an /// `init_if_needed` ix in the same tx if necessary.) #[account( mut,- Line 51
seeds = [SEED_POLICY, vault.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 52
bump = policy.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 53
has_one = vault,: Checks that a stored pubkey field matches the passed account. - Line 67
/// Recipient's USDC ATA. Must already exist (the upstream x402 facilitator: Uses x402, an HTTP 402 pattern where an agent pays per request with an on-chain payment. - Line 70
/// `init_if_needed` ix in the same tx if necessary.): Creates the account only if it doesn't exist. Watch for re-initialisation bugs.
programs/agent_vault/src/instructions/owner_ops.rs L25–48 @ 159b9c0 · MIT · original has_one = owner, )] pub vault: Box<Account<'info, Vault>>, #[account( mut, seeds = [SEED_POLICY, vault.key().as_ref()], bump = policy.bump, has_one = vault, )] pub policy: Box<Account<'info, Policy>>,} // ─── pause / resume ─────────────────────────────────────────────────────── pub fn pause(ctx: Context<OwnerOnly>) -> Result<()> { let policy = &mut ctx.accounts.policy; policy.paused = true; emit!(VaultPaused { vault: ctx.accounts.vault.key(), ts: Clock::get()?.unix_timestamp, }); Ok(())}- Line 25
has_one = owner,: Checks that a stored pubkey field matches the passed account. - Line 31
seeds = [SEED_POLICY, vault.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 32
bump = policy.bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 43
emit!(VaultPaused {: Emits an Anchor event that clients can parse from the logs. - Line 45
ts: Clock::get()?.unix_timestamp,: Reads on-chain time from the Clock sysvar.
programs/agent_vault/src/instructions/create_vault.rs L25–47 @ 159b9c0 · MIT · original pub per_call_cap: u64, /// `sha256(host)` of each permitted endpoint. Up to `MAX_ALLOWLIST` (8) entries. pub endpoint_allowlist: Vec<[u8; 32]>, /// Unix timestamp (seconds). Must be strictly in the future at submit time. pub expires_at: i64,} #[derive(Accounts)]#[instruction(args: CreateVaultArgs)]pub struct CreateVault<'info> { #[account(mut)] pub owner: Signer<'info>, #[account( init, payer = owner, space = Vault::SIZE, seeds = [SEED_VAULT, owner.key().as_ref(), &args.agent_id], bump, )] pub vault: Box<Account<'info, Vault>>,- Line 38
pub owner: Signer<'info>,: Requires this account to have signed the transaction. - Line 41
init,: Creates and pays for a new account. Space and payer must be specified. - Line 44
seeds = [SEED_VAULT, owner.key().as_ref(), &args.agent_id],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 45
bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
programs/agent_vault/src/state.rs L25–48 @ 159b9c0 · MIT · original /// User who owns this vault. Only this signer can pause / resume / clawback / update policy. pub owner: Pubkey, /// Application-defined agent identifier, supplied by the owner at creation. /// Lets one user own multiple distinct vaults (e.g. one per agent persona). pub agent_id: [u8; 32], /// USDC mint this vault holds. Locked at creation so the policy can't be evaded /// by swapping in a different SPL mint later. pub usdc_mint: Pubkey, pub bump: u8, pub created_at: i64,} impl Vault { /// 8-byte discriminator + struct fields. pub const SIZE: usize = 8 + 32 + 32 + 32 + 1 + 8;} #[account]#[derive(Debug)]pub struct Policy { pub vault: Pubkey, /// Ed25519 public key allowed to authorise `spend` ixs. Held by the x402guard /// backend session for the hackathon; v2 will move this to user-side WebAuthn. pub delegation_key: Pubkey,- Line 33
pub bump: u8,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 46
/// Ed25519 public key allowed to authorise `spend` ixs. Held by the x402guard: Uses x402, an HTTP 402 pattern where an agent pays per request with an on-chain payment.
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 test | ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts |
web | npm run dev | vite |
web | npm run build | vue-tsc --noEmit --skipLibCheck && vite build |
web | npm run lint | eslint --ext .ts,.vue src |
. | anchor build | compile programs and generate IDLs |
. | anchor test | local validator + test suite |
. | anchor run test | yarn run ts-mocha -p ./tsconfig.json -t 1000000 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
- Learn the pattern first: Blinks and Solana Actions