Build / Oracles

Using Verifiable Randomness (VRF)

Somnia VRF is a native VRF coordinator built on Somnia's Reactivity primitive and mixed with drand via Somnia Agents. Consumers pay only their tx gas; the coordinator pays for entropy delivery out of its own balance. No off-chain fulfiller runs behind it.

Two entropy modes share one coordinator:

  • Fast: entropy from blockhash(revealBlock - 1). Cheap and low-latency, but the block producer for the reveal block can influence it. Testnet-grade only.
  • Verifiable: the reveal block's hash is mixed with a drand round pulled through the Somnia Agents JSON API Request agent. Publicly verifiable seed; compromising either drand or the reveal-block producer alone does not compromise the result. The only mode available on mainnet.

The live demo connects your wallet, flips a coin, and shows the whole cycle end-to-end.

Mainnet contracts (chain 5031)

Contract Address
SomniaVRFCoordinator 0x50AAEE5ECE41627F2bc4b64Ad29a4659a5C018cb
CoinFlip (example consumer) 0x0c80964dB885091194F547eaDf95249b74Ba651D
Somnia Agents platform 0x5E5205CF39E766118C01636bED000A54D93163E6

The mainnet coordinator is deployed with FAST_MODE_ENABLED = false; blockhash-only requests revert at request time.

Testnet contracts (chain 50312)

Contract Address
SomniaVRFCoordinator 0x0834459256bbb8d2efee23dc6c3f1722266182dd
CoinFlip (example consumer) 0x9c220e99bf41df220ce54147a0a10a025a151e63
Somnia Agents platform 0x037Bb9C718F3f7fe5eCBDB0b600D607b52706776

Both modes are enabled on the testnet coordinator.

Cost and latency

Every request is paid out of the coordinator's balance, not the consumer's. Consumers only pay the tx gas for requestRandomWords.

Mode Cost per request End-to-end latency
Fast ~0.00067 native ~4 seconds (16-block commit delay)
Verifiable ~0.10 native ~10 seconds (adds drand fetch)

Consumer contract

A consumer only needs two things: the coordinator address and the two selectors it will use (requestRandomWords outbound, rawFulfillRandomWords inbound). No package install, no external repo dependency. The example below is a full, drop-in consumer that just needs the coordinator address at construction:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface ISomniaVRFCoordinator {
    struct RandomWordsRequest {
        uint32 callbackGasLimit;
        uint16 commitDelayBlocks;
        uint32 numWords;
        bool useVerifiableEntropy;
    }
    function requestRandomWords(RandomWordsRequest calldata req)
        external returns (uint256 requestId);
}

contract CoinFlip {
    ISomniaVRFCoordinator public immutable COORDINATOR;

    enum Result { Pending, Heads, Tails }
    mapping(uint256 => Result) public flips;

    event Landed(uint256 indexed requestId, Result result);

    error OnlyCoordinator(address caller);

    constructor(address coordinator) {
        COORDINATOR = ISomniaVRFCoordinator(coordinator);
    }

    /// Fires a Verifiable-mode request. Reveal ~10s later.
    function flipVerifiable() external returns (uint256 requestId) {
        return COORDINATOR.requestRandomWords(
            ISomniaVRFCoordinator.RandomWordsRequest({
                callbackGasLimit: 100_000,
                commitDelayBlocks: 16,
                numWords: 1,
                useVerifiableEntropy: true
            })
        );
    }

    /// Called by the coordinator after the reveal + drand fetch.
    /// The coordinator guarantees msg.sender == COORDINATOR when it calls
    /// this selector, so a single check is all that's needed.
    function rawFulfillRandomWords(uint256 requestId, uint256[] calldata words)
        external
    {
        if (msg.sender != address(COORDINATOR)) revert OnlyCoordinator(msg.sender);
        Result r = (words[0] & 1) == 0 ? Result.Heads : Result.Tails;
        flips[requestId] = r;
        emit Landed(requestId, r);
    }
}

Deploy it against the mainnet coordinator:

forge create CoinFlip.sol:CoinFlip \
  --rpc-url https://api.infra.mainnet.somnia.network \
  --private-key $PRIVATE_KEY \
  --constructor-args 0x50AAEE5ECE41627F2bc4b64Ad29a4659a5C018cb \
  --broadcast --gas-limit 30000000

