Skip to content
D20DAODocs
Keeper LabExplorerStatusGitHub

BUILD / GUIDE

Integration

Request general-purpose randomness and receive an authenticated callback.

Read the repository reference
01

Consumer contract

Extend D20VRFConsumer with the verified coordinator proxy address. Its base authenticates callbacks. The local example below tracks outstanding request IDs and stores the delivered word. Keep delivery small and apply application actions separately. The same consumer works on Arc and Robinhood Chain. Find the testnet addresses in the Deployed contracts guide and verify the active implementation before connecting a consumer.

RandomApp.solSolidity / 26 lines
// SPDX-License-Identifier: MITpragma solidity 0.8.28;import {D20VRFConsumer} from "./protocol/D20VRFConsumer.sol";import {ID20VRF} from "./protocol/interfaces/ID20VRF.sol";contract RandomApp is D20VRFConsumer {    ID20VRF public immutable rng;    mapping(uint256 => bytes32) public results;    mapping(uint256 => bool) private requested;    constructor(address coordinator) D20VRFConsumer(coordinator) {        rng = ID20VRF(coordinator);    }    function request(bytes32 input) external payable returns (uint256 id) {        require(msg.value >= rng.quoteFee(100_000), "Fee below quote");        id = rng.requestRandomness{value: msg.value}(input, 100_000, msg.sender);        requested[id] = true;    }    function _fulfillRandomness(uint256 id, bytes32 word) internal override {        require(requested[id], "Unknown request");        delete requested[id];        results[id] = word;    }}
Authenticated callback. Exact request fee.View source
02

Request and fee

The fee is max(minFee, feeMultiplier × baseFee × (fulfillGasOverhead + callbackGasLimit)); pricing() returns the live parameters. A consumer contract pays quoteFee(callbackGasLimit) inside the requesting transaction, which is exact. An off-chain sender quotes quoteFeeAt(callbackGasLimit, latestBlock.baseFeePerGas) plus a buffer because eth_call usually reports a base fee of 0; the SDK's quoteRequestFee does this. requestRandomness(clientSeed, callbackGasLimit, refundAddress) reverts with IncorrectFee below the quote, and any excess becomes refund credit for the refund address, withdrawn with withdrawRefundCredit. The fee is paid in the native token: USDC on Arc, ETH on Robinhood Chain. On Arc, the request checkpoints its epoch's canonical anchor, escrows the charged fee (requestFeePaid) and refund ratio (requestRefundBps) and pins its epoch ID, consumer, client seed, mapping, request block and fixed refund recipient. It can be accepted before its epoch packet is published. Until publication, the epoch hash and randomness target are unresolved. An absent packet or unaccepted proof can lead to expiry; there is no automatic refund or guaranteed delivery SLA. On Robinhood Chain, the request binds its drand round at once (RoundAssigned) and escrows the same fields; there is no publication step.

03

Result handling

On Arc, after publication, the target block is max(requestBlock, epochCommitBlock + 1). Its hash becomes part of the fixed VRF input, so the packet is committed before that hash is known. Track the request until proof acceptance and callback delivery are separately established. Accepted proof earns the configured keeper and treasury fee shares even if the callback fails. Retry delivery with the stored word; do not request a replacement outcome for callback failure. After expiry without accepted proof, claim the refundable share of the escrowed fee by transaction for the fixed refund recipient. To wait for a result, read requestId from the RandomnessRequested log in the request receipt, then poll getRequest (getRoundRequest on Robinhood Chain): read the latest block first, stop when fulfilled is true (then read getMappedResult or your consumer's stored value), and treat the request as expired once that block's timestamp is past deadline. Choose polling intervals and loading states from the measured completion times at https://d20dao.org/benchmarks (Arc Testnet, a primary and a backup keeper, isolated and burst load, reproducible with https://github.com/d20dao/benchmarks), but treat only the 60-second deadline as a rule. On Robinhood Chain a typical delivery takes 5–8 seconds. Give recovery transactions explicit gas limits: refundRequest 400,000; retryCallback the callback gas limit plus 250,000; retryRefundCallback the gas limit plus 150,000.

04

Best practices

The SDK README explains each rule.

  1. Quote with quoteFee inside the requesting transaction; off-chain, use quoteFeeAt with the latest base fee plus a buffer, never eth_call.
  2. Request from a contract; a wallet call reverts with ContractConsumerRequired.
  3. Keep the callback small: store the word and do the work later. retryCallback redelivers the same word.
  4. Record each request ID with its context, and reject unknown or finished IDs in the callback.
  5. Derive many values from one word: a diceRoll count, chooseMany, shuffle or keccak256(abi.encode(word, i)).
  6. Handle expiry, and choose a refund address that can receive the native token or call withdrawRefundCredit.
  7. Never re-roll a result you dislike; the word is final once fulfilled is true.
  8. Close bets and entries when you request: the pending fulfillment reveals the word about one block before it lands.
  9. Freeze any list before requesting an index into it, for example by hashing it into clientSeed.
  10. Withdraw leftover refund credit, or return the change in the requesting transaction.
  11. Never use blockhash or block.timestamp as randomness.
  12. On Robinhood Chain: Send requests as normal transactions through the sequencer. A request forced through Ethereum's delayed inbox takes an earlier timestamp and may bind a round that is already public.
05

Browser applications

Bundle the ESM package with Vite, webpack or esbuild. quoteRequestFee expects an ethers v6 provider; with viem, read the latest block's baseFeePerGas, add a buffer and call quoteFeeAt. Add Arc to a wallet with wallet_addEthereumChain: chain ID 0x13b2 (5042) or 0x4cef52 (5042002), native currency USDC with 18 decimals, and the RPC and explorer URLs from the deployment manifest. Robinhood Chain is 0x1237 (4663) and its testnet 0xb626 (46630), native currency ETH with 18 decimals. Common ad-block lists block *.arc.io, so public Arc RPC reads can fail in visitors' browsers (net::ERR_BLOCKED_BY_CLIENT); read through the connected wallet or a same-origin read-only relay, as github.com/d20dao/randomizer-demo does. Decode coordinator reverts with coordinatorAbi from @d20dao/vrf-sdk/abi. In ethers v6, a struct field named like an array member, such as values or length, must be read by position, getValue(name) or toObject().

06

SDK and agent resources

Install @d20dao/vrf-sdk from npm. Start with the packaged AGENTS.md and the Getting started guide, or copy the task prompt below into your agent. Additional integration skills live at github.com/d20dao/skills. API.md in the package (github.com/d20dao/d20-sdk/blob/main/API.md) lists every coordinator and registry function, event and error with its caller and what to do about each error. github.com/d20dao/randomizer-demo is a complete dapp that uses every randomness option (live at mainnet-demo.d20dao.org). Independently verify chain, proxy, registry, implementation history, public key and configuration. A stable proxy address does not guarantee unchanged behavior.

Work with your agents

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

 

View prompt
Implement a D20DAO consumer in my existing application. Preserve its access controls and payment model; associate every request ID with one application operation and store authenticated results.

Read these public references first; do not guess signatures, addresses or limits:
- This guide: https://d20dao.org/docs/integration (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.