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
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.
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).
// 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.
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)
// 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:
// 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:
// 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:
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
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
- Verify agents via CPI — let your Solana program check tiers on-chain
- Set up webhooks to get notified when tiers change
- Vault instructions reference for the full on-chain API