Quickstart
A consumer contract needs three things: forward the fee with a request, inherit the consumer base, and implement one callback. Complete working example:
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;
import {IDicedEntropy} from "diced/IDicedEntropy.sol";
import {IDicedConsumer} from "diced/IDicedConsumer.sol";
contract CoinFlip is IDicedConsumer {
IDicedEntropy public immutable entropy;
event FlipResult(uint64 indexed seq, bool heads);
constructor(address entropy_) { entropy = IDicedEntropy(entropy_); }
function flip(bytes32 userRandom) external payable returns (uint64 seq) {
// send at least getFeeV2(); any surplus is credited back to you
seq = entropy.request{value: msg.value}(userRandom, 0);
}
function getEntropy() internal view override returns (address) {
return address(entropy);
}
function entropyCallback(uint64 seq, address, bytes32 randomNumber) internal override {
emit FlipResult(seq, uint256(randomNumber) & 1 == 0);
}
}
Map the 32-byte word onto your outcome space with modulo arithmetic
(uint256(randomNumber) % sides). One word is enough for many sub-outcomes โ
slice bits or re-hash with a domain separator.
userRandom client-side with
crypto.getRandomValues per request. It's your half of the entropy: the provider
committed its chain before your request, and you commit your contribution before the reveal.
Addresses
| What | Value |
|---|---|
| Oracle โ mainnet | 0x09aB0B2c0fAC9B3a8F28DEA45C71030F3c8A3B32DicedEntropyV2 ยท source-verified ยท chain 4663 |
| Oracle โ testnet | 0x9781059578EA4578952839a1ae6e6Cefe963D796same source, own keeper ยท chain 46630 ยท faucet |
| Agent endpoint | POST https://x402.diced.fun/x402/v1/randomHTTP 402, $0.05 USDG per word |
| RPC | https://rpc.mainnet.chain.robinhood.comtestnet: https://rpc.testnet.chain.robinhood.com |
| Explorer | robinhoodchain.blockscout.com testnet: explorer.testnet.chain.robinhood.com |
| Legacy oracle (V1) | 0xc934f1c9e2A9CDdc19Ed2E4a1F88DC8d24EA48eEstill served, for consumers that hardcoded it โ don't build on it |
API reference
Requesting
| Function | Notes |
|---|---|
request(bytes32 userRandomNumber, uint32 callbackGasLimit) payable โ uint64 |
V1-era entry point, kept for compatibility. On V2 prefer requestV2: it takes at least the fee and credits any surplus back to you, so a fee change cannot strand a request in flight. Gas limit 0 = provider default (200k), capped by the on-chain max (2M). Returns your sequence number. |
requestV2() payable โ uint64requestV2(uint32) payable โ uint64 |
Convenience overloads with an auto-derived user contribution (a salt โ fine when you just need "unpredictable to everyone but the provider", weaker than supplying your own). |
requestWithToken(address token, bytes32 userRandomNumber, uint32 callbackGasLimit) โ uint64 |
Pay the fee in an enabled ERC-20 (WETH at launch). Approve first; exact amount pulled, fee-on-transfer tokens rejected. |
Reading
| Function | Notes |
|---|---|
getFee() โ uint128 / getFee(address token) โ uint96 | Current fee. Always read at call time โ don't hardcode. |
failedCallbackRandomness(uint64) โ bytes32 | Your word, if your callback reverted (see below). Zero otherwise. |
requests(uint64) | Pending request struct; zeroed once revealed or refunded. |
Refunds
refundRequest(uint64 seq) โ callable by anyone once a request has been
pending longer than the refund delay (10 minutes at launch). The fee returns to the original
requester in the original token, always. Native-fee consumers must be able to receive ETH
(have a receive()), or use WETH.
Events
event Requested(uint64 indexed sequenceNumber, address indexed requester,
address feeToken, uint96 feePaid, uint32 callbackGasLimit, bytes32 userContribution);
event Revealed(uint64 indexed sequenceNumber, address indexed requester,
bytes32 randomNumber, bool callbackSucceeded);
event CallbackFailed(uint64 indexed sequenceNumber, address indexed requester,
bytes32 randomNumber, bytes reason);
event Refunded(uint64 indexed sequenceNumber, address indexed requester,
address feeToken, uint96 amount);
The callback
- Only the oracle can invoke
_entropyCallback; theIDicedConsumerbase enforces it. You override the internalentropyCallback. - It runs under your requested gas limit. Keep it lean; do heavy work in a later user transaction.
- A revert in your callback does not undo the reveal. The word is stored in
failedCallbackRandomness(seq)and aCallbackFailedevent fires โ build a self-serve path if your logic can revert. - Reveals can arrive in any order relative to other requests. Key your state by sequence number, never by "latest".
Fees & refunds lifecycle
- Request: fee escrows inside the oracle, tied to your request.
- Reveal: escrow becomes operator revenue โ only now.
- No reveal: after the delay, anyone triggers your refund; escrow returns to you in full.
The operator cannot withdraw escrow of pending requests โ the accounting makes it impossible, not just impolite.
TypeScript
import { getFee, requestRandomness, waitForCallback } from '@diced/sdk';
const fee = await getFee(publicClient, ENTROPY);
const { sequenceNumber } = await requestRandomness(publicClient, walletClient, ENTROPY);
const { randomNumber } = await waitForCallback(publicClient, ENTROPY, sequenceNumber);
The SDK ships viem-based helpers plus the Solidity interfaces. Contact us for access while the repository is private.
Migrating from other entropy oracles
The consumer surface is deliberately Pyth-Entropy-shaped: requestV2(), the
_entropyCallback(uint64, address, bytes32) selector, fee-forwarding semantics.
If your contract already consumes a Pyth-style entropy service, migration is: point it at the
diced.fun oracle address. The only behavioral differences to review are the flat (not
gas-scaled) fee, the exact-msg.value rule, and the refund flow above.
Trust model
What is cryptographically guaranteed, and what you still take on trust. No hand-waving.
Guaranteed
- No grinding. The provider's hash chain is committed on-chain before your request; your contribution lands before the reveal. The word is
keccak256(providerReveal, yourContribution, seq, you)โ neither side alone chooses it. - Verifiability. Every reveal is keccak-verified on-chain against the commitment; anyone can recompute every delivered word from public data.
- Isolation. Other consumers' reverting or gas-hungry callbacks cannot affect your delivery.
- Refundability. An unrevealed request is always refundable, permissionlessly. Funds cannot be stuck.
Taken on trust
- Withholding. The provider knows future reveals and could decline to publish one, converting an outcome into a refund. It cannot substitute a different outcome. Design your application so refund-instead-of-delivery is an acceptable worst case.
- Provider participation. Knowing its future reveals, the provider could act as a user of your downstream application with a chosen contribution. Relevant only if you don't trust the operator; visible on-chain if it happens; not cryptographically prevented in any single-provider design.
- Liveness. One keeper serves reveals (1โ3s typical). Downtime means delayed delivery, then refunds โ never stuck funds.