Verify Agents via CPI (Rust)
This tutorial shows Solana/Anchor developers how to verify Vouch agent identities directly on-chain using Cross-Program Invocation (CPI). No off-chain infrastructure needed — just PDA deserialization.
Step 1 — Add the CPI Crate
# Add the Vouch CPI crate and Anchor to your dependencies [dependencies] vouch-cpi = "0.1" anchor-lang = "0.31"
Step 2 — Import Types
use anchor_lang::prelude::*; use vouch_cpi::{ AgentIdentity, // On-chain agent account struct AgentTier, // Enum: Observer, Basic, Standard, Premium require_vouch_active, // Macro: assert agent is active (reverts if not) require_vouch_tier, // Macro: enforce a minimum trust tier require_vouch_capability, // Macro: check agent has specific capabilities require_vouch_reputation, // Macro: check agent meets minimum reputation require_vouch_tier_stable, // Macro: check tier is stable (flash-loan protection) require_vouch_owner, // Macro: validate PDA ownership };
Step 3 — Add Agent PDA to Your Context
Pass the Vouch agent PDA as an account in your instruction. The PDA is derived from ["agent", owner_pubkey].
use anchor_lang::prelude::*; #[derive(Accounts)] pub struct ProcessPayment<'info> { #[account(mut)] pub payer: Signer<'info>, // The transaction signer paying fees /// The Vouch agent identity PDA /// CHECK: Validated by require_vouch_owner! + vouch-cpi deserialization pub agent_identity: AccountInfo<'info>, // PDA derived from ["agent", owner_pubkey] /// Clock sysvar (needed by require_vouch_tier_stable!) pub clock: Sysvar<'info, Clock>, }
Step 4 — Verify Agent Is Active
The require_vouch_active! macro deserializes the agent PDA and checks that the agent status is active. Reverts if not.
use anchor_lang::prelude::*; pub fn process_payment(ctx: Context<ProcessPayment>) -> Result<()> { // First verify the account is owned by the Vouch program require_vouch_owner!(ctx.accounts.agent_identity); // Then safely deserialize the agent PDA into an AgentIdentity struct let agent = AgentIdentity::try_deserialize( &mut &ctx.accounts.agent_identity.data.borrow()[..] )?; // Reverts with VouchNotActive if agent is deactivated require_vouch_active!(&agent); // Your payment logic here... Ok(()) }
Step 5 — Check Minimum Tier
Use require_vouch_tier! to enforce a minimum stake tier. This is useful for gating high-value operations.
use anchor_lang::prelude::*; pub fn high_value_transfer(ctx: Context<ProcessPayment>) -> Result<()> { // First verify the account is owned by the Vouch program require_vouch_owner!(ctx.accounts.agent_identity); // Deserialize the agent PDA let agent = AgentIdentity::try_deserialize( &mut &ctx.accounts.agent_identity.data.borrow()[..] )?; // Requires at least Standard tier ($2 USDC staked — threshold read from on-chain config) require_vouch_tier!( &agent, AgentTier::Standard ); // Proceed with high-value transfer... Ok(()) }
Step 6 — Additional Verification Macros
The vouch-cpi crate provides several additional macros for granular on-chain checks. Each macro takes a deserialized AgentIdentity struct and reverts if the condition is not met.
use anchor_lang::prelude::*; pub fn conditional_access(ctx: Context<ProcessPayment>) -> Result<()> { // Validate PDA ownership (must come BEFORE deserialization) require_vouch_owner!(ctx.accounts.agent_identity); // Then safely deserialize the agent PDA let agent = AgentIdentity::try_deserialize( &mut &ctx.accounts.agent_identity.data.borrow()[..] )?; // Check agent has specific capabilities (bitmask) require_vouch_capability!(&agent, 0b0000_0001); // Check agent meets minimum reputation require_vouch_reputation!(&agent, 600); // Check tier is stable (flash-loan protection) — needs Clock and min_age_seconds let clock = Clock::get()?; require_vouch_tier_stable!(&agent, &clock, 300); // tier must be stable for 5 minutes Ok(()) }
Complete Anchor Example
use anchor_lang::prelude::*; // Anchor framework prelude use vouch_cpi::{ AgentIdentity, AgentTier, require_vouch_active, require_vouch_tier, require_vouch_reputation, require_vouch_owner, }; declare_id!("YourProgram1111111111111111111111111111111"); // Replace with your program's address #[program] pub mod my_marketplace { use super::*; pub fn buy_service( ctx: Context<BuyService>, amount: u64, ) -> Result<()> { // First verify both accounts are owned by the Vouch program require_vouch_owner!(ctx.accounts.seller_agent); require_vouch_owner!(ctx.accounts.buyer_agent); // Deserialize seller agent PDA let seller = AgentIdentity::try_deserialize( &mut &ctx.accounts.seller_agent.data.borrow()[..] )?; // Verify the seller agent is active and at least Basic tier require_vouch_active!(&seller); // Reverts if seller is deactivated require_vouch_tier!( &seller, AgentTier::Basic ); // Reverts if seller stake is below Basic tier threshold // Deserialize buyer agent PDA let buyer = AgentIdentity::try_deserialize( &mut &ctx.accounts.buyer_agent.data.borrow()[..] )?; // Check buyer meets minimum reputation threshold require_vouch_reputation!(&buyer, 400); msg!( // Log to Solana transaction logs for debugging "Processing {} lamports, buyer rep: {}", amount, buyer.reputation_score, ); // Transfer logic here... Ok(()) } } #[derive(Accounts)] pub struct BuyService<'info> { #[account(mut)] pub buyer: Signer<'info>, // The buyer signing and paying for the transaction /// CHECK: Validated by require_vouch_owner! + vouch-cpi deserialization pub buyer_agent: AccountInfo<'info>, // Buyer's Vouch agent PDA /// CHECK: Validated by require_vouch_owner! + vouch-cpi deserialization pub seller_agent: AccountInfo<'info>, // Seller's Vouch agent PDA pub system_program: Program<'info, System>, // Required for any SOL transfers }
Zero infrastructure cost. CPI verification reads directly from the on-chain PDA. No API calls, no servers, no latency — your program deserializes the Vouch account in the same transaction.
Next Steps
- Registry instructions reference for full CPI documentation
- Reputation instructions for behavioral reporting details
- Account structures for the complete AgentIdentity layout