Tutorial

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

Cargo.toml
# Add the Vouch CPI crate and Anchor to your dependencies
[dependencies]
vouch-cpi = "0.1"
anchor-lang = "0.31"

Step 2 — Import Types

Rust
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].

Rust
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.

Rust
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.

Rust
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.

Rust
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

lib.rs
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