Skip to main content
Use this pattern as the starting point for most Solidity integrations against the hosted roll.codes coordinator.

What this example shows

  • requesting one random number
  • storing request metadata keyed by requestId
  • authenticating the callback caller
  • rejecting unknown or already-settled requests

Minimal consumer

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

import {IVRFSystem} from "@rollcodes/interfaces/IVRFSystem.sol";
import {IVRFSystemCallback} from "@rollcodes/interfaces/IVRFSystemCallback.sol";

contract ExampleConsumer is IVRFSystemCallback {
    error Unauthorized();
    error RequestNotFound(uint256 requestId);
    error AlreadySettled(uint256 requestId);

    IVRFSystem public immutable vrfSystem;

    struct Pending {
        address player;
        bool settled;
        uint256 randomNumber;
    }

    mapping(uint256 => Pending) public pendingByRequestId;

    constructor(address coordinatorAddress) {
        vrfSystem = IVRFSystem(coordinatorAddress);
    }

    function play() external payable returns (uint256 requestId) {
        uint256 fee = vrfSystem.requestFee();
        require(msg.value == fee, "fee mismatch");

        uint256 traceId = 0;
        requestId = vrfSystem.requestRandomNumberWithTraceId{ value: fee }(traceId);

        pendingByRequestId[requestId] = Pending({
            player: msg.sender,
            settled: false,
            randomNumber: 0
        });
    }

    function randomNumberCallback(uint256 requestId, uint256 randomNumber) external override {
        if (msg.sender != address(vrfSystem)) revert Unauthorized();

        Pending storage p = pendingByRequestId[requestId];
        if (p.player == address(0)) revert RequestNotFound(requestId);
        if (p.settled) revert AlreadySettled(requestId);

        p.settled = true;
        p.randomNumber = randomNumber;

        // settle your game or app logic here
    }
}

Required guards

1

Authorize the callback

Reject any callback where msg.sender is not the configured coordinator.
2

Validate the request ID

Reject callbacks for request IDs that were never created or already settled.
3

Keep settlement deterministic

Persist state and derive outcomes without unnecessary branches or external calls.

Why the request is stored after creation

The request and callback happen in separate transactions. As soon as requestRandomNumberWithTraceId returns, store the metadata you will need later to settle the request. Typical fields include:
  • player or account
  • match or session ID
  • user choice or wager
  • settled flag

Implementation notes

Derive bounded outcomes such as dice rolls inside the callback from the delivered randomNumber. Do not try to precompute outcomes before the request exists.
Same-round deliveries share the same base randomNumber. If you need distinct values per request inside that window, derive them locally, for example with uint256(keccak256(abi.encodePacked(requestId, randomNumber))).
If your callback reverts, delivery fails and the request will not be marked as settled in your consumer. Keep the callback small enough to succeed reliably.

Next steps

Consumer patterns

See common mappings from request IDs to users, rounds, and outcomes.

Coordinator reference

Look up the exact function signatures and config fields used in this example.

Callback security

Review the callback guardrails before adapting the example to production logic.

Troubleshooting

Diagnose fee mismatches, unauthorized callbacks, and pending requests.
Last modified on March 9, 2026