Verified Random Number Generator (vRNG)

Verified Random Number Generator (vRNG)

ApeChain vRNG

ApeChain vRNG is a native on-chain random number generator designed for developers who need fair, unbiased, and cryptographically secure randomness. It enables smart contracts to request tamper-proof random numbers on-chain, suitable for NFT minting, gaming, lotteries, simulations, and any use case where provable fairness matters.

Your contract requests a random value and pays a small $APE fee; the value is delivered to a callback and can be independently verified on-chain. No external oracle network, no off-chain subscription — randomness is produced and served natively.

Contract Addresses

ApeChain (mainnet) — chain ID 33139

FieldValue
Chain ID33139
Explorerhttps://apescan.io
Native token$APE
ContractAddress (proxy)
NativeVRNG (entry point)0xB9B0d73104BE5e286142258204AA497435a97415
RandomBeaconHistory0x031a84F81a9505E48624936c274f4DfD497676Ae
FeeManager0x2125EA37840c7f15d6b5D352049329a79e90803D

ApeChain Curtis (testnet) — chain ID 33111

FieldValue
Chain ID33111
Explorerhttps://curtis.apescan.io
Native token$APE
ContractAddress (proxy)
NativeVRNG (entry point)0xA899846c23c6af77Da3DA16C8c6137746a460449
RandomBeaconHistory0x63B964F4Bd9589A05FB8C86B9e23A859ec064846
FeeManager0x381Ad148F370188CA76B245897BCb8aEc319B432

Usage

Integrate ApeChain Native vRNG into a consumer contract in three steps. This is the quickstart — see the repo docs ↗ (opens in a new tab) for deeper detail on each step.

Prerequisites: intermediate Solidity, a deployed NativeVRNG address (see Contract Addresses above), and some $APE to pay the per-request fee.

The model

vRNG is request → callback (asynchronous), like Chainlink VRF or Pyth Entropy:

  1. Your contract requests randomness and pays a fee.
  2. The protocol fulfils the request with the first beacon written after it.
  3. The protocol calls your contract back with the random value.

Your contract therefore needs to do two things: send a request, and implement a callback.

Step 1 - Import the interface

NativeVRNG implements IVRNGProvider. Copy that interface from Interfaces & ABIs ↗ (opens in a new tab) into your project, import it, and store the provider address — 0xB9B0d73104BE5e286142258204AA497435a97415 on ApeChain mainnet.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
 
import { IVRNGProvider } from "./interfaces/IVRNGProvider.sol"; // copy from docs → Interfaces & ABIs
 
contract MyGame {
    IVRNGProvider public immutable vrng;
 
    constructor(address vrngProvider) {
        vrng = IVRNGProvider(vrngProvider);
    }
}

Step 2 - Request randomness

Read the fee with getFee, then call requestRandomness forwarding that fee. Store the returned requestId so you can recognize the request when it comes back.

mapping(bytes32 => address) public requesterOf;
 
function roll(bytes32 userCommitment) external payable returns (bytes32 requestId) {
    uint256 fee = vrng.getFee(address(this));
    require(msg.value >= fee, "fee too low");
 
    requestId = vrng.requestRandomness{ value: fee }(userCommitment);
    requesterOf[requestId] = msg.sender;
    // refund msg.value - fee if you collected extra
}
  • userCommitment is your own entropy (e.g. keccak256(player, salt)), mixed into the derivation — it does not need to be secret for the instant flow.
  • requestId uniquely identifies this request. Details: Requesting randomness ↗ (opens in a new tab).

Step 3 - Implement the callback

The protocol calls fulfillRandomness(requestId, randomValue) on the contract you requested from. Guard it so only NativeVRNG can call it, then use the value.

function fulfillRandomness(bytes32 requestId, bytes32 randomValue) external {
    require(msg.sender == address(vrng), "only vRNG");
 
    address player = requesterOf[requestId];
    require(player != address(0), "unknown request");
    delete requesterOf[requestId];
 
    uint256 outcome = uint256(randomValue) % 100; // map to your range
    // ... apply outcome ...
}

That's the whole integration. Keep the callback cheap and non-reverting — see Receiving randomness ↗ (opens in a new tab) for why and how.

Full working example

See examples/lootbox.md ↗ (opens in a new tab) for a complete, annotated consumer (LootBoxDemo) that implements both the instant and commit-reveal flows, handles fee refunds, and derives a rarity tier from the random value.

Next

Supra dVRF

Supra dVRF is a distributed and cryptographically verifiable randomness service and an alternative to ApeChain vRNG (opens in a new tab). It enables smart contracts to request tamper-proof random numbers on-chain — suitable for NFT minting, gaming, lotteries, and any use case where provable fairness matters.

Contract addresses for ApeChain Mainnet and Testnet are listed on the Supra dVRF Available Networks ↗ (opens in a new tab) page.

Dev Guides

Advanced Example (with Custom Seed)

This example demonstrates requesting multiple random numbers with a custom client seed for additional entropy, and mapping results back to individual users.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
 
interface ISupraRouter {
    function generateRequest(
        string memory _functionSig,
        uint8 _rngCount,
        uint256 _numConfirmations,
        uint256 _clientSeed,          // Optional: provide your own seed for extra entropy
        address _clientWalletAddress
    ) external returns(uint256);
}
 
contract Interaction {
    address supraAddr;
 
    constructor(address supraSC) {
        // Pass the ApeChain Mainnet Router address:
        // 0xf4966137dE4638Baa280ea35265B3AFEc5332df3
        supraAddr = supraSC;
    }
 
    mapping(uint256 => string) result;
    mapping(string => uint256[]) rngForUser;
 
    function exampleRequest(uint8 rngCount, string memory username) external {
        // Number of block confirmations before randomness is generated.
        // You can customize this value to meet your needs. Minimum: 1, Maximum: 20.
        uint256 numConfirmations = 1;
 
        uint256 nonce = ISupraRouter(supraAddr).generateRequest(
            "exampleCallback(uint256,uint256[])", // Must match callback signature exactly
            rngCount,
            numConfirmations,
            123,        // Custom client seed — replace with your own value (e.g. timestamp, UUID)
            msg.sender  // Must be your whitelisted wallet address
        );
 
        // Map the nonce to the username so the callback knows who to assign results to
        result[nonce] = username;
    }
 
    // Supra Router calls this automatically once randomness is ready
    function exampleCallback(uint256 nonce, uint256[] calldata rngList) external {
        // Security: only the Supra Router is allowed to call this function
        require(msg.sender == supraAddr, "only supra router can call this function");
 
        uint256[] memory x = new uint256[](rngList.length);
        rngForUser[result[nonce]] = x;
 
        for(uint8 i = 0; i < rngList.length; i++) {
            // Example: normalise each result to a value between 0–99
            rngForUser[result[nonce]][i] = rngList[i] % 100;
        }
    }
 
    // View the random results assigned to a username
    function viewUserName(string memory username) external view returns (uint256[] memory) {
        return rngForUser[username];
    }
}