Operating Fletcher (the keeper)
Fletcher is the provider's off-chain service. It holds the hash-chain seed, watches
for callback requests addressed to your provider, computes the matching reveal, and submits
revealWithCallback to deliver randomness. This guide covers running it reliably.
What Fletcher does
- Preflight — confirms the provider is registered and reports how many random values remain in the chain.
- Backfill — scans recent blocks for callback requests missed while offline and queues them.
- Watch — subscribes to
RandomnessRequestedfor your provider and fulfills each callback request (serially, to avoid nonce races). - Skips requests already fulfilled on-chain; retries transient failures.
It does not need to serve the pull flow — pull-flow requesters reveal themselves. If
you also offer pull-flow randomness, expose an endpoint that returns
revelation <seq> for already-requested sequence numbers only (never future ones).
CLI
fletcher genseed # generate a 32-byte seed (store as FLETCHER_SEED)
fletcher register # register the provider (commitment = keccak^N(seed))
fletcher run # start the keeper
fletcher info # print the on-chain provider record
fletcher revelation <seq> # print the revealed value for a sequence (debug / pull-flow serving)
fletcher rotate <seedHex> <len> # rotate onto a fresh chain segment
Run via pnpm --filter @quiver/fletcher exec tsx src/index.ts <cmd>, or build
(pnpm --filter @quiver/fletcher build) and use the fletcher bin.
Configuration (environment)
| Var | Required | Meaning |
|---|---|---|
FLETCHER_NETWORK | yes | rh_testnet | rh_mainnet | local |
RH_TESTNET_RPC_URL / RH_MAINNET_RPC_URL / LOCAL_RPC_URL | recommended | RPC endpoint (use a dedicated provider in prod) |
QUIVER_COORDINATOR_ADDRESS | yes | deployed coordinator |
PRIVATE_KEY | yes | the provider/operator key |
FLETCHER_SEED | yes | 32-byte hash-chain seed — secret |
FLETCHER_CHAIN_LENGTH | yes | Random values per chain segment |
FLETCHER_FEE_WEI | no (0) | per-request fee at registration |
FLETCHER_MAX_NUM_HASHES | no (500) | reveal hash bound at registration |
FLETCHER_ANCHOR | no (0) | sequence anchor of the current segment (set after a rotation) |
FLETCHER_BACKFILL_BLOCKS | no (5000) | startup look-back window |
FLETCHER_POLL_MS | no (3000) | event poll interval |
Seed security — this is the crown jewel
Anyone who learns your seed can predict every future value and thus every outcome
you'll serve. Treat it exactly like a private key:
- Generate with
fletcher genseed(CSPRNG). Never derive it from something guessable. - Store in a secrets manager / KMS, not in a plaintext file on a shared box.
- Never log it, commit it, or put it in a shell history (
exportfrom a secrets tool). - Rotate on any suspicion of compromise (below). Future values become safe immediately.
Rotation
Chains are finite. Rotate before exhaustion (Fletcher warns when values run low) or to retire a compromised seed:
NEW=$(fletcher genseed 2>/dev/null)
fletcher rotate "$NEW" 100000
# then update env for the NEW segment and restart:
# FLETCHER_SEED=$NEW FLETCHER_CHAIN_LENGTH=100000 FLETCHER_ANCHOR=<printed anchor>
Rotation anchors the new chain at sequenceNumber - 1, so the next request verifies in
one hash. Keep the old segment's config if old requests may still be outstanding —
Fletcher must reveal those against the old seed. For zero-downtime multi-segment serving,
run the keeper with a persisted list of segments (roadmap) or briefly run two instances
(old-segment reveal + new-segment run).
Running in production
- Process manager: systemd or a container with
restart: always. Fletcher exits cleanly on SIGINT/SIGTERM. - Dedicated RPC: public endpoints are rate-limited; use Alchemy or another dedicated provider for both reads (event polling) and writes.
- Operator ETH: the provider key pays gas for every reveal. Alert and top up before it runs dry. (A dry keeper = withheld reveals = stalled consumers.)
- Monitoring / alerts:
- low operator ETH balance,
- remaining values below a threshold (
endSeq - sequenceNumber), - reveal failures / growing gap between requests and reveals,
CallbackFailedvolume (consumer bugs, not yours, but worth visibility).
- Idempotency: Fletcher tracks seen sequence numbers and re-checks on-chain state, so a restart won't double-submit.
Example systemd unit
[Unit]
Description=Quiver Fletcher keeper
After=network-online.target
[Service]
WorkingDirectory=/opt/quiver
EnvironmentFile=/etc/quiver/fletcher.env # holds FLETCHER_SEED etc. (chmod 600)
ExecStart=/usr/bin/node /opt/quiver/fletcher/dist/index.js run
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Failure handling reference
| Situation | Fletcher behavior |
|---|---|
| Request already fulfilled | detected via getRequest → skipped |
| Reveal tx fails (RPC/gas) | logs error, drops from "seen" so it retries |
| Consumer callback reverts | on-chain buffer + CallbackFailed; consumer retries via retryCallback |
| seq outside current segment | error asking you to configure the segment's seed/anchor |
| Random values nearly exhausted | warns on preflight — rotate |