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.

Generate 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.

Without a contract

You can request from an ordinary account, which is what the demo on the home page does. Send requestV2() to the oracle with getFeeV2() attached, then read the word out of the Revealed event rather than a callback — an EOA has no code to call back into, so the oracle's delivery call is a harmless no-op and callbackSucceeded comes back true.

// 1. fee, 2. request, 3. read the word off the event
const fee = await entropy.read.getFeeV2();
const hash = await entropy.write.requestV2({ value: fee });
const receipt = await client.waitForTransactionReceipt({ hash });

// sequenceNumber is topic 2 of Requested; watch Revealed for the same one
const logs = await client.getLogs({
  address: ORACLE, fromBlock: receipt.blockNumber,
  event: parseAbiItem('event Revealed(address indexed provider, uint64 indexed sequenceNumber, address indexed requester, bytes32 randomNumber, bool callbackSucceeded)'),
  args: { sequenceNumber: seq },
});
const word = logs[0].args.randomNumber;
This path gives up the callback, not the verifiability. The word is still keccak256(reveal, yourContribution, provider, seq, you) and still checked against the provider's commitment on-chain before it is emitted. But requestV2() with no arguments derives your contribution automatically — pass your own via requestV2(provider, userRandomNumber, gasLimit) if you want to prove you chose it. Anything settling real value should use a consumer contract so the result arrives atomically.

Addresses

WhatValue
Oracle — mainnet0x09aB0B2c0fAC9B3a8F28DEA45C71030F3c8A3B32
DicedEntropyV2 · source-verified · chain 4663
Oracle — testnet0x9781059578EA4578952839a1ae6e6Cefe963D796
same source, own keeper · chain 46630 · faucet
Agent endpointPOST https://x402.diced.fun/x402/v1/random
HTTP 402, $0.05 USDG per word
RPChttps://rpc.mainnet.chain.robinhood.com
testnet: https://rpc.testnet.chain.robinhood.com
Explorerrobinhoodchain.blockscout.com
testnet: explorer.testnet.chain.robinhood.com
$DICED token0x75115dFa63bbC1c1812F9c2ad8bC5ea77519D524
official token · ERC-20, 18 decimals · chain 4663 (mainnet only)
Legacy oracle (V1)0xc934f1c9e2A9CDdc19Ed2E4a1F88DC8d24EA48eE
still served, for consumers that hardcoded it — don't build on it

API reference

Requesting

FunctionNotes
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 → uint64
requestV2(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 provider, 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. The leading provider is a V2 addition — V1's three-argument form does not exist here.

Reading

FunctionNotes
getFee() → uint128 / getFee(address token) → uint96Current fee. Always read at call time — don't hardcode.
failedCallbackRandomness(address provider, uint64) → bytes32Your word, if your callback reverted (see below). Zero otherwise. Keyed by provider on V2 — the one-argument V1 form does not exist here.
getRequestV2(address provider, uint64) → RequestPending 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

Fees & refunds lifecycle

  1. Request: fee escrows inside the oracle, tied to your request.
  2. Reveal: escrow becomes operator revenue — only now.
  3. 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

Taken on trust

Pending ≠ secret. Once any later sequence number is revealed, earlier chain elements are publicly derivable. Treat an undelivered outcome as unresolved, not as hidden information.