# Daily Bundles

Once a day the calendar writes the previous UTC day as one CAR file. That file holds everything a reader needs to check the day offline, from block headers to every source's answer, packed as content-addressed blocks in the plain archive format IPFS uses to move data around.

## What a Bundle Holds

The export runs shortly after 00:10 UTC, once all of the day's ticks have 400 confirmations. The file holds these blocks.

- the day's tick bodies, the exact bytes behind each tick's CID
- the beacon evidence object of each tick with every source's answer, the error text and HTTP status of the ones that failed, and the raw block headers it stores (bundles with version 3 evidence also hold the raw RPC body of each answer)
- each anchor transaction with 400 confirmations, with the tick roots of its batch, so the calldata rebuilds from the block alone
- each anchor transaction as it was signed, with the ETC block it was mined in, header and all its transactions
- each witness capture the day's ticks commit to, with the normalized form and the raw bytes wherever we kept them (news captures since 2026-10-23 keep metadata only and carry `raw_omitted: "not-kept"`)
- the Bitcoin roll-ups that completed that day, with their proofs
- the signer keys in force that day, the chain and witness registries the ticks ran under, and the normalizers the captures used

Shortly after 00:10 UTC everything a day's ticks committed to goes into one CAR file. The index gives its root CID and sha256, and the file is served from /v1/exports and block by block from /ipfs.

Building the same day twice gives the same bytes (the layouts are under [Daily Export](https://github.com/classix-dev/etchd/blob/main/SPEC.md#daily-export) in SPEC.md), and the file's sha256 and its root CID both name the day. When a publisher asks, we remove the stored raw copy of their capture, and exports built after the removal carry that capture with its raw bytes left out. Its hash and the proofs stay valid.

## Where the Record Starts

The first bundle is 2026-10-24. The calendar ran from 2026-10-21, but those three days were development runs in heavier formats, and we deleted their bundles, tick bodies, captures and index rows. The export index says so in its `start` field and lists no earlier day. The tick and proof routes answer 404 with `purged: before the record starts on 2026-10-24` for a tick from those days. A proof file saved then still checks offline against ETC, because it carries its whole path to the transaction.

The calendar's [chain of ticks](https://github.com/classix-dev/etchd/blob/main/SPEC.md#start-of-the-record) was not restarted. The first tick of 2026-10-24 links back to the last tick of 2026-10-23 through `prev_root` and `prev_body_cid`, and no bundle or route serves that body anymore. It is the one link in the record that points outside it.

## Checking a Day Offline

A version 2 bundle needs no network. Each anchor block's header hashes to its block hash. Its transactions rebuild the header's transactionsRoot. At its index in that list sits the signed anchor transaction, carrying the calldata, and its signature has to recover to the key that the day's signer list names for its epoch at the block's time. Version 3 evidence links the raw body of each answer by CID, and each body must hash to its link. Version 4 evidence keeps no bodies. `etchd mirror` runs these checks on each day it stores and then checks a sample of transactions against ETC RPCs as well.

Since evidence version 4 the calendar keeps no RPC response bodies and stores each answer in about 60 bytes. A day of beacon evidence and tick bodies comes to about 37 MB, and near 26 MB gzipped, plus the witness captures. Bundles with version 3 evidence carried every body and ran near 1 GB, and bundles built before version 2 held the anchor records and tick roots but not the signed transactions or blocks. Both kinds were from before 2026-10-24 and went with the purge.

Next to each CAR sits a gzipped copy, `<date>.car.gz`, with its own sha256 and size in the index. It is the same CAR byte for byte once unpacked, served as a file rather than with a content encoding, and it is the copy we upload to the Internet Archive. `etchd mirror` fetches it when the index lists one and checks both hashes.

The plain CAR also goes to [Filebase](https://filebase.com/), an IPFS pinning service outside Cloudflare, through its `dag/import` API with the day root pinned. Once `pin/ls` lists the root as a recursive pin and its block read back from the Filebase gateway hashes to the CID, the index gives the day a `filebase` field with the CID and a gateway link. A day without the field has no checked copy there yet.

## The Export Index

The [Archive](https://etchd.app/archive) page lists every day with its download links and IPFS pins.

The page fills this list in the browser from [https://etchd.app/v1/exports](https://etchd.app/v1/exports).

The export workflow waits up to four hours for the day's last ticks. If a tick is still short of 400 confirmations after that, it builds the export anyway and marks it incomplete, and a mirror that stores the day reports how many of its ticks have no anchor.

Fetch a day and check it

```
curl -s https://etchd.app/v1/exports | jq '.exports[-1]'
curl -sO https://etchd.app/v1/exports/2026-10-24.car.gz
sha256sum 2026-10-24.car.gz     # compare with gz.sha256 in the index
gunzip -k 2026-10-24.car.gz
sha256sum 2026-10-24.car        # compare with the sha256 in the index
```

Single blocks are served at `/ipfs/<cid>` in the IPFS Trustless Gateway format, with `?format=raw` for the block bytes or `?format=car` for a one-block CAR. The gateway checks each block against its CID before sending it. The raw RPC bodies that version 3 evidence links come only inside the CAR or from an IPFS node that imported it.

```
curl -s -H 'accept: application/vnd.ipld.raw' https://etchd.app/ipfs/<cid> > block.bin
```

The same day comes from Filebase by its root CID, without etchd.app. The gateway walks the DAG and sends the blocks in its own order, so the file it returns has the same blocks as ours and a different sha256. It is slow for a whole day. On 2026-10-23 it sent 16 MB of the 33 MB day of 2026-10-21 in five minutes. An IPFS node of your own can pin the CID as well.

Fetch a day by CID

```
cid=$(curl -s https://etchd.app/v1/exports | jq -r '.exports[] | select(.date == "2026-10-24") | .filebase.cid')
curl -s "https://ipfs.filebase.io/ipfs/$cid?format=raw" > root.bin     # the day root block
curl -s "https://ipfs.filebase.io/ipfs/$cid?format=car" > 2026-10-24.ipfs.car
ipfs pin add $cid     # on your own Kubo node
```

## IPFS Without Gateways

etchd.app does not use public IPFS gateways. Proofs never pass through IPFS. Those gateways are going away. On 2026-09-21 ipfs.io and dweb.link stopped serving backend fetches ([gatewaychanges.ipfs.io](https://gatewaychanges.ipfs.io/)), and Cloudflare's gateway has been gone since 2024-08-14, while the [IPFS Foundation](https://blog.ipfs.tech/2026-08-beyond-sponsored-gateways/) says automated clients "shouldn't rely on the public-good gateway at all". Etchd uses content addressing and leaves the IPFS network to the people who run nodes, for the reasons we gave in [The IPFS Decision](https://github.com/classix-dev/etchd/blob/main/docs/design.md#the-ipfs-decision) in docs/design.md.

- Tick bodies and evidence objects are deterministic DAG-CBOR blocks, each with a CIDv1, and the tick bodies link into one DAG.
- Our own Worker serves those blocks as a trustless gateway at `/ipfs/<cid>`, and a client that checks hashes, such as `@helia/verified-fetch`, can read from it. It announces nothing to the DHT.
- The daily CARs reach the DHT through [mirrors](https://etchd.app/docs/mirrors#compose) such as the Docker Compose setup, run by whoever wants a copy.
- We run one such copy ourselves on Filebase, which keeps each day pinned under its CID if etchd.app goes away.
