Tutorial

Resolve DIDs & Verify Credentials

Every Vouch agent gets a W3C Decentralized Identifier (DID) and can attach Verifiable Credentials. This tutorial covers resolving DIDs, generating DID Documents, and working with credentials using both the SDK and the REST API.

The DID Format

Vouch uses the did:sol method. The DID is derived directly from the owner's Solana public key:

Format
did:sol:<owner_pubkey>

# Example:
did:sol:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU

Resolve DIDs with the SDK

Derive a DID

TypeScript
import { RegistryClient } from '@vouch-protocol/sdk'; // Vouch SDK for DID derivation
import { PublicKey } from '@solana/web3.js'; // Solana public key type

const owner = new PublicKey('7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU'); // Agent owner's Solana pubkey

// Derive the DID string
const did = RegistryClient.deriveAgentDid(owner); // Deterministic: always the same for a given pubkey
console.log(did);
// "did:sol:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"

Generate a DID Document

The DID Document follows the W3C DID Core specification and includes verification methods, authentication, and service endpoints. First, set up the Anchor provider and program (same pattern as the registration tutorial):

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

// Set up connection, wallet, and Anchor
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);
const provider = new AnchorProvider(connection, wallet, { commitment: 'confirmed' });
const program = new Program(IDL, provider);

// Initialize RegistryClient and generate the DID Document
const registry = new RegistryClient(program, provider);

const didDoc = await registry.generateDidDocument(owner); // Build a W3C DID Document from on-chain data
console.log(JSON.stringify(didDoc, null, 2)); // Pretty-print the DID Document

The returned DID Document structure:

DID Document
{
  "@context": [
    "https://www.w3.org/ns/did/v1",
    "https://w3id.org/security/suites/ed25519-2020/v1"
  ],
  "id": "did:sol:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "controller": "did:sol:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "verificationMethod": [{
    "id": "did:sol:7xKXtg...#key-1",
    "type": "Ed25519VerificationKey2020",
    "controller": "did:sol:7xKXtg...",
    "publicKeyBase58": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
  }],
  "authentication": ["did:sol:7xKXtg...#key-1"],
  "assertionMethod": ["did:sol:7xKXtg...#key-1"],
  "service": [{
    "id": "did:sol:7xKXtg...#agent-card",
    "type": "A2AAgentCard",
    "serviceEndpoint": "https://example.com/agent-card.json"
  }, {
    "id": "did:sol:7xKXtg...#vouch-registry",
    "type": "VouchRegistry",
    "serviceEndpoint": "solana:5i73FN7Ycgnh9dr2Yzf3oFiTCKhu85JPpTUbZ84VPtu4:<agentPda>"
  }]
}

Resolve DIDs with the API

Resolve Agent + DID Document

GET /did/did:sol:<pubkey>

Returns the agent data plus a full DID Document.

cURL
# Resolve a DID to get agent data + full DID Document
curl https://api.vouchprotocol.xyz/did/did:sol:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU
Response
{
  "did": "did:sol:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "agent": {
    "pda": "BKq8rN4EwJQG3R9FnLhSGqJ2tNkh8cVRxvNApj7hbfQM",
    "name": "CommerceBot",
    "tier": "standard",
    "reputation_score": 720
  },
  "didDocument": { "@context": ["..."], "id": "did:sol:...", "...": "..." }
}

Resolve DID Document Only

GET /did/did:sol:<pubkey>/document

Returns only the W3C DID Document. Content-Type: application/did+ld+json.

cURL
# Fetch only the W3C DID Document (Content-Type: application/did+ld+json)
curl https://api.vouchprotocol.xyz/did/did:sol:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU/document

Verifiable Credentials

Vouch supports four types of Verifiable Credentials that can be attached to agents:

Type Purpose
SecurityAudit Proves the agent passed a security audit
Identity Verifies the agent operator's identity
Capability Certifies specific skills or capabilities
Compliance Proves compliance with a regulatory framework

Submit a Credential

Submit a Verifiable Credential for an agent. Requires wallet authentication.

POST /agents/:pda/credentials/verify

Submit a VC for verification and storage. Requires wallet auth headers.

cURL
# Submit a Verifiable Credential for an agent (requires wallet auth)
curl -X POST https://api.vouchprotocol.xyz/agents/AgentPDA.../credentials/verify \
  -H "Content-Type: application/json" \
  -H "X-Wallet-Pubkey: 7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU" \
  -H "X-Wallet-Signature: <signed-message>" \
  -H "X-Wallet-Timestamp: 1710000000" \
  -d '{
    "credential": {
      "@context": ["https://www.w3.org/2018/credentials/v1"],
      "type": ["VerifiableCredential", "SecurityAuditCredential"],
      "issuer": "did:sol:AuditorPubkey...",
      "credentialSubject": {
        "id": "did:sol:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
        "auditResult": "pass",
        "auditDate": "2026-03-15"
      }
    }
  }'

