Skip to content
D20DAODocs
Keeper LabExplorerStatusGitHub

PROTOCOL / GUIDE

Architecture

Arc publishes epochs for demand; Robinhood Chain binds each request to a future drand round.

Read the repository reference
01

Arc: local epochs and on-demand publication

Epochs span 200 blocks. The first starts 200 blocks after registry initialization; each source-selection anchor is the block immediately before its epoch starts. The keeper prepares the current epoch locally: normally the drand round that was current when the epoch started, with its BLS signature. A saved round that has grown too old to be accepted is replaced by a current one, and only while no commit of its epoch has been sent. Idle epochs require no publication transaction. A live paid request triggers publication of that packet, and the registry has the round's signature checked onchain; the accepted packet cannot be overwritten. Unused local packets are retired after 50 epochs. Public events for used epochs remain onchain.

02

Arc: request, publication and future target

A paid request checkpoints the canonical source anchor and escrows its quoted fee even if no epoch commitment exists yet. Its epoch ID is fixed at inclusion; its epoch hash resolves from the first onchain publication. The randomness target is max(requestBlock, epochCommitBlock + 1), strictly after publication. The fixed seed binds chain, coordinator, key hash, request ID, consumer, client seed, mapping hash, request block, target block and target hash, epoch ID and epoch hash. fulfillRandomness(requestId, proof) verifies the genuine secp256k1 VRF proof; fulfillRandomnessBatch serves up to 16 requests in one transaction with the same per-request events, emitting FulfillmentSkipped for members already fulfilled, refunded or past their deadline. How the keepers grouped requests into batches under burst and sustained load on Arc Testnet is published at https://d20dao.org/benchmarks. Single calldata is 452 bytes and proof evidence is 416 bytes, read from FulfillmentEvidence rather than calldata.

03

Robinhood Chain: round-bound requests

D20VRFCoordinatorRobinhood has no epochs and no registry. A request binds, when it is made, the first drand evmnet round scheduled at least 3 seconds after its block's timestamp. fulfillRandomness(requestId, proof, roundSignature) verifies the round's BLS signature through the beacon verifier the first time a request of that round is served, caches the round's randomness and checks the VRF proof against a seed that binds it. fulfillRandomnessBatch(rounds, ids, proofs) serves up to 16 requests, listing each round's signature once. A stateless proof verifier checks VRF proofs and a linked mapping library computes mapped results. Block numbers are the chain's own, read from ArbSys.

04

Robinhood Chain: keepers and settlement

Proof submission is permissionless. The keeper share, 80% of the escrowed fee, goes to the submitter when the owner allows it as a backup keeper, and otherwise to keeper(); the DAO receives the rest. A second keeper can serve the same requests from the same chain data. A typical delivery takes 5–8 seconds. If a request is not fulfilled within 60 seconds, its fee can be refunded to the request's refund address. As with any randomness service, design applications so that a party cannot cancel a request and retry for a better outcome.

05

Arc: recovery and settlement

Round preparation runs outside request processing. There is no per-request fetch or resampling. The drand catalog has one source and no fallback: while no fresh round can be obtained no epoch is published, and affected requests expire and their escrowed fee becomes refundable. Saved snapshots and active transaction recovery data survive restarts. Old-epoch requests can finish after an epoch boundary while their own 60-second deadlines remain open. Only accepted proof earns the configured fee split: the keeper share goes to the wallet that submitted the accepted proof when the registry authorizes that wallet, which is the committer or an allowed backup committer, and otherwise to the committer; the remainder goes to the treasury recipient. Failed recipient transfers become backed credits. Unfulfilled-request escrow remains protected for refunds. Keeper completion time, batching and throughput measured on Arc Testnet, with the operator-declared host and settings, are published at https://d20dao.org/benchmarks and reproducible with https://github.com/d20dao/benchmarks; they describe the testnet keepers at one point in time, not an SLA.

06

Arc: continuity when a keeper stops

The service can run a second keeper alongside the first. Both prepare the same epochs from the same registered recipes, and the registry decides which wallets are allowed to publish, so if the first stops publishing or fulfilling, the second carries on. The takeover does not reach an integration: the same contracts, addresses and fee apply, each request keeps its own 60-second deadline, and the evidence to replay a result is unchanged, because a proof is checked against its fixed input whichever authorized wallet submitted it. That wallet earns the keeper share of the requests it serves itself. A second keeper shortens the time the service is unattended; it adds no second source, because the drand catalog has one, and it is not a delivery guarantee: a request that no wallet serves before its deadline stays refundable on the usual terms.

07

Stable address, explicit upgrade authority

The coordinators, and Arc's registry, use UUPS implementations behind ERC1967 proxies. Ownership transfers in two steps; owner, fee recipient and keeper/committer roles can rotate. Moving a keeper or rotating its transaction wallet does not rotate the VRF key. This implementation has no setter for the fixed VRF key, but the trusted upgrade owner can change implementation behavior. Replay must identify the implementation and configuration active at each historical receipt, not merely the proxy address.

