Daily Merkle roots
Storing every attestation on-chain would cost more than this product earns, so the history is committed one Merkle root per complete day. The root goes to AttestationRegistry on Robinhood Chain and is mirrored to CheckpointAnchor on Ethereum. Both writes are permanent; neither has an override.
No daily root has been committed yet.
A root covers a whole UTC day and is built only once that day is over, because a tree committed at noon would have to be rewritten at midnight — and a root that can be rewritten is worth nothing. Today’s attestations are live on the assertion ledger already; they enter a tree after the day closes. Only signed verdicts are included, so a node running read-only produces no roots at all.
Why the same root is written twice
AttestationRegistry lives on Robinhood Chain — the chain this project exists to watch. Left there alone, the record is a circular trust assumption: if the chain we audit can be rewritten, so can our record of auditing it. A watchtower whose evidence sits inside the blast radius of the thing it is watching has not moved the trust anywhere, it has only added a step.
CheckpointAnchor closes that loop. It is the same root, the same leaf count and the same mismatch count, written to Ethereum, where Robinhood has no special power and where the rollup itself must ultimately settle. The two writes disagreeing would be its own kind of evidence, which is exactly why each day carries both transaction hashes in the ledger rather than one summarised status.
Verifies signatures on demand and holds the day’s root. Cheap to write, cheap for a contract on this chain to read, and inside the blast radius.
not deployed on this node
A mirror and nothing else. Expensive to write, outside anyone’s reach on Robinhood Chain, and the copy that survives if this project does not.
not deployed on this node
Write-once, with no lever attached
submitDailyRoot reverts with RootAlreadyCommitted if the day already holds a root. There is no second path, no admin overwrite, no upgrade proxy and no pause — not for the owner, not for anyone. A watchtower that can rewrite yesterday is not a watchtower.
The only power the owner holds is over the attester key set, and revocation is deliberately non-retroactive: roots submitted by a key while it was live stay valid forever. Making revocation reach backwards would turn key rotation into silent history deletion, which is the failure mode this contract is shaped to prevent.
The mirror on Ethereum has no owner, no upgrade path, no pause and no admin function of any kind. Writers are fixed at construction and the set can only ever shrink: there is no addWriter, and there never will be, because adding one would require an authority the contract deliberately does not have.
A writer may permanently give up its own access with retireSelf, and nothing can grant it back. The last writer cannot retire — writerCount == 1 reverts with NoWriters — because an anchor with no writers stops accepting history forever. Allowing that would turn an operational mistake into permanent data loss, so the contract refuses.
Reproduce a proof without asking us anything
The tree layout is OpenZeppelin-compatible on purpose, so MerkleProof.verify works unmodified and a third party can rebuild any day’s root from the attestations alone. Leaves are double-hashed, which makes an internal node impossible to pass off as a leaf; sibling pairs are sorted, so a proof is just an ordered list of hashes with no direction bits to get wrong.
digest = EIP-712 hash of the Attestation struct
domain: name "Verderer Attestation", version "1",
chainId 4663, verifyingContract = AttestationRegistry
fields, in this exact order:
bytes32 assertionHash bytes32 parentAssertionHash
bytes32 claimedBlockHash bytes32 claimedSendRoot
bytes32 localSendRoot uint64 l2BlockNumber
uint64 parentBlockNumber uint8 verdict (0 = match, 1 = mismatch)
uint64 checkedAt (unix seconds)
leaf = keccak256(keccak256(digest)) // OpenZeppelin double hash
pair = keccak256(min(a, b) ++ max(a, b)) // siblings sorted, so proofs carry
// no direction bits
odd = a lone node at the end of a level is promoted unchanged
order = leaves sorted by assertionHash ascending
set = signed match and mismatch verdicts only; pending, lagging and
unavailable are never committedWith those rules, membership is checkable on-chain against AttestationRegistry.verifyInclusion(day, leaf, proof), or against the Ethereum mirror using the root from CheckpointAnchor.checkpoint(day). Neither call touches our API.
A root proves what we said and when we said it. It does not stop an assertion, reverse one, or oblige anybody to act on it — Verderer is outside the validator allowlist and cannot open a challenge on Robinhood Chain. The complete list of what we cannot do →
What it does buy is that the record cannot be quietly edited afterwards, by us or by the operator, on either chain. That is the whole claim, and it is a smaller one than it first sounds — which is why it is worth writing down twice.