Trigger a flip and watch the result land on-chain:

# fire the request
cast send $COINFLIP "flipVerifiable()" \
  --rpc-url https://api.infra.mainnet.somnia.network \
  --private-key $PRIVATE_KEY --gas-limit 6000000

# 10 seconds later, read the result (0 = pending, 1 = heads, 2 = tails)
cast call $COINFLIP "flips(uint256)(uint8)" $REQUEST_ID \
  --rpc-url https://api.infra.mainnet.somnia.network

Request shape

  • callbackGasLimit (uint32): max gas for your rawFulfillRandomWords execution. Max 2_500_000.
  • commitDelayBlocks (uint16): number of blocks between request and reveal. Min 16, max 200.
  • numWords (uint32): number of uint256 words to deliver. Max 500.
  • useVerifiableEntropy (bool): true for drand mix (Verifiable), false for blockhash-only (Fast). On mainnet, false reverts with FastModeDisabled().

How it works

Fast mode

  1. Consumer calls coordinator.requestRandomWords(req) with useVerifiableEntropy: false.
  2. Coordinator records the request under revealBlock = block.number + commitDelayBlocks.
  3. If the coordinator does not already have a subscription for revealBlock, it schedules one against the Somnia Reactivity precompile. Multiple requests at the same reveal block share a single subscription.
  4. At revealBlock, Reactivity calls coordinator.onEvent(...).
  5. The coordinator computes seed = keccak256(blockhash(revealBlock - 1), requestId, consumer, revealBlock) and derives numWords words.
  6. Words are delivered through consumer.rawFulfillRandomWords(requestId, words) with the caller-specified callbackGasLimit.

Verifiable mode

Steps 1 through 4 are identical.

  1. The coordinator captures blockhash(revealBlock - 1) and fires a Somnia Agents JSON API Request against https://api.drand.sh/public/latest, extracting the randomness field. A DrandRequested(requestId, agentRequestId) event is emitted so consumers can link the request to its receipt in the Agent Explorer.
  2. When the validator subcommittee reaches consensus on the drand fetch, the platform calls coordinator.handleDrandResponse(...).
  3. The coordinator computes seed = keccak256(drand, blockhash, requestId, consumer, revealBlock) and delivers words.

If the drand fetch fails on-chain (network partition, consensus timeout, round rollover), the coordinator marks the request fulfilled with callbackSuccess = false in RandomWordsFulfilled, so consumers can retry without stalling the queue.

Bounds and defaults

Parameter Bound Reason
commitDelayBlocks 16 min, 200 max Long enough to raise the cost of a single-validator grinding attack; short enough to keep blockhash inside the 256-block EVM lookback.
callbackGasLimit 2,500,000 max Coordinator-side ceiling so one bad consumer cannot stall the reveal block.
numWords 1 to 500 Each word costs one keccak256 at fulfillment.

Security notes

  • Fast mode is testnet-grade. Entropy is blockhash(revealBlock - 1), which the reveal-block producer can influence. Use it for testing, dev tooling, and games where the outcome is not high-value. The mainnet coordinator rejects this mode at request time.
  • Verifiable mode is trust-minimized. drand's League of Entropy is an eight-organisation threshold BLS committee with roughly five years of uptime; a 4-of-8 drand compromise plus Somnia validator collusion at the same reveal block is the joint attack. The coordinator does not cryptographically verify the BLS signature on-chain (that would require a BLS precompile), but any observer can independently check the drand round against api.drand.sh and against the on-chain agentRequestId receipt.
  • Consumer callbacks are wrapped in a try/catch on the coordinator side. A reverting consumer does not stall other requests at the same reveal block; the failure surfaces in RandomWordsFulfilled with callbackSuccess = false.
  • Always guard rawFulfillRandomWords with msg.sender == COORDINATOR. Otherwise anyone could call it with a chosen words[0] and forge the result.

Try it live

The showcase has a chain toggle for testnet and mainnet, connects your wallet, and runs the CoinFlip contract end-to-end. Verifiable-mode reveals include a link to the drand fetch's receipt in the Agent Explorer.

Copied from docs.somnia.network (somnia-main-docs) by scripts/import-gitbook.mts, until it finds a home in a product's docs.Edit this page