Self Hosting
You can run an Etchd calendar on your own Cloudflare account. It answers on your domain and anchors with your key, which pays its own gas, and it deploys with the script that put etchd.app live on 2026-10-21. We haven't yet deployed a second calendar on another account ourselves, and the steps below come from the scripts and from our own deploys.
What Runs Where
Etchd is built on Cloudflare Workers, Durable Objects, D1, R2, KV and Workflows. Of the two Workers in a calendar, etchd serves the API, the explorer and the calendar host, while etchd-signer holds the anchor key and answers only the main Worker's service binding. No standalone server binary exists.
For local experiments, the Docker Compose devnet in docker/README.md runs both Workers under wrangler dev against Anvil with no Cloudflare account at all.
You need Workers Paid. By our arithmetic at one tick a minute, the Durable Objects take about 97,000 requests a day against the Free allowance of 100,000, and the Anchor and each BeaconGroup stay awake around the clock at about 11,000 GB-s a day each, while a Free account gets 13,000 GB-s for everything it runs. Free allows 50 subrequests per invocation. Our beacon alarm plans up to 100. We took those limits from Cloudflare's pricing pages and haven't tried Etchd on a Free account (Workers Free and Workers Paid).
The Anchor Contract
You don't need a contract of your own. EtchdAnchor at 0xa6f241d2 has no owner and takes posts from any sender, logging each one under the sender's address. Your signer address tells your anchors from ours.
A copy is optional. scripts/deploy-contract.ts deploys one at 1 gwei from the key in ETCHD_SIGNER_KEY, and with --dry-run it prints the gas estimate and the address the contract will get. Ours took 68,141 gas. Give the script a separate deployer key if you can, because the Anchor halts when another transaction uses one of its nonces, and the script refuses a key that has sent transactions before unless you pass --force.
ETCHD_SIGNER_KEY=0x... node scripts/deploy-contract.ts --dry-run
ETCHD_SIGNER_KEY=0x... node scripts/deploy-contract.tsBefore You Start
You need these:
- A Cloudflare account on Workers Paid, with your domain as a zone in it.
- An API token. deploy.ts calls the D1, KV and R2 APIs itself and wrangler does the rest, which comes to the account permissions D1 Edit, Workers KV Storage Edit, Workers R2 Storage Edit and Workers Scripts Edit, plus Workers Routes Edit on your zone. We haven't tried a narrower token.
- Node 22.18 or later and pnpm, or Nix. We run our deploys in
nix shell nixpkgs#nodejs_22 nixpkgs#pnpm nixpkgs#foundryfrom the repo root, afterpnpm install. - An ETC key used for nothing else, holding a few days of gas.
One anchor takes about 23,100 gas. At 1 gwei and 1,440 anchors a day, one for each UTC minute, that comes to 0.033264 ETC a day or about 1 ETC a month.
Configure
The deploy script holds the production configs to the constants in scripts/lib/prod.ts, and the Worker runs with the production env of packages/worker/wrangler.jsonc. Change these:
| File | Setting | Set to |
|---|---|---|
scripts/lib/prod.ts | CLOUDFLARE_ACCOUNT | Your account id |
scripts/lib/prod.ts | ZONE | Your domain |
scripts/lib/prod.ts | ETC_RPCS | The RPCs deploy.ts checks the contract on |
packages/worker/wrangler.jsonc | routes | Your domain and a.etc.calendar.<your domain> |
packages/worker/wrangler.jsonc | CALENDAR_URI | https://a.etc.calendar.<your domain> |
packages/worker/wrangler.jsonc | ETC_RPCS | The RPCs your Anchor reads |
packages/worker/wrangler.jsonc | IA_ITEM_PREFIX | A prefix of your own, with archive.org keys |
deploy.ts refuses a CLOUDFLARE_ACCOUNT_ID other than the one in prod.ts. The guard takes the calendar URI only in the form https://a.etc.calendar.<zone>, with ETC_QUORUM at 2 and CAL at a. Item names at archive.org are global, and without IA_ITEM_PREFIX yours would be named etchd-a-<date> like ours. Chain 61, the 1 gwei gas price, 60 s ticks, the 0.05 ETC daily cap and the internal depths of 12 and 400 are fixed, while the depth at which proofs are served (PROOF_DEPTH, default 1) is a var (Fixed Parameters).
The chain registry and the witness sources live in KV. The deploy seeds the registry key from packages/core/src/chains/prod.ts when it is missing, and --reseed overwrites it. Without a witness_sources key the Scheduler captures the list in packages/core/src/witness/registry.ts, the RSS front pages of 80 news outlets among them, and publishers' takedown requests would come to you. An empty list turns captures off.
wrangler kv key put witness_sources '{"env":"production","sources":[]}' --namespace-id <id> --remoteThe config key holds the live settings, read every 30 s. On a new calendar we would set alertWebhook first. Setting btcBridge to false stops the hourly Bitcoin roll-ups, 24 digests a day to the OpenTimestamps aggregators.
Secrets
Keys and tokens come from the environment. The script hands each secret to wrangler secret put on stdin without printing it, and a secret missing from the environment keeps its old value.
| Variable | Goes to | Needed |
|---|---|---|
CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID | deploy.ts | Yes, except for --dry-run |
ANCHOR_CONTRACT | A var on both Workers | Yes |
ETCHD_SIGNER_KEY | Signer secret | Yes |
ETCHD_SIGNER_KEY_NEXT | Signer secret | For a key rotation |
ADMIN_TOKEN | Worker secret | Yes, for /v1/admin |
ALERT_WEBHOOK | Worker secret | No |
IA_ACCESS_KEY, IA_SECRET_KEY | Worker secrets | No, and both or neither |
FILEBASE_RPC_TOKEN | Worker secret | No |
ANCHOR_CONTRACT is our address or your copy. The signer keys are 0x and 64 hex characters.
Deploy
Start with a dry run. It needs no token and calls no Cloudflare API, and it bundles both Workers with wrangler deploy --dry-run.
node scripts/deploy.ts --dry-run
CLOUDFLARE_API_TOKEN=... CLOUDFLARE_ACCOUNT_ID=... \
ANCHOR_CONTRACT=0x... ETCHD_SIGNER_KEY=0x... ADMIN_TOKEN=... \
node scripts/deploy.tsThe real run, in order:
- checks the configs against the guards
- confirms that at least two ETC RPCs serve the anchor runtime at
ANCHOR_CONTRACT - creates the missing D1 databases, KV namespace and R2 buckets, with r2.dev access off
- writes
wrangler.production.generated.jsoncnext to each config and applies the D1 migrations - seeds KV and builds the explorer
- deploys
etchd-signer, thenetchd, and sets the secrets
Run it again whenever you like, since it creates only what is missing. --skip-secrets leaves the secrets alone and --skip-build reuses packages/web/dist. Through pnpm it is pnpm run deploy, because pnpm deploy is a pnpm command of its own.
Smoke Test
scripts/smoke.ts exits 1 if any check fails. From outside, it stamps one random digest and finds it again on both hostnames, makes sure the explorer comes from Static Assets while the API paths reach the Worker, and hashes a sample of published witness bodies. It defaults to our hosts. Name yours.
Smoke testnode scripts/smoke.ts https://example.com --calendar https://a.etc.calendar.example.comStamping Against It
The CLI installs from the GitHub release with one line, once we have pushed the first cli-v* tag. With the clone you deploy from, pnpm cli:pack builds the same tarball.
npm i -g https://github.com/classix-dev/etchd/releases/latest/download/etchd-cli.tgz
# or, from the repo root
pnpm cli:pack && npm i -g ./packages/cli/dist/etchd-cli.tgzWithout -c the CLI stamps on our calendar. ETCHD_CALENDAR sets yours for every command. A proof names the calendar it came from, and upgrade asks that one with no flag.
etchd stamp report.pdf -c https://a.etc.calendar.example.com
etchd upgrade report.pdf.ots --wait
etchd verify report.pdf.ots --signers https://example.com/v1/signers --require-known-signerYour Signer List
/v1/signers on your calendar lists each signer epoch with the time and tick it started at, the launch key as epoch 1. The Anchor records them itself. The verifier fetches that list from the calendar a proof names, and whoever hands over a proof picks that calendar. Publish your signer addresses in a second place under your name, such as a repo, where a reader can copy them into a file for --signers.
Running It
Alerts go as a JSON POST to ALERT_WEBHOOK or alertWebhook. The Anchor holds back a repeat of the same alert key for 10 min, which keeps a stuck condition from flooding the hook.
low-balance fires when the hot wallet holds less than one day of spend, 0.033264 ETC by default, and the watchdog reads the balance every five minutes. /v1/status shows it as balanceWei. We top ours up by hand. The signer stops for the rest of a UTC day after 0.05 ETC of gas, counting the full 30,000 gas limit of each signature, which reserves 0.0432 ETC on a normal day.
To rotate, put the new key on the signer as ETCHD_SIGNER_KEY_NEXT, fund it with at least one day of spend, and call the route below. Proofs from the old epoch stay valid. Leave about 0.001 ETC on the old address for 30 days, because the reorg audit can send an old-epoch transaction back to the mempool. After that, promote the secret as Key Rotation in DEPLOY.md describes.
curl -X POST https://example.com/v1/admin/rotate -H "authorization: Bearer $ADMIN_TOKEN" \
-H "content-type: application/json" -d '{"reason": "move to the long-term key", "waitSeconds": 60}'Beacon rows go to one database per UTC year, and a calendar still running on 2027-01-01 needs a BEACON_DB_2027 binding added in December 2026.
Daily exports start by themselves for each UTC day that ends after the deploy. The backfill loops in DEPLOY.md rebuild ticks sealed before a feature shipped, and a calendar started today needs none of them until an upgrade brings one. For a copy elsewhere, run etchd mirror -c https://example.com --dir ./mirror on another machine (Mirrors).
A halted Anchor stays halted until POST /v1/admin/resume. Durable Object storage keeps 30 days of point-in-time recovery, and the full runbooks for etchd.app are in DEPLOY.md and docs/operations.md.
Costs
Cloudflare charges $5 a month for Workers Paid, plus usage past what the plan includes. Usage depends on your traffic, and Cloudflare's pricing page has the current rates. We haven't checked Workflows step pricing on our own account yet. Our daily exports take about 60 MB a day in R2, under 2 GB a month, and at $0.015 per GB-month that stays under $3 in the first year.
Gas is 1 gwei × about 23,100 gas × 1,440 transactions, or 0.033264 ETC a day. A contract of your own adds about 0.00007 ETC once.
Known Limits
Parts of the code still name etchd.app. The CLI's default calendar, the defaults of smoke.ts, the --status-url default of deploy-contract.ts and the line deploy.ts prints when it finishes all point at our hosts. The explorer you deploy carries these docs, and they describe our calendar and our contract from the constants at the top of packages/web/src/pages/docs/Doc.tsx. The witness fetchers send a User-Agent with our URL in it. Change them in your fork.