Solana ProgramsAuditAnchor

AuditAnchor is the cheapest of the three programs — it stores one Merkle root per batch and that’s it. But it’s the keystone of Regent’s tamper-evidence claim: every audit event ever ingested can be proven (or disproven) against an account in this program.

Program ID (devnet)8N1PpbJZKmvJjG86XWpP82XrWzp8HY5FHZuzyQTgjJas
FrameworkAnchor 0.30
Upgradeable?No
Sourcepackages/contracts-solana/programs/audit-anchor

Why a separate program

Agents and mandates are bounded — there are some number of agents, some number of mandates, and each has a lifecycle. Audit events are unbounded: every action of every agent produces one. Batching them into Merkle roots is the only way to keep on-chain costs constant per event regardless of throughput.

Account layout

#[account]
pub struct AuditBatch {
    pub batch_id: [u8; 16],   // raw UUID bytes
    pub merkle_root: [u8; 32],
    pub event_count: u32,
    pub sealed_at: i64,
    pub bump: u8,
}

PDA seeds: [b"batch", batch_id].

The account is tiny — 53 bytes plus discriminator — so anchoring is cheap even at scale.

Instructions

seal_batch

Writes a new AuditBatch account. Once written, an account is immutable — there’s no update_batch instruction by design.

ArgType
batch_id[u8; 16]
merkle_root[u8; 32]
event_countu32

Off-chain, api-audit accumulates events until either:

  • A configured batch size threshold is reached (~512 events), or
  • A configured time interval elapses (~5 minutes)

…then computes the Merkle root over the event payload hashes and calls seal_batch. The transaction signature is recorded on every event in the batch as solana_tx.

How proofs work

Once a batch is sealed, every event in it has a Merkle proof — a list of sibling hashes that lets anyone verify the event without downloading the rest of the batch.

If you want to prove that “event 1” was in this batch:

  1. Hash event 1’s canonical payload → leaf hash H
  2. Combine with sibling G (provided in the proof) → get parent D
  3. Combine D with sibling C (provided) → get parent A
  4. Combine A with sibling B (provided) → root R
  5. Compare R against the on-chain merkle_root

Match → event is provably in the batch. No match → either the event was forged, or the batch has been tampered with off-chain.

Why this matters

Without on-chain anchoring, an audit log can be rewritten silently. With anchoring:

  • Adding a fake event after the batch is sealed produces a different root → mismatch
  • Deleting a real event produces a different root → mismatch
  • Modifying any event’s payload produces a different root → mismatch

The only way to forge convincingly would be to issue a new on-chain transaction with the modified root — which leaves a fingerprint on Solana that anyone can see (and that would conflict with the original transaction).

Reading a batch off-chain

const [batchPda] = PublicKey.findProgramAddressSync(
  [Buffer.from("batch"), Buffer.from(batchId, "hex")],
  programId,
);
const batch = await program.account.auditBatch.fetch(batchPda);
console.log("Merkle root:", Buffer.from(batch.merkleRoot).toString("hex"));
console.log("Event count:", batch.eventCount);
console.log("Sealed at:", new Date(batch.sealedAt * 1000));

To verify an individual event, see Verifying on-chain.