Skip to main content

Verify

.verify() fetches the current Merkle root from the Solana program and compares it against the local state of your collection. A matching root proves your data has not been tampered with.

Basic usage

const proof = await collection.verify();

console.log("Verified:", proof.verified);
console.log("On-chain root:", proof.onChainRoot);
console.log("Local root:", proof.localRoot);
console.log("Vectors in collection:", proof.vectorCount);
console.log("Explorer:", proof.solanaExplorerUrl);

Return value

interface VerifyResult {
  verified: boolean; // true if on-chain root matches local root
  onChainRoot: string; // Merkle root stored on Solana (hex)
  localRoot: string; // Merkle root computed from local index (hex)
  vectorCount: number; // number of vectors in the collection
  blockTime: number; // Unix timestamp of the on-chain write
  solanaExplorerUrl: string; // link to view the transaction on Solana Explorer
  programId: string; // VecLabs Anchor program address
}

What does verification prove?

A successful verification proves:
  1. Completeness - the collection contains exactly the vectors that were there at the last write
  2. Integrity - no vector has been added, modified, or deleted without a corresponding on-chain Merkle root update
  3. Timestamp - the on-chain record includes the block time, proving when the write occurred
  4. Non-repudiation - the Merkle root was signed by your wallet - only you could have written it

Performance

.verify() makes a Solana RPC call. Expect ~400ms latency due to network round-trip. This is completely separate from the query path - your queries remain at sub-5ms regardless. Do not call .verify() in the hot path of your application. Call it:
  • After important write operations when you need an audit record
  • Periodically in a background job to maintain an audit log
  • Before reading critical data in high-stakes applications
// ✅ Good - called after write, not in query loop
await collection.upsert([...]);
const proof = await collection.verify();
await saveAuditLog(proof);

// ❌ Bad - verify before every query adds 400ms to every request
async function search(query: string) {
  await collection.verify();  // don't do this
  return await collection.query({ vector: await embed(query), topK: 10 });
}

Handling verification failure

A verification failure means the on-chain root doesn’t match the local index. This should not happen under normal operation.
const proof = await collection.verify();

if (!proof.verified) {
  console.error("VERIFICATION FAILED");
  console.error(`On-chain root: ${proof.onChainRoot}`);
  console.error(`Local root:    ${proof.localRoot}`);

  // Possible causes:
  // 1. Local index is out of sync (missed a write)
  // 2. Background async Solana write hasn't completed yet
  // 3. Collection was modified outside the SDK

  // Wait a moment and retry - the async Solana write may not have settled
  await new Promise((resolve) => setTimeout(resolve, 2000));
  const retryProof = await collection.verify();

  if (!retryProof.verified) {
    throw new Error("Collection integrity check failed after retry");
  }
}

Audit logging pattern

interface AuditEntry {
  timestamp: string;
  collectionId: string;
  vectorCount: number;
  merkleRoot: string;
  solanaExplorerUrl: string;
  verified: boolean;
}

async function auditedUpsert(vectors: any[]) {
  await collection.upsert(vectors);

  // Wait briefly for async Solana write to settle
  await new Promise((resolve) => setTimeout(resolve, 2000));

  const proof = await collection.verify();

  const entry: AuditEntry = {
    timestamp: new Date().toISOString(),
    collectionId: collection.name,
    vectorCount: proof.vectorCount,
    merkleRoot: proof.onChainRoot,
    solanaExplorerUrl: proof.solanaExplorerUrl,
    verified: proof.verified,
  };

  // Store in your audit log
  await db.auditLog.insert(entry);

  return { proof, entry };
}