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 yourrawFulfillRandomWordsexecution. Max2_500_000.commitDelayBlocks(uint16): number of blocks between request and reveal. Min16, max200.numWords(uint32): number of uint256 words to deliver. Max500.useVerifiableEntropy(bool):truefor drand mix (Verifiable),falsefor blockhash-only (Fast). On mainnet,falsereverts withFastModeDisabled().
How it works
Fast mode
- Consumer calls
coordinator.requestRandomWords(req)withuseVerifiableEntropy: false. - Coordinator records the request under
revealBlock = block.number + commitDelayBlocks. - 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. - At
revealBlock, Reactivity callscoordinator.onEvent(...). - The coordinator computes
seed = keccak256(blockhash(revealBlock - 1), requestId, consumer, revealBlock)and derivesnumWordswords. - Words are delivered through
consumer.rawFulfillRandomWords(requestId, words)with the caller-specifiedcallbackGasLimit.
Verifiable mode
Steps 1 through 4 are identical.
- The coordinator captures
blockhash(revealBlock - 1)and fires a Somnia Agents JSON API Request againsthttps://api.drand.sh/public/latest, extracting therandomnessfield. ADrandRequested(requestId, agentRequestId)event is emitted so consumers can link the request to its receipt in the Agent Explorer. - When the validator subcommittee reaches consensus on the drand fetch, the platform calls
coordinator.handleDrandResponse(...). - 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.shand against the on-chainagentRequestIdreceipt. - Consumer callbacks are wrapped in a
try/catchon the coordinator side. A reverting consumer does not stall other requests at the same reveal block; the failure surfaces inRandomWordsFulfilledwithcallbackSuccess = false. - Always guard
rawFulfillRandomWordswithmsg.sender == COORDINATOR. Otherwise anyone could call it with a chosenwords[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