Records
A record is one fact the calendar captured, such as a gold price, a block head, a signed tree head, a NIST pulse or a headline, written as a typed envelope of bytes. Every tick commits its records in branches, one Merkle tree per branch, and the branches in one map whose root is part of the tick root. A contract on ETC proves any record with one call, and the Records pages of the explorer list them by branch.
Every byte table, JSON form and rule of the branches is in Records in SPEC.md.
The Envelope
Every record of every branch starts with the same 11-byte header.
| Offset | Length | Field | Encoding |
|---|---|---|---|
| 0 | 1 | tag | 0x03, the record tag |
| 1 | 4 | branch | u32, the branch id, 1 or more |
| 5 | 2 | schema | u16, the payload layout version of that branch |
| 7 | 4 | length | u32, the payload length, the record length minus 11 |
| 11 | length | payload | as the branch and schema define |
leaf = sha256(envelope) the tag is byte 0, no other prefixA record is 11 to 65,536 bytes long and never exactly 64. Sixty-four bytes is the size of an inner node of a Merkle tree, two 32-byte hashes side by side, so a 64-byte record would pass for one. A layout whose bytes would come to 64 adds one 0x00 pad byte, which the decoder drops, and a decoder refuses any other trailing byte. One record therefore has one encoding and one leaf.
The header carries everything a reader needs to split and hash records without knowing their schema. That is how a mirror running an older CLI still checks every root of a day that holds a branch it has never heard of.
Branches and the Map
A tick sorts its records by branch. Inside a branch the envelopes are sorted bytewise and hashed into a Merkle tree, the branch root. The envelopes themselves go out concatenated in the same order as one raw block, the pack, and each envelope carries its own length, so a pack splits one way only. A branch with no records in a tick has no pack and no leaf in the map.
branch_root = merkle_tree([sha256(e) for e in the branch's envelopes, sorted bytewise])
branch_leaf = sha256(0x04 || u32be(branch_id) || branch_root) 37 bytes in
branches_root = merkle_tree(branch leaves sorted by branch_id) ZERO with no records
tick_root = sha256(prev_tick_root || calendar_root || branches_root || tick_header)The branches root sits at offset 64 of the 128 bytes the tick root hashes, next to the calendar root that holds the stamps (Ticks). The branch table is append-only and an id is never reused. The record started with these six.
| Id | Name | Layout | Fed by | Trust |
|---|---|---|---|---|
| 1 | price | fixed, 89 bytes | Kraken, Coinbase, Swissquote, gold-api and Chainlink on Ethereum each minute, the ECB once a day | O, Chainlink rechecked on Ethereum |
| 2 | chain-head | 72 bytes, then the hash | the beacon, when a chain's committed head changes | C where a header is kept, O otherwise |
| 3 | ct-head | 101 bytes, then the signature | the signed tree heads of the Certificate Transparency logs in force, every 5 minutes | S |
| 4 | nist-pulse | 109 bytes, then NIST's fields and signature | the NIST Randomness Beacon 2.0, each minute | S |
| 5 | headline | variable | the items of about 80 news feeds and Hacker News, each distinct item once a day | O |
| 6 | front-page | variable, references to headlines | one per feed capture | O |
Trust says how a reader checks a record without trusting us. S is signed by the source, C is content-addressed (a block hash whose header is in the day's bundle), P is a publisher hash a reader rechecks while the object exists, and O rests on the operator alone. An O record is the operator's account of what a provider said. Providers sign nothing there, and the record shows only that the operator committed those bytes by the anchor time.
Each branch has its own uniqueness rule, such as one price per pair per slot or one pulse per pulse index, and etchd records verify checks it for every day. The rules are versioned, and every registry snapshot names the version it ran, so an old day is judged by the rule it was written under.
Price
A price record is 89 bytes. The price is a uint128 with 18 decimals, so a contract compares and multiplies prices with no exponent per record. Every price source runs once a minute at second 1, and the ECB reference rates once a day at 15:30 UTC. The Prices page shows them minute by minute.
| Offset | Length | Field | Encoding |
|---|---|---|---|
| 11 | 32 | pair | ASCII provider:BASE/QUOTE, padded with 0x00, like kraken:BTC/USD |
| 43 | 16 | price | uint128, quote units per base unit times 10^18, above 0 |
| 59 | 8 | quoted_at | uint64, the provider's time for the quote, read with the flags |
| 67 | 8 | captured_at | uint64, the second the capture started, inside its slot |
| 75 | 2 | parser | uint16, the version of the parser that made the record |
| 77 | 1 | kind | 0 mid of bid and ask, 1 last trade, 2 daily reference rate, 3 on-chain oracle answer, 4 aggregated spot, 5 official close |
| 78 | 1 | flags | bit 0 quoted_at is Date minus Age, bit 1 an update time, bit 2 a trade date, bit 3 revises a value already committed |
| 79 | 10 | ref | uint80, the oracle round id for kind 3, zero otherwise |
bytes32 pair = bytes32(r[11:43]);
uint256 price = uint128(bytes16(r[43:59]));
uint64 quotedAt = uint64(bytes8(r[59:67]));
uint64 capturedAt = uint64(bytes8(r[67:75]));
uint8 kind = uint8(r[77]);
uint8 flags = uint8(r[78]);Kraken and Coinbase give no quote time, so quoted_at is the response's Date header minus its Age, with flag bit 0 set. A cache in front of a provider would make that time too young, so a capture refuses a pair with the reason cached when Age is above 5 s or Cloudflare reports a cache hit, and the retries at seconds 21 and 41 try again. Parsing is exact decimal arithmetic from the source text, never a float. A record is one per pair per source slot, and a fixing is named by the start of its slot, so the 12:00 fixing is the slot that starts at 12:00:01 UTC. The gold escrow reads these records.
{
"pair": "kraken:BTC/USD",
"price": "67123450000000000000000",
"quoted_at": "1767225599",
"captured_at": "1767225601",
"parser": 1,
"kind": 0,
"flags": 1,
"ref": "0"
}etchd at prints the price of a pair that was current at a moment, the last record captured at or before it, in the quote currency and checked against ETC first (The Record at a Moment). records list --format csv with a pair prints the whole series.
etchd at "2026-10-28 05:00" swissquote:XAU/USDThe same on the local devnetrecords current at 2026-10-26T13:38:30Z
id branch key time fields
179302190-1-21 price swissquote:XAU/USD 2026-10-26T13:38:21Z 4,096.474 USD, mid, quoted 2026-10-26T13:38:20Z
2026-10-26T13-38 root bafyreiaxtqfbhb3rxtospd6tk3suqer7al6btfhvcmu5v57fnvjqiipntq verified against ETC block 52, from the local copyChain Head
The beacon follows about 35 chains (The Beacon Chains). At each close the calendar compares the block a quorum of sources agreed on for each chain with the last one it committed, and writes a head record when the hash changed or when the chain has no record yet in that UTC day. Every day's bundle therefore holds the head of every chain that had a quorum that day. A Bitcoin or an Ethereum record is 104 bytes.
| Offset | Length | Field | Encoding |
|---|---|---|---|
| 11 | 32 | chain | sha256 of the CAIP-2 chain id |
| 43 | 1 | kind | 0 head, 1 final |
| 44 | 8 | height | uint64, the chain's own height |
| 52 | 8 | seen_at | uint64, when the calendar's quorum agreed on the block |
| 60 | 8 | block_time | uint64, the block's own time, 0 where a chain has none |
| 68 | 1 | n_agree | sources that named this block |
| 69 | 1 | n_sources | sources asked |
| 70 | 1 | header_format | 0 none, 1 the 80-byte Bitcoin header, 2 an RLP header |
| 71 | 1 | hash_len | 1 to 64 |
| 72 | hash_len | hash | the block hash as the chain shows it |
A head record names the newest block the sources reported, and a final one the newest block the chain's own protocol has finalized. For the UTXO and EVM chains the tick stores the raw block header as an evidence block in the day's bundle, so a reader checks the hash offline. When one block has several head records of a kind, readers and contracts take the earliest anchored one. The Bitcoin swap uses the Bitcoin head records.
etchd at "2026-10-28 05:00" bip122:000000000019d6689c085ae165831e93 prints the Bitcoin head that was current at 05:00 UTC, its height, hash, kind and block time, from a day it checked against ETC first (The Record at a Moment).
CT Head
Certificate Transparency logs sign a tree head over every certificate they hold. Every 5 minutes the source ct-heads asks each log shard in force for its signed tree head, and a record is written once per log, tree size and root hash in a day, so a log that has not moved keeps one record a day.
| Offset | Length | Field | Encoding |
|---|---|---|---|
| 11 | 32 | log_id | the RFC 6962 log ID |
| 43 | 8 | tree_size | uint64 |
| 51 | 8 | timestamp_ms | uint64, the tree head's own time |
| 59 | 32 | root_hash | the tree head |
| 91 | 8 | captured_at | uint64 |
| 99 | 2 | sig_len | uint16 |
| 101 | sig_len | signature | the log's signature as served, about 72 bytes for ECDSA P-256 |
The record keeps the log's own signature, and etchd records verify --signatures checks it with the log key the CLI build pins (or one you add with --trust), never a key only the registry snapshot names. ETC has no P-256 precompile, so no example contract checks it on chain.
NIST Pulse
Each minute the NIST Randomness Beacon publishes a pulse of 512 random bits, signed with NIST's RSA key. The record keeps NIST's signed fields and the signature unchanged, with copies of the pulse index, time and output at fixed offsets. A record is 1,430 bytes today.
| Offset | Length | Field | Encoding |
|---|---|---|---|
| 11 | 8 | captured_at | uint64 |
| 19 | 8 | chain_index | uint64, NIST's chain |
| 27 | 8 | pulse_index | uint64 |
| 35 | 8 | pulse_time | uint64, the pulse time in Unix seconds |
| 43 | 64 | output_value | sha512(fields || signature) |
| 107 | 2 | fields_len | uint16, 807 today |
| 109 | fields_len | fields | the pulse as NIST signs it |
| 109 + fields_len | 2 | sig_len | uint16, 512 |
| 111 + fields_len | sig_len | signature | NIST's RSA signature |
A pulse is named by its chain and pulse index, since NIST restarts the index with each new chain. The capture checks the signature with the key the pulse's certificate names and refuses a signature at or above the modulus, which would also pass the RSA check and give an output NIST never published. NIST Randomness on ETC checks the same bytes inside the EVM.
Headline
A headline is one item of a news feed or of Hacker News, as its source id and up to four short fields, the title, the link, the published date and the guid. Each field has a length prefix and a bit in a presence byte. A headline holds no time and no rank, so one item gives the same bytes on every capture, and a calendar commits each distinct headline once per UTC day.
We read only capped metadata from a feed, the first 100 items with titles cut at 300 code points, links and guids at 500 and dates at 64, and from Hacker News the first 30 stories. No description, body, image or author is ever read. Text is normalized the same way everywhere, with entities decoded, white space folded, NFC applied and an over-long value cut to its cap with a trailing ellipsis.
Front Page
A front page is one capture of one feed, its source id, the capture time, the parser version and its entries in rank order. Each entry is the 32-byte leaf of a headline, and for Hacker News also the score and the comment count. Every entry resolves inside the same day's bundle to a headline record, so a day stands alone.
A front page and the headlines it brings are sealed together in one tick, so a front page captured at 23:59:55 and sealed after midnight writes its headlines again into the new day. About 80 feeds are read every two hours, each at its own minute, and Hacker News every hour. What We Publish lists the feeds.
etchd at prints the front page of a source that was current at a moment, with its headlines expanded by rank to title and link, after it has checked the day against ETC (The Record at a Moment). The long form is records list with --at and --resolve.
etchd at "2026-10-28 05:00" bbc-front
etchd records list etchd-exports --key bbc-front --at "2026-10-28 05:00" --resolve --format tableOn the local devnet, first linesrecords current at 2026-10-26T13:38:10Z
id branch key time fields
179302188-6-0 front-page bbc-front 2026-10-26T13:38:05Z 12 headlines
1 Garden allotment society finds a lost comet catalogue http://fixtures.devnet/bbc/story/29914392?src=rss&n=0
2 Northern ferry line delays the spring timetable http://fixtures.devnet/bbc/story/29914397
3 Island weather station opens a second reading room http://fixtures.devnet/bbc/story/29914402?src=rss&n=2The Decoder
decodeRecord turns an envelope into its fields and its JSON form, with one decoder per branch and schema. The CLI, the record API and the explorer all use it, and no root check runs it. Large integers come out as decimal strings and byte strings as lowercase hex. A branch, schema or rule version the decoder does not know is reported as not decoded, and the day still passes when every root matches.
Decode one recordetchd records decode 0x0300000001000100...
etchd records show day.car 29882000-1-3
curl -s https://etchd.app/v1/records/29882000-1-3 | jq .decodedThe Generic Record Check
One call of EtchdVerifier v2, recordExistedAtBy(operator, branchId, record, recordPath), proves a record of any branch, including one added after the contract was deployed. It checks the envelope header, folds the record into its branch root, the branch leaf into the branches root, hashes the 128-byte tick root, folds the batch and asks EtchdRegistry v2 when that operator sent the result. It returns that time and block, or reverts.
The path cannot be bent. Every hop is 64 bytes, the branch step is 37 bytes and the tick step 128, so a forged path has to follow the real trees down to a real record of that branch. A user's stamp sits in the calendar root at offset 32 of the tick root, where no record path goes, so a stamped digest never passes as a record. Proofs in Contracts walks through the call, its gas and the operator address a contract has to pin. etchd records proof prints its input (Proofs for Contracts).
Branches 7 to 29
A new branch takes the next id, a layout and a parser, and needs no new tick version and no new contract. Branches 7 to 29 are built in the same release and enter one family at a time after the record starts, from drand rounds and signed log checkpoints to official figures, quakes, sanctions files and model catalogs. Their layouts are under Branches 7 to 29 in SPEC.md, and Record Sources gives each family's sources, credits and trust class.