Reading the anchor on-chain yourself
06 · On-chain
The point of anchoring on a public chain is that you never have to take Backrun's word for what's there. This page shows exactly how to look it up — with a public RPC endpoint, no account, no API key, and no Backrun software involved at any step.
The contract
| Address | 0x256080339DEA7E8F3089C49CCEB0F98547103f91 |
| Chain | Base mainnet, chain id 8453 |
| Explorer | basescan.org |
| Source | contracts/src/AuditAnchor.sol, MIT licensed |
Confirm the contract and chainId fields on your own
receipt match these before trusting anything else on this page — a root that
matches on the wrong chain or the wrong contract proves nothing about the right one.
Why this contract can be trusted to not have a back door
The entire contract, in full — short enough to read end to end:
contract AuditAnchor { event Anchored( address indexed publisher, uint256 indexed sequence, bytes32 indexed root, bytes32 batchId, uint256 timestamp ); mapping(address publisher => uint256 count) public anchorCount; error EmptyRoot(); function anchor(bytes32 root, bytes32 batchId) external returns (uint256 sequence) { if (root == bytes32(0)) revert EmptyRoot(); unchecked { sequence = ++anchorCount[msg.sender]; } emit Anchored(msg.sender, sequence, root, batchId, block.timestamp); } }
That's the whole contract. Notice what's absent:
- No owner, admin role, or access control of any kind — anyone can call
anchor(), for their own address only. - No upgradeability — no proxy, no
delegatecall, no way to swap in different logic later. - No function that updates or deletes a past anchor. There is no code path for it — not "disabled," structurally absent.
- No pause switch.
Roots live in the event log, not contract storage — logs are far cheaper than
SSTORE and equally readable by anyone with an archive node or indexer. The
only thing actually stored is anchorCount, a per-publisher counter, so a
verifier can detect gaps: if you expect sequence 1 through N and only find 1
through N−1, something is missing, without needing an indexer to tell you so.
Anchors are namespaced by msg.sender. Backrun's operator key
is not a privileged authority in this contract — it's one publisher among any number of
others who could call the same function. A customer who stopped trusting Backrun entirely
could anchor their own roots on this same contract and keep every property this page
describes.
The Anchored event
event Anchored( address indexed publisher, uint256 indexed sequence, bytes32 indexed root, bytes32 batchId, uint256 timestamp )
Solidity ABI (JSON), matching engine/src/anchor/auditAnchorAbi.ts:
{
"type": "event",
"name": "Anchored",
"inputs": [
{ "name": "publisher", "type": "address", "indexed": true },
{ "name": "sequence", "type": "uint256", "indexed": true },
{ "name": "root", "type": "bytes32", "indexed": true },
{ "name": "batchId", "type": "bytes32", "indexed": false },
{ "name": "timestamp", "type": "uint256", "indexed": false }
]
}
Topic 0 (the event signature hash every log of this type carries) is
keccak256("Anchored(address,uint256,bytes32,bytes32,uint256)"):
Finding your root with cast
Foundry's cast against any
public Base RPC — no key, no signup:
# current chain tip, to sanity-check you're pointed at Base mainnet cast chain-id --rpc-url https://mainnet.base.org # → 8453 # how many times a given publisher has anchored (your receipt's `publisher` field) cast call 0x256080339DEA7E8F3089C49CCEB0F98547103f91 \ "anchorCount(address)(uint256)" 0x16EF29F63A4CB0624EF5c1079E08E1530A0b7142 \ --rpc-url https://mainnet.base.org # → 2 # the actual Anchored logs — eth_getLogs is capped at a 10,000-block range per call # by most public RPCs, so widen fromBlock/toBlock if your anchor is further back cast logs --rpc-url https://mainnet.base.org \ --address 0x256080339DEA7E8F3089C49CCEB0F98547103f91 \ --from-block 49840000 --to-block latest \ "Anchored(address,uint256,bytes32,bytes32,uint256)"
Real output, from this contract, at the time this page was written:
- address: 0x256080339DEA7E8F3089C49CCEB0F98547103f91
blockNumber: 49844752
data: 0xe1f0680050984951b9bd7f250e75441800000000000000000000000000000000
000000000000000000000000000000000000000000000000000000006a7b8103
topics: [
0x8277244955aa743047a5b8241ef1294e25c28ff1b2a98dcf10710a67510c43b5 // topic0: Anchored(...)
0x00000000000000000000000016ef29f63a4cb0624ef5c1079e08e1530a0b7142 // publisher
0x0000000000000000000000000000000000000000000000000000000000000001 // sequence = 1
0x9c84221263e9265b0e2e62bd356393fa6f30c01339e26f21ab645da2da652195 // root
]
transactionHash: 0xa6f5999326a7e32173afb7b1d660830103254b11f63b9656bf41d1b6cf262f9a
- address: 0x256080339DEA7E8F3089C49CCEB0F98547103f91
blockNumber: 49844809
data: 0x7ec21c87385800d6a85a16baa6c044c948e42edb5520ba8c204328cf2013e6b6
00000000000000000000000000000000000000000000000000000006a7b8175
topics: [
0x8277244955aa743047a5b8241ef1294e25c28ff1b2a98dcf10710a67510c43b5
0x00000000000000000000000016ef29f63a4cb0624ef5c1079e08e1530a0b7142
0x0000000000000000000000000000000000000000000000000000000000000002 // sequence = 2
0xf08778f618913874b8e07513f7abfbb9457d911db3d501a0d394096ec487f0b0
]
transactionHash: 0x806421299727b8524c7fa68e86f9b8c1b0209bb82ad47f37d09112da4fd1ca33
data holds the two non-indexed parameters back to back, 32 bytes each:
batchId then timestamp. Decoded, the first log above is
batchId = 0xe1f0680050984951b9bd7f250e75441800000000000000000000000000000000,
timestamp = 1786478851 (2026-08-11T20:07:31Z) — matching the
batchId/anchoredAt pair on a real receipt exactly.
Once you have the root from a log, compare it — as a plain string — to
receipt.root. That comparison, plus the offline proof fold described on the
verification algorithm page, is the entire trust
model.
Finding your root with viem
import { createPublicClient, http, parseAbiItem } from "viem"; import { base } from "viem/chains"; const client = createPublicClient({ chain: base, transport: http() }); // public RPC, no key const logs = await client.getLogs({ address: "0x256080339DEA7E8F3089C49CCEB0F98547103f91", event: parseAbiItem( "event Anchored(address indexed publisher, uint256 indexed sequence, bytes32 indexed root, bytes32 batchId, uint256 timestamp)" ), args: { publisher: "0x16EF29F63A4CB0624EF5c1079E08E1530A0b7142" }, // your receipt's `publisher` field fromBlock: 49_840_000n, toBlock: "latest", }); for (const log of logs) { console.log(log.args.sequence, log.args.root, log.args.batchId, log.args.timestamp); }
This is the same call the engine's own auditAnchorAbi
(engine/src/anchor/auditAnchorAbi.ts) is built for — kept hand-written rather
than generated specifically so it stays readable by a human without a build step, since
it's the interface a third-party verifier reads, not just internal plumbing.