Full VRF Contract
Verifiable randomness on every tick with AutoLoopVRFCompatible — provably fair randomness for dice games, lotteries, and more.
Overview
AutoLoopVRFCompatible provides ECVRF (Elliptic Curve Verifiable Random Function) proof generation and on-chain verification on every tick. The worker generates a cryptographic proof off-chain, and the contract verifies it on-chain before exposing the random value.
Best for: Dice games, lotteries, coin flips, any game that needs provably fair randomness on every single tick.
How VRF Works
- Your contract returns
shouldProgressLoop() => truewith the current_loopID - The worker detects VRF support via ERC-165 (
supportsInterface) - The worker generates an ECVRF proof off-chain using its private key and a deterministic seed
- The proof is wrapped around your game data and submitted to
progressLoop() VRFVerifier.solverifies the proof on-chain and outputs abytes32random value
The seed is deterministic: keccak256(contractAddress, loopID). Controllers cannot choose or manipulate seeds.
Integration
import "@luckymachines/autoloop/src/AutoLoopVRFCompatible.sol";
contract MyVRFGame is AutoLoopVRFCompatible {
uint256 public lastRoll;
uint256 public interval;
uint256 public lastTimeStamp;
uint256 private _loopID;
constructor(uint256 _interval) {
interval = _interval;
lastTimeStamp = block.timestamp;
}
function shouldProgressLoop()
external view override
returns (bool loopIsReady, bytes memory progressWithData)
{
loopIsReady = (block.timestamp - lastTimeStamp) > interval;
progressWithData = abi.encode(_loopID);
}
function progressLoop(bytes calldata progressWithData) external override {
(bytes32 randomness, bytes memory gameData) =
_verifyAndExtractRandomness(progressWithData, tx.origin);
uint256 loopID = abi.decode(gameData, (uint256));
require(loopID == _loopID, "stale");
lastRoll = (uint256(randomness) % 6) + 1;
lastTimeStamp = block.timestamp;
++_loopID;
}
}Register Controller Key
Before a VRF contract accepts proofs from a controller, the controller's public key must be registered:
myVRFGame.registerControllerKey(controllerAddress, pkX, pkY);Only the controller itself or a contract admin can call this.
VRF Envelope Format
The worker wraps the original progressWithData in a VRF envelope:
abi.encode(
uint8 vrfVersion, // 1 = ECVRF-SECP256K1-SHA256-TAI
uint256[4] proof, // [gamma_x, gamma_y, c, s]
uint256[2] uPoint, // precomputed for fastVerify
uint256[4] vComponents, // precomputed for fastVerify
bytes gameData // original progressWithData
)Key Functions
_verifyAndExtractRandomness(bytes calldata, address controller)
Verifies the VRF proof and extracts the random value. Returns (bytes32 randomness, bytes memory gameData).
registerControllerKey(address, uint256 pkX, uint256 pkY)
Register a controller's secp256k1 public key. Required before the controller can submit VRF proofs.
computeSeed(uint256 loopID) → bytes
Computes the deterministic seed: keccak256(address(this), loopID).
Gas Cost
| Metric | Value |
|---|---|
| Median gas per tick | ~240,000 |
| Cost per tick (2026) | ~$0.022 |
| 10-min game @ 1 tick/sec | ~$13.38 |
VRF adds ~150k gas overhead for on-chain elliptic curve operations. If you don't need randomness on every tick, consider Hybrid VRF to cut costs by ~56%.