Solana ProgramsVerifying on-chain

This page assumes you don’t trust Regent’s database. Everything below uses standard Solana tooling and public on-chain data only.

Verify an agent

You need:

  • An agent_id (from Regent’s API or the dashboard)
  • The AgentRegistry program ID: 5jBmqyeo1vUAjHbEFuY59NMGTQR8cEe9Jvz2uCwCjp3L
import { Connection, PublicKey } from "@solana/web3.js";
import { sha256 } from "js-sha256";
 
const conn = new Connection("https://api.devnet.solana.com");
const programId = new PublicKey("5jBmqyeo1vUAjHbEFuY59NMGTQR8cEe9Jvz2uCwCjp3L");
 
const agentId = "agent_b1c59d23...";
const agentIdHash = Buffer.from(sha256(agentId), "hex");
 
const [pda] = PublicKey.findProgramAddressSync(
  [Buffer.from("agent"), agentIdHash],
  programId,
);
 
const info = await conn.getAccountInfo(pda);
if (!info) {
  console.log("Agent not anchored on-chain — do not trust");
  process.exit(1);
}
 
// Parse with anchor IDL or borsh-deserialize manually
// Expected: status field offset, registered_at, revoked_at, etc.

If the account exists and status == Active, the agent is verifiably present and not revoked. If the account is missing, the agent claim is forged.

Verify a mandate’s limits

When a disputed transaction is challenged, you want to confirm the mandate’s limits at the time of authorization.

const programId = new PublicKey("8HAzw3UFGmabsHJkAsuGLfBZG8djYQ3J1FRNUVjkseMr");
const mandateIdBytes = uuidToBytes("cab07ae3-e0e3-48d7-9e41-dc10512c4329");
 
const [pda] = PublicKey.findProgramAddressSync(
  [Buffer.from("mandate"), mandateIdBytes],
  programId,
);
 
const account = await program.account.mandate.fetch(pda);
console.log("Per-tx:", account.perTxLimit?.toString());     // e.g. "5000" cents = $50
console.log("Daily:", account.dailyLimit?.toString());      // e.g. "30000" cents = $300
console.log("Status:", account.status);                     // Active | Suspended | Revoked

These limits are what the protocol used to authorize or reject the transaction. If the API’s reported limits differ from these, trust the on-chain values.

Verify an audit event

This is the most involved recipe — and the most valuable. It proves that a specific event was in a specific batch, and that the batch is anchored on Solana.

You need:

  • The event’s payload (or payload_hash)
  • The event’s batch_id, merkle_index, merkle_proof (returned by GET /audit/events/{event_id})
  • The AuditAnchor program ID: 8N1PpbJZKmvJjG86XWpP82XrWzp8HY5FHZuzyQTgjJas
import { createHash } from "crypto";
 
// 1. Canonicalize the event payload and hash it
function canonicalJson(obj: unknown): string {
  // Sort keys, no whitespace
  return JSON.stringify(obj, Object.keys(obj as object).sort());
}
const payloadHash = createHash("sha256")
  .update(canonicalJson(event.payload))
  .digest();
 
// 2. Walk up the Merkle tree using the proof
function combine(left: Buffer, right: Buffer): Buffer {
  return createHash("sha256").update(Buffer.concat([left, right])).digest();
}
 
let current = payloadHash;
let index = event.merkle_index;
for (const sibling of event.merkle_proof) {
  const sibBuf = Buffer.from(sibling.replace(/^0x/, ""), "hex");
  if (index % 2 === 0) {
    current = combine(current, sibBuf);
  } else {
    current = combine(sibBuf, current);
  }
  index = Math.floor(index / 2);
}
const computedRoot = current.toString("hex");
 
// 3. Read the on-chain root for this batch
const programId = new PublicKey("8N1PpbJZKmvJjG86XWpP82XrWzp8HY5FHZuzyQTgjJas");
const [batchPda] = PublicKey.findProgramAddressSync(
  [Buffer.from("batch"), uuidToBytes(event.batch_id)],
  programId,
);
const batchAccount = await program.account.auditBatch.fetch(batchPda);
const onchainRoot = Buffer.from(batchAccount.merkleRoot).toString("hex");
 
// 4. Compare
if (computedRoot === onchainRoot) {
  console.log("Event verified on-chain");
} else {
  console.log("Mismatch — event is either tampered or the API lied");
}

If steps 1–4 produce a match, the event was provably in the batch, and the batch is anchored on Solana. No trust in Regent is required.

Programmatic verification via the CLI

A future Regent CLI will wrap these recipes so a single command suffices:

regent verify agent agent_b1c59d23...
regent verify mandate cab07ae3-e0e3-48d7-9e41-dc10512c4329
regent verify event trade-2234146-d41d57ed

…each returning a green check (or a structured failure reason). Until that lands, the snippets on this page are the source of truth.

What attacks this prevents

AttackPrevented?How
Forging a non-existent agentYesNo on-chain account → verification fails
Re-activating a revoked agent in the DBYesOn-chain status is still Revoked
Widening a mandate’s limits retroactivelyYesOn-chain per_tx_limit is fixed
Inserting a fake past event into the audit logYesMerkle root would change → mismatch with on-chain
Deleting an inconvenient eventYesSame — leaf removal changes the root
Modifying an event’s payloadYesHash changes → mismatch

What this does NOT prevent

  • A forged audit event that was never ingested into the protocol won’t have an on-chain proof — but it also won’t carry the payload_hash and merkle_proof fields, so it’s trivially identifiable as not-from-Regent
  • Real-time censorship (the protocol could choose not to ingest an event in the first place — but the responsible party sees all events from their agent via the dashboard, so this is detectable)