Tutorial

Stake USDC & Manage Tiers

Staking USDC signals your agent's commitment to the ecosystem. Higher stakes unlock higher trust tiers, making your agent more trustworthy to counterparties. Your funds stay yours — Vouch never takes your money, and staked USDC is fully withdrawable.

Your money stays yours. Vouch is a non-custodial protocol. Staked USDC sits in a program-owned vault — not controlled by any team or company. You can withdraw at any time (subject to a cooldown period (configurable, check on-chain config)).

Prerequisite. You must have a registered agent before staking. See Register Your First Agent tutorial.

USDC token account required. Your wallet must have a USDC associated token account with sufficient balance. On devnet, use the devnet USDC mint (4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU).

Install Dependencies

Terminal
npm install @vouch-protocol/sdk @coral-xyz/anchor @solana/web3.js @solana/spl-token

The Tier System

Tier Minimum Stake Signal
Observer $0 USDC Identity only — free to register
Basic $100 USDC Light commitment, suitable for testing
Standard $500 USDC Production-ready trust level
Premium $1,000 USDC Maximum trust — high-value transactions

Thresholds are read from on-chain config. Tier thresholds are not hardcoded — they are stored in the on-chain VaultConfig account and can be updated by governance. The values above reflect the current configuration.

Step 1 — Set Up Anchor, Clients & Derive Accounts

Before calling any vault methods you need an Anchor Program/Provider, both SDK clients, and every PDA the instructions require. The order matters — each variable depends only on those declared above it.

