Developer guide · Zikaron

The registry contract and anchoring

The registry contract does one thing: it receives a fingerprint and emits an event. What the law reads is the format of the event.

Interface

event Anchored(address indexed author, bytes32 indexed hash); function anchor(bytes32 hash) external; function anchorMany(bytes32[] calldata hashes) external;

anchor emits one event; anchorMany emits one for each element of the array, and zero for an empty array. The author in the event is the caller’s address. The contract has zero storage, zero owners and zero upgrade paths, and treats every caller alike. The source is base/zikaron-core/contracts/src/ZikaronRegistry.sol, licensed CC0-1.0.

Known deployments

The app comes with two deployments of the same pinned build, with the same code hash:

NetworkChain IDRegistry contractStart block
Ethereum mainnet (default)10x36Ea8A857a5FE813429d4D9947000C644A88809A26087229
Sepolia testnet111551110xC29410B882c4C3b77e33659d2f06ac563e7B08a311715660

The start block is where scanning begins, earlier than the first anchor on that contract.

Two forms of anchor

Registry form
The transaction succeeds, and its receipt holds a log emitted by a registry contract with exactly three topics and empty data: topic 0 is the keccak256 of Anchored(address,bytes32), topic 1 is the transaction’s sender, topic 2 is the fingerprint. The same fingerprint also appears in the transaction’s calldata, at an offset that is a multiple of 32, or 4 plus a multiple of 32.
Self-transfer form
A transaction sent to oneself whose calldata is a positive multiple of 32 bytes long, each 32 bytes being one fingerprint. It relies on the transaction alone and suits any EVM chain.

The two forms are equally valid. A verifier states in its scan range which registry addresses, which senders and which self-transfers it accepts.

Three requirements on the sender

  • The sender is the address recovered from the transaction’s signature. For a transaction sent through a relayer, the sender is the relayer’s address; for a transaction validated by account code, the sender field under the law is empty.
  • The sending address is a plain address both before and after the block of the anchor. Anchors sent from an address that carries code or an EIP-7702 delegation are void.
  • The transaction’s signature names the chain ID.

These three keep every anchor readable as the key holder’s own act.

The code hash

The contract’s runtime bytecode is 246 bytes long, and its keccak256 is:

Code hash0xfa97a1d9b22fab2b52f4e27c9a965b32734c40001b565ab365d05c887118f57d

It comes from fixed build settings: solc 0.8.28, EVM version cancun, optimizer on with 200 runs, via_ir on, metadata removed from the bytecode. Any change to the source or the settings changes the hash. To recompute it:

cd base/zikaron-core/contracts forge build cast keccak $(forge inspect ZikaronRegistry deployedBytecode)

Deploying your own

Anyone may deploy the same bytes on any EVM chain that has passed the Shanghai upgrade. After deploying, check that the hash of the code on chain matches the table above, then enter the chain ID, registry contract and start block in the app under Settings › Network › “Edit nodes…”.

forge create src/ZikaronRegistry.sol:ZikaronRegistry \ --rpc-url <node> --private-key <deployment key> --broadcast cast keccak $(cast code <new address> --rpc-url <node>)

Deploy with a dedicated key; the identity key signs the ledger and sends anchors.

This pinned build uses the PUSH0 instruction and suits chains that have passed the Shanghai upgrade. An earlier chain needs its own build, with its own code hash. The law reads logs by the contract addresses a verifier declares, so a second build is purely a deployment convenience.

Gas, for reference

CallGas used by the contract
anchor2,352
anchorMany, 1 element2,776
anchorMany, 10 elements17,746
anchorMany, 100 elements167,661

The table covers the contract’s execution as measured in forge; the transaction’s 21,000 base cost and calldata cost come on top. The more elements, the less each one costs. The app sends with a gas limit of 200,000.

Anchoring through other contracts

Any contract that emits a log of the same format can serve as a registry. The log counts as an anchor when three conditions hold together: the transaction is sent by the ledger’s key itself; the fingerprint appears in that transaction’s own calldata, at an offset as stated above; and the log is emitted by the contract that was called. When a contract reaches a registry through an inner call, the log names that contract’s address, and to every ledger it is an ordinary log.

A verifier should list in its scan range only contract addresses whose source it has read and whose code is fixed.

Self-contained anchor proofs

Law §9.7 defines self-contained anchor proofs, and the proofs/ directory of a record kit carries them. With such a proof a verifier checks an anchor from two trusted block hashes alone: the block headers, Merkle proofs of the transaction and of its receipt, and account proofs of the sending address at the two blocks. Such proofs stay valid after nodes prune their history.