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
| Field | Value |
|---|---|
| Chain ID | 33139 |
| Explorer | https://apescan.io |
| Native token | $APE |
| Contract | Address (proxy) |
|---|---|
NativeVRNG (entry point) | 0xB9B0d73104BE5e286142258204AA497435a97415 |
RandomBeaconHistory | 0x031a84F81a9505E48624936c274f4DfD497676Ae |
FeeManager | 0x2125EA37840c7f15d6b5D352049329a79e90803D |
ApeChain Curtis (testnet) — chain ID 33111
| Field | Value |
|---|---|
| Chain ID | 33111 |
| Explorer | https://curtis.apescan.io |
| Native token | $APE |
| Contract | Address (proxy) |
|---|---|
NativeVRNG (entry point) | 0xA899846c23c6af77Da3DA16C8c6137746a460449 |
RandomBeaconHistory | 0x63B964F4Bd9589A05FB8C86B9e23A859ec064846 |
FeeManager | 0x381Ad148F370188CA76B245897BCb8aEc319B432 |
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:
- Your contract requests randomness and pays a fee.
- The protocol fulfils the request with the first beacon written after it.
- 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
}userCommitmentis your own entropy (e.g.keccak256(player, salt)), mixed into the derivation — it does not need to be secret for the instant flow.requestIduniquely 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
- Next: Requesting randomness ↗ (opens in a new tab) — the request flows in detail.
- Receiving randomness ↗ (opens in a new tab) — make your callback safe.
- See also: Fees & rate limits ↗ (opens in a new tab) · Verifying randomness ↗ (opens in a new tab)
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
- Getting Started ↗ (opens in a new tab)
- Create your Subscription ↗ (opens in a new tab)
- Gas Configurations ↗ (opens in a new tab)
- Deposit & Withdraw Funds ↗ (opens in a new tab)
- Request Random Numbers ↗ (opens in a new tab)
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];
}
}