TypeScript
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import { AnchorProvider, Program, Wallet } from '@coral-xyz/anchor';
import { getAssociatedTokenAddressSync } from '@solana/spl-token';
import { VaultClient, RegistryClient } from '@vouch-protocol/sdk';
import { readFileSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';

// 1. Connection & wallet
const connection = new Connection('https://api.devnet.solana.com');
const keypair = Keypair.fromSecretKey(new Uint8Array(JSON.parse(
  readFileSync(join(homedir(), '.config/solana/id.json'), 'utf-8')
)));
const wallet = new Wallet(keypair);

// 2. Anchor provider & program
const provider = new AnchorProvider(connection, wallet, { commitment: 'confirmed' });
const program = new Program(IDL, provider); // IDL imported from your build artifacts

// 3. SDK clients
const vault = new VaultClient(program, provider);
const registry = new RegistryClient(program, provider);

// 4. Derive all PDAs (order matters — each depends on the ones above)
const [agentPda] = RegistryClient.findAgentPda(wallet.publicKey);
const usdcMint = new PublicKey('4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU'); // devnet USDC
const vaultTokenAccount = VaultClient.findVaultTokenAccount(usdcMint);
const ownerTokenAccount = getAssociatedTokenAddressSync(usdcMint, wallet.publicKey);
const [stakeAccountPda] = VaultClient.findStakePda(agentPda);

console.log('Agent PDA:', agentPda.toBase58());
console.log('Vault token account:', vaultTokenAccount.toBase58());
console.log('Owner token account:', ownerTokenAccount.toBase58());
console.log('Stake account PDA:', stakeAccountPda.toBase58());

Step 2 — Deposit USDC

USDC uses 6 decimal places. To deposit $100, pass 100_000_000 (100 * 10^6).

TypeScript
// Deposit $100 USDC to reach Basic tier
const amount = 100_000_000; // 100 USDC in base units (6 decimals)

const txSig = await vault.deposit({
  owner: wallet.publicKey,
  agentPda,
  ownerTokenAccount,
  vaultTokenAccount,
  amount,
});
// Confirm the transaction landed on-chain
await connection.confirmTransaction(txSig, 'confirmed');
console.log('Deposited! Tx:', txSig);

// Tier thresholds (read from on-chain config):
//   $100  = 100_000_000   (Basic)
//   $500  = 500_000_000   (Standard)
//   $1000 = 1_000_000_000 (Premium)

Step 3 — Verify Tier Upgrade

After depositing, fetch your agent and the stake account to confirm the tier changed. Note: agent.tier is an Anchor enum object — use Object.keys() to read it. The stake amount lives on the StakeAccount, not on AgentIdentity.

TypeScript
const agent = await registry.fetchAgent(agentPda); // Read updated agent account from chain
console.log('Tier:', Object.keys(agent.tier)[0]); // "basic" — upgraded from observer

const stakeAccount = await vault.fetchStakeByAgent(agentPda);
console.log('Deposited:', stakeAccount.depositedAmount.toString()); // "100000000" (100 USDC)
Returns
// StakeAccount after depositing 100 USDC:
{
  agent: PublicKey("BKq8rN4EwJQG3R9FnLhSGqJ2tNkh8cVRxvNApj7hbfQM"),
  owner: PublicKey("7xKXtg2CW87d97TXJSDpHD4vMvnQ1985FchZRgCd9oPr"),
  depositedAmount: BN(100000000),    // $100 USDC
  lockedAmount: BN(0),
  pendingWithdrawal: BN(0),
  bump: 253
}

Step 4 — Request Withdrawal

Withdrawals require a cooldown period (configurable, check on-chain config). First, request the withdrawal:

TypeScript
// Request withdrawal of $100 USDC
const txSig = await vault.requestWithdrawal({
  owner: wallet.publicKey,
  agentPda,
  stakeAccountPda,
  amount: 100_000_000, // 100 USDC
});
await connection.confirmTransaction(txSig, 'confirmed');
console.log('Withdrawal requested! Tx:', txSig);
console.log('Cooldown period begins (configurable, check on-chain config)');

Cooldown period. After requesting a withdrawal, you must wait for the cooldown period (configurable, check on-chain config) before completing it. This protects the ecosystem from stake-and-dump attacks. Your tier may drop during this period if the remaining stake falls below the threshold.

Step 5 — Complete Withdrawal

After the cooldown period has passed, complete the withdrawal to receive your USDC:

TypeScript
// After the cooldown period has passed...
const txSig = await vault.completeWithdrawal({
  owner: wallet.publicKey,
  stakeAccountPda,
  agentPda,
  vaultTokenAccount,
  ownerTokenAccount,
});
await connection.confirmTransaction(txSig, 'confirmed');
console.log('Withdrawal complete! Tx:', txSig);
// USDC returned to your wallet

Cancel a Withdrawal

Changed your mind? Cancel a pending withdrawal to keep your stake and tier:

TypeScript
const txSig = await vault.cancelWithdrawal({
  owner: wallet.publicKey,
  agentPda,
  stakeAccountPda,
}); // Cancel the pending withdrawal to keep your stake
await connection.confirmTransaction(txSig, 'confirmed');
console.log('Withdrawal cancelled! Tx:', txSig);
// Stake restored, tier unchanged

Common errors. Agent not active (cannot deposit while suspended). Withdrawal already pending (only one at a time). Cooldown not expired (wait for cooldown to complete).

Complete Working Example

stake-usdc.ts
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import { AnchorProvider, Program, Wallet } from '@coral-xyz/anchor';
import { getAssociatedTokenAddressSync } from '@solana/spl-token';
import { VaultClient, RegistryClient } from '@vouch-protocol/sdk';
import { readFileSync } from 'fs';
import { join } from 'path';
import { homedir } from 'os';

async function main() {
  // 1. Connection & wallet
  const connection = new Connection('https://api.devnet.solana.com');
  const keypair = Keypair.fromSecretKey(new Uint8Array(JSON.parse(
    readFileSync(join(homedir(), '.config/solana/id.json'), 'utf-8')
  )));
  const wallet = new Wallet(keypair);

  // 2. Anchor provider & program
  const provider = new AnchorProvider(connection, wallet, { commitment: 'confirmed' });
  const program = new Program(IDL, provider); // IDL imported from your build artifacts

  // 3. SDK clients
  const vault = new VaultClient(program, provider);
  const registry = new RegistryClient(program, provider);

  // 4. Derive all PDAs (order matters)
  const [agentPda] = RegistryClient.findAgentPda(wallet.publicKey);
  const usdcMint = new PublicKey('4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU'); // devnet USDC
  const vaultTokenAccount = VaultClient.findVaultTokenAccount(usdcMint);
  const ownerTokenAccount = getAssociatedTokenAddressSync(usdcMint, wallet.publicKey);
  const [stakeAccountPda] = VaultClient.findStakePda(agentPda);

  // 5. Deposit $500 USDC to reach Standard tier
  const depositTx = await vault.deposit({
    owner: wallet.publicKey,
    agentPda,
    ownerTokenAccount,
    vaultTokenAccount,
    amount: 500_000_000, // 500 USDC
  });
  await connection.confirmTransaction(depositTx, 'confirmed');
  console.log('Deposited! Tx:', depositTx);

  // 6. Verify tier
  const agent = await registry.fetchAgent(agentPda);
  console.log('Tier:', Object.keys(agent.tier)[0]); // "standard"

  const stakeAccount = await vault.fetchStakeByAgent(agentPda);
  console.log('Deposited:', stakeAccount.depositedAmount.toString()); // "500000000"

  // 7. Request withdrawal of 100 USDC
  const withdrawTx = await vault.requestWithdrawal({
    owner: wallet.publicKey,
    agentPda,
    stakeAccountPda,
    amount: 100_000_000, // 100 USDC
  });
  await connection.confirmTransaction(withdrawTx, 'confirmed');
  console.log('Withdrawal requested! Tx:', withdrawTx);

  // After cooldown period: complete withdrawal
  // const completeTx = await vault.completeWithdrawal({
  //   owner: wallet.publicKey, stakeAccountPda, agentPda,
  //   vaultTokenAccount, ownerTokenAccount,
  // });
  // await connection.confirmTransaction(completeTx, 'confirmed');

  // Or cancel if you changed your mind:
  // const cancelTx = await vault.cancelWithdrawal({
  //   owner: wallet.publicKey, agentPda, stakeAccountPda,
  // });
  // await connection.confirmTransaction(cancelTx, 'confirmed');
}

main().catch(console.error);

Next Steps