Hybrid VRF Contract
Selective verifiable randomness with AutoLoopHybridVRFCompatible — cheap standard ticks with VRF only when you need it.
Overview
AutoLoopHybridVRFCompatible is the sweet spot for most on-chain games. Standard ticks run at minimal gas cost (~90k). VRF ticks (~240k gas) fire only when your contract requests randomness — for loot drops, critical hits, spawns, or any random event.
Best for: RPGs, roguelikes, loot systems, any game that needs occasional randomness but not on every tick.
How It Works
- Your contract overrides
_needsVRF(loopID)to decide when VRF is needed shouldProgressLoop()encodes(needsVRF, loopID, gameData)— the worker reads the flag- On standard ticks: worker sends data as-is, contract calls
_onTick()(~90k gas) - On VRF ticks: worker wraps data in a VRF envelope with an ECVRF proof, contract verifies the proof and calls
_onVRFTick()(~240k gas)
Integration
import "@luckymachines/autoloop/src/AutoLoopHybridVRFCompatible.sol";
contract MyHybridGame is AutoLoopHybridVRFCompatible {
uint256 public score;
uint256 public lastRoll;
uint256 public interval;
uint256 public lastTimeStamp;
uint256 private _loopID;
constructor(uint256 _interval) {
interval = _interval;
lastTimeStamp = block.timestamp;
}
// When to request VRF (e.g., every 10th tick)
function _needsVRF(uint256 loopID)
internal view override returns (bool)
{
return loopID % 10 == 0;
}
// Readiness check
function _shouldProgress()
internal view override
returns (bool ready, bytes memory gameData)
{
ready = (block.timestamp - lastTimeStamp) > interval;
gameData = abi.encode(score);
}
// Standard tick — cheap, no randomness
function _onTick(bytes memory gameData) internal override {
lastTimeStamp = block.timestamp;
++score;
++_loopID;
}
// VRF tick — includes verified randomness
function _onVRFTick(bytes32 randomness, bytes memory gameData)
internal override
{
lastRoll = (uint256(randomness) % 6) + 1;
lastTimeStamp = block.timestamp;
score += lastRoll;
++_loopID;
}
}Key Functions
_needsVRF(uint256 loopID) → bool
Override this to control when VRF randomness is requested. The worker reads this flag from shouldProgressLoop() output.
Examples:
return loopID % 10 == 0;— VRF every 10th tickreturn loopID % 5 == 0;— VRF every 5th tickreturn true;— VRF on every tick (equivalent to full VRF)
_shouldProgress() → (bool ready, bytes memory gameData)
Override with your timing/readiness logic. Returns whether the loop is ready and any game-specific data to pass through.
_onTick(bytes memory gameData)
Called on standard ticks (when _needsVRF() returns false). This is your cheap game logic — movement, timers, state updates.
_onVRFTick(bytes32 randomness, bytes memory gameData)
Called on VRF ticks with verified randomness. Use randomness for loot tables, dice rolls, critical hits, spawn locations, etc.
ERC-165 Interface
Workers auto-detect hybrid VRF contracts via ERC-165:
bytes4 constant HYBRID_VRF_INTERFACE_ID =
bytes4(keccak256("AutoLoopHybridVRFCompatible"));Data Format
The shouldProgressLoop() return value encodes:
abi.encode(bool needsVRF, uint256 loopID, bytes gameData)For VRF ticks, the worker wraps this in a VRF envelope (>= 640 bytes). The contract detects the format by data size and routes accordingly.
Events
| Event | When |
|---|---|
StandardTick(uint256 indexed loopID, uint256 timestamp) | On standard (non-VRF) ticks |
HybridVRFTick(uint256 indexed loopID, bytes32 randomness, uint256 timestamp) | On VRF ticks |
Gas Cost
| Tick Type | Gas | Cost (2026) |
|---|---|---|
| Standard tick | ~90,000 | ~$0.008 |
| VRF tick | ~240,000 | ~$0.022 |
| Average (VRF every 10th) | ~105,000 | ~$0.010 |
Hybrid VRF is 56% cheaper than full VRF and only 16% more than standard — the ideal balance for games with occasional randomness.
No Chainlink Equivalent
Hybrid VRF is unique to AutoLoop. Chainlink has no equivalent — you would need to manually coordinate Keepers + VRF v2.5 with separate subscriptions and LINK balances, and there is no way to selectively request VRF on certain ticks.