Install

The SDK ships the TypeScript helpers, the Solidity interfaces and a complete worked example. viem is a peer dependency — nothing is bundled.

npm install @diced/sdk viem

For Solidity, copy solidity/ into your project or add a remapping:

@diced/=lib/diced-sdk/solidity/
Start on testnet. It runs the same source as mainnet with its own keeper, so you can exercise the full request-and-reveal path before spending real ETH. Fund a wallet from the faucet, then flip one address to go live.

Solidity interfaces

Two interfaces, depending on where you're coming from. IDicedEntropy is the native surface. IEntropyV2 is byte-compatible with Pyth Entropy V2 — if you already have a Pyth consumer, keep your code and change one address.

Consumers inherit IDicedConsumer, which guards the _entropyCallback(uint64,address,bytes32) selector so only the oracle can deliver a result. That is the same selector Pyth's IEntropyConsumer uses, so the two are interchangeable.

// 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(); surplus is credited back, gas limit 0 = default
        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);
    }
}

This exact contract is compiled and round-tripped against the oracle in the test suite, so it is known to work rather than merely illustrative.

Three rules that matter

  1. Quote the fee at call time. getFeeV2() is a per-provider dial. Never hardcode it.
  2. Keep the callback cheap. It runs under the gas limit you requested. If it reverts, the reveal still happened — your word is retrievable from failedCallbackRandomness(provider, seq).
  3. Supply your own userRandom. The overloads without it derive a contribution in-contract, which weakens the model to "provider and sequencer don't collude".

TypeScript SDK

Every helper auto-detects which oracle generation it is talking to — only V2 answers getDefaultProvider, so one read distinguishes them. Pass version explicitly to skip the probe in hot paths.

import { requestRandomness, waitForCallback, getFee } from '@diced/sdk';

const ORACLE = '0x09aB0B2c0fAC9B3a8F28DEA45C71030F3c8A3B32';

const fee = await getFee(publicClient, ORACLE);

const { sequenceNumber, provider } = await requestRandomness(
  publicClient, walletClient, ORACLE,
);

const { randomNumber, callbackSucceeded } = await waitForCallback(
  publicClient, ORACLE, sequenceNumber, { provider },
);
ExportPurpose
getFee(client, oracle, opts?)Current fee in wei. opts.token prices an ERC-20; opts.provider selects a provider.
requestRandomness(pub, wallet, oracle, opts?)Files a request. Returns { sequenceNumber, txHash, userContribution, provider }.
waitForCallback(client, oracle, seq, opts?)Resolves on reveal. Watches events and backfills once, so a reveal landing before the watcher attaches is never missed.
randomUserContribution()32 cryptographically random bytes for your contribution.
detectVersion(client, oracle)Returns 1 or 2.
abiFor(v), dicedEntropyAbi, dicedEntropyV2AbiRaw ABIs, if you'd rather drive viem yourself.
V2 sequence numbers are per-provider. A bare sequence number is ambiguous across providers — pass provider to waitForCallback whenever you have it.

Agent integration

If you can't reasonably hold ETH on an L3 and deploy a consumer contract — agents, scripts, backends — buy a word over HTTP instead. No wallet setup, no gas, no Solidity.

POST https://x402.diced.fun/x402/v1/random

An unpaid request returns HTTP 402 with the payment terms. Pay by signing an EIP-2612 permit for $0.05 USDG and retrying with an X-PAYMENT header. A successful reply looks like this:

{
  "randomNumber": "0x339d160f5ea5219eaf4b4fd56f6e2a52ece339b1976a65e3b81b5746491ced5a",
  "proof": {
    "oracle":         "0x09aB0B2c0fAC9B3a8F28DEA45C71030F3c8A3B32",
    "sequenceNumber": "4",
    "requestTx":      "0xbb4cf7ea…",
    "revealTx":       "0x5000d278…",
    "revealBlock":    "26224269"
  },
  "payment": { "settlementTx": "0x9bda1a51…", "amount": "50000" }
}
You are not asked to trust the endpoint. The reply carries the sequence number and reveal transaction, so the word can be re-derived from chain data and checked against the provider's pre-committed hash chain. If the endpoint lied, the proof would not match.

Choosing a provider

V2 is multi-provider. Each provider commits its own hash chain, sets its own fee and keeps its own fees; registration is permissionless. Omit the provider argument and you get the default one.

This matters for liveness. A provider cannot change your outcome, but it can withhold one. If that happens your fee is refundable permissionlessly after the refund delay — and you can simply request from a different provider. Read getProviderInfoV2(address) for a provider's fee, chain position and remaining capacity before you commit to it.

Quick reference

Oracle — mainnet0x09aB0B2c0fAC9B3a8F28DEA45C71030F3c8A3B32
Oracle — testnet0x9781059578EA4578952839a1ae6e6Cefe963D796
ChainRobinhood Chain 4663 · testnet 46630
Fee0.00002 ETH per request (per provider)
Typical latency1–3 seconds
Refund delay10 minutes, then permissionless
Callback gas200k default, 2M max
Agent endpointPOST https://x402.diced.fun/x402/v1/random
LicenseApache-2.0

Both oracles are source-verified — read the code you're trusting before you send it anything.