List Agent Credentials

GET /agents/:pda/credentials

List all Verifiable Credentials attached to an agent. Public, no auth required.

cURL
# List all Verifiable Credentials for an agent (public, no auth needed)
curl https://api.vouchprotocol.xyz/agents/AgentPDA.../credentials
Response
{
  "pda": "BKq8rN4EwJQG3R9FnLhSGqJ2tNkh8cVRxvNApj7hbfQM",
  "total": 1,
  "credentials": [
    {
      "type": "SecurityAuditCredential",
      "issuer": "did:sol:AuditorPubkey123...",
      "credential_url": "https://auditor.example/vc/123.json",
      "structurally_valid": true,
      "valid_from": "2026-01-01T00:00:00Z",
      "valid_until": "2027-01-01T00:00:00Z"
    }
  ]
}

SDK Credential Helpers

The SDK provides utility functions for working with credentials:

TypeScript
import {
  isCredentialActive,       // Checks expiry + revocation status
  isCredentialForAgent,     // Verifies credential subject matches agent DID
  buildCredentialsExtension, // Formats VCs as a DID Document extension
} from '@vouch-protocol/sdk';

// Check if a credential is still valid (not expired/revoked)
const active = isCredentialActive(credential); // Returns false if expired or revoked
console.log('Active:', active); // true

// Check if a credential belongs to a specific agent (second param is the owner PublicKey)
const agentOwner = new PublicKey('7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU');
const belongs = isCredentialForAgent(credential, agentOwner); // Matches credentialSubject.id to the owner's PublicKey
console.log('Belongs to agent:', belongs); // true

// Build a credentials extension for the DID Document
const extension = buildCredentialsExtension(credentials); // Attach VCs to a DID Document
console.log(extension);
// { credentials: [...] }

Complete SDK Example

resolve-did.ts
import { Connection, Keypair } from '@solana/web3.js';
import { AnchorProvider, Program, Wallet } from '@coral-xyz/anchor';
import {
  RegistryClient,
  IDL,
  isCredentialActive,
  isCredentialForAgent,
} from '@vouch-protocol/sdk';
import { readFileSync } from 'fs';
import { homedir } from 'os';
import { join } from 'path';

async function main() {
  // 0. Set up connection, wallet, and Anchor
  const connection = new Connection('https://api.mainnet-beta.solana.com');
  const keypair = Keypair.fromSecretKey(new Uint8Array(JSON.parse(
    readFileSync(join(homedir(), '.config/solana/id.json'), 'utf-8')
  )));
  const wallet = new Wallet(keypair);
  const provider = new AnchorProvider(connection, wallet, { commitment: 'confirmed' });
  const program = new Program(IDL, provider);

  // 1. Derive the DID
  const did = RegistryClient.deriveAgentDid(wallet.publicKey); // Pure function, no RPC call needed
  console.log('DID:', did);

  // 2. Generate the full DID Document
  const registry = new RegistryClient(program, provider);
  const didDoc = await registry.generateDidDocument(wallet.publicKey); // Fetches agent PDA and builds the W3C document
  console.log('DID Document:', JSON.stringify(didDoc, null, 2));

  // 3. Fetch credentials via API
  const [agentPda] = RegistryClient.findAgentPda(wallet.publicKey); // Returns [PublicKey, bump] tuple
  const res = await fetch( // GET credentials from the REST API
    `https://api.vouchprotocol.xyz/agents/${agentPda.toBase58()}/credentials`
  );
  const { credentials } = await res.json();

  // 4. Map API response (snake_case) to AgentCredentialRef (camelCase)
  //    isCredentialActive expects { credentialUrl, validFrom, validUntil, ... }
  const refs = credentials.map((c: any) => ({
    type: c.type,
    issuer: c.issuer,
    credentialUrl: c.credential_url,
    validFrom: c.valid_from,
    validUntil: c.valid_until,
    structurallyValid: c.structurally_valid,
  }));

  // 5. Filter active credentials for this agent
  const valid = refs.filter( // Keep only non-expired VCs that match this agent
    (c: any) => isCredentialActive(c) && isCredentialForAgent(c, wallet.publicKey)
  );
  console.log('Valid credentials:', valid.length);
}

main().catch(console.error);

API response mapping. The REST API returns credentials with snake_case fields (credential_url, valid_from, valid_until, structurally_valid), but the SDK's isCredentialActive function expects an AgentCredentialRef object with camelCase fields (credentialUrl, validFrom, validUntil, structurallyValid). Make sure to map the fields before passing API responses to SDK helpers.

Next Steps