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 |
| Framework | Anchor 0.30 |
| Upgradeable? | No |
| Source | packages/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.
| Arg | Type |
|---|---|
batch_id | [u8; 16] |
merkle_root | [u8; 32] |
event_count | u32 |
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:
- Hash event 1’s canonical payload → leaf hash
H - Combine with sibling
G(provided in the proof) → get parentD - Combine
Dwith siblingC(provided) → get parentA - Combine
Awith siblingB(provided) → rootR - Compare
Ragainst the on-chainmerkle_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.