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
Authorize the callback
Reject any callback where msg.sender is not the configured coordinator.
Validate the request ID
Reject callbacks for request IDs that were never created or already settled.
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.