Work with your agents

Copy a task-specific prompt with the protocol references and integration rules.

 

View prompt
Explain how D20DAO fits my application's architecture. On Arc, trace a request through epoch publication, the future target block, fixed-input VRF proof, callback and settlement. Identify the public contracts and the evidence my app needs.

Read these public references first; do not guess signatures, addresses or limits:
- This guide: https://d20dao.org/docs/architecture (full text of every guide: https://d20dao.org/llms-full.txt, agent guide: https://d20dao.org/agents.md)
- SDK README and AGENTS.md: https://github.com/d20dao/d20-sdk (also inside the installed package). It lists the randomness options, callback gas bounds, how to wait for results, compiler setup and gas needs for refunds and retries.
- Protocol source (contracts and replay library): https://github.com/d20dao/d20-sdk/tree/main/protocol, pinned to commit 98e537fb249dd0d3365b8d78e6040a9323a65a88 by PROTOCOL-PROVENANCE.json
- Deployment manifests: https://d20dao.org/deployments/arc-mainnet.json, https://d20dao.org/deployments/arc-testnet.json, https://d20dao.org/deployments/robinhood-mainnet.json, https://d20dao.org/deployments/robinhood-testnet.json
- Integration skills and templates: https://github.com/d20dao/skills (d20-consumer: methods.md, RandomnessConsumer.sol, mappings.mjs)
- Explorer for checking requests: https://d20dao.org/explorer (arc.d20dao.org for Arc Mainnet, arc-testnet.d20dao.org for Arc Testnet; Robinhood requests are read from the chain)
- Agent API, for agents and services without a contract: https://d20dao.org/docs/agents (0.05 USDC per call over x402)
If a link is unavailable, use the installed package's README, AGENTS.md, ABIs and protocol sources.

Networks: Arc Mainnet chain 5042 (live, USDC; RPC https://rpc.mainnet.arc.io; explorer https://explorer.arc.io); Arc Testnet chain 5042002 (build and test here first, USDC; RPC https://rpc.testnet.arc.io; explorer https://testnet.arcscan.app); Robinhood Chain chain 4663 (live, ETH; RPC https://rpc.mainnet.chain.robinhood.com; explorer https://robinhoodchain.blockscout.com); Robinhood Chain Testnet chain 46630 (build and test here first, ETH; RPC https://rpc.testnet.chain.robinhood.com; explorer https://explorer.testnet.chain.robinhood.com). Native USDC on Arc and ETH on Robinhood Chain have 18 decimals. The consumer interface is the same on both; Arc serves requests through epochs, Robinhood Chain binds each request to a future drand round. Compile with Solidity 0.8.28 and EVM version cancun, importing contracts from the installed @d20dao/vrf-sdk package. Take the coordinator proxy address from the manifest of the network you target and check that its implementation matches the manifest. Any consumer contract can request; there is no allowlist, and EOAs cannot request.

Requests and fees: use the D20VRFRequests helpers from the SDK for every randomness option (raw word, dice, custom dice, coin flip, ranges, choose one or many, shuffle). Never hardcode the fee. In a contract, quote with quoteFee(callbackGasLimit) inside the requesting transaction. Off chain, quote with quoteFeeAt(callbackGasLimit, latest block baseFeePerGas) plus a buffer, or the SDK's quoteRequestFee with an ethers v6 provider, because eth_call reports a base fee of 0. Choose one payment pattern deliberately: collect a buffered quote from the user, pay the exact fee from the contract and return the change; or forward msg.value and accept that the excess becomes refund credit of the refund address.

Results: store the delivered word or mapped result in your consumer when the authenticated callback arrives, keyed by request ID, and have the application read that state. Off chain, wait for the coordinator's fulfillment events or poll getRequest (getRoundRequest on Robinhood Chain); the SDK documents the fields and mapped-result readers. A browser app must bundle the ESM package. Delivery usually takes a few seconds; measured Arc Testnet completion times, including under burst load, are at https://d20dao.org/benchmarks (observations of the testnet keepers, not an SLA). On Robinhood Chain a typical delivery takes 5–8 seconds. An unserved request can be refunded strictly after its 60-second deadline at the refund ratio fixed when it was requested. Give refund and callback-retry transactions explicit gas limits as documented.

Keep callbacks small and authenticated through D20VRFConsumer, and never request randomness from inside a callback. An accepted proof with a failed callback is paid service: retry the same stored result, never reroll. Optional _onRefund(requestId) means the refund recipient was paid or credited, not that the consumer received funds, and notification retries must not pay twice. Keep application assets and settlement separate from RNG fees.

For verification, keep the original epoch packet, request and target context and accepted proof evidence. Mathematical replay alone does not prove chain inclusion; check receipts and the implementation history too.

Implement only the requested changes. Do not ask for keeper, VRF, deployer or bot secrets, and do not send funded or mainnet transactions without my explicit authorization. Compile, run lifecycle tests on a local chain or a testnet, then report changed files, results, anything unverified, and any documentation that was unclear or missing.