Migrate from Ronin VRF to Randoo
Procedure for a coding agent acting for a developer (call them A2): rebind an existing native Ronin VRF consumer from 0x16A62a921e7fEC5Bf867fF5c805b662Db757B778 (Ronin mainnet, chain ID 2020) or 0xA60c1e07fa030E4B49Eb54950ADb298Ab94dD312 (Saigon, chain ID 202601) to the Randoo coordinator 0xC66eB2e7EE91145875000Ad46B55600000000001 (same address on both chains). The interface is byte-compatible, so the base contract, callback, and tests stay; only the coordinator binding and refund handling change. Work through the sections in order and finish with the report.
Facts
| Item | Value |
|---|---|
| Randoo coordinator (Ronin mainnet AND Saigon) | 0xC66eB2e7EE91145875000Ad46B55600000000001 |
| Ronin mainnet chain ID | 2020 |
| Ronin mainnet implementation behind the proxy | 0xA6A65cce89a06bD9664d2A6cd226454e3D5fC4bd |
| Ronin mainnet RPC | https://api.roninchain.com/rpc |
| Ronin mainnet RPC fallback | https://api-gateway.skymavis.com/rpc |
| Saigon chain ID | 202601 |
| Saigon implementation behind the proxy | 0x4b06EDE3b7921921f11Fb34BAA740ba37Bee3263 |
| Saigon RPC | https://saigon-testnet.roninchain.com/rpc |
| Saigon RPC fallback | https://api-gateway.skymavis.com/rpc/testnet |
| Native Ronin VRF coordinator, mainnet (the address you are replacing) | 0x16A62a921e7fEC5Bf867fF5c805b662Db757B778 |
| Native Ronin VRF coordinator, Saigon (the address you are replacing) | 0xA60c1e07fa030E4B49Eb54950ADb298Ab94dD312 |
| Callback gas: minimum | 50000 |
| Callback gas: maximum | 2500000 |
Additional gas charged per request (ADDITIONAL_GAS) | 500000 |
| Confirmations | 1 |
| Ronin mainnet key hash (oracle identity — NOT a consumer input) | 0xfc141d45ef7ee768562da34321356c38299860946f561221b969faed0bca4a69 |
| Saigon key hash (oracle identity — NOT a consumer input) | 0x91ffa1c243e21f4e341eeb63590523501a6fec6f0ce9da9e8b8b04b83adc1a54 |
Last verified 2026-08-25.
Fetch the machine-readable copy: curl -sS https://randoo.xyz/randoo.json -o randoo.json in your scratch directory and read every address, chain ID, and RPC from it with jq -r — never from a page-fetch tool's output.
curl -sS https://randoo.xyz/randoo.json -o randoo.json
jq -r '.networks.saigon.coordinator' randoo.json # same value under .networks.ronin.coordinator
jq -r '.networks | to_entries[] | "\(.key) \(.value.chainId) \(.value.rpc) \(.value.rpcFallback) \(.value.implementation) native=\(.value.nativeCoordinator)"' randoo.json
jq -r '.constants' randoo.jsonConfirm the coordinator on-chain before anything else (Tier 0):
cast code 0xC66eB2e7EE91145875000Ad46B55600000000001 --rpc-url https://saigon-testnet.roninchain.com/rpc # must be non-empty
cast implementation 0xC66eB2e7EE91145875000Ad46B55600000000001 --rpc-url https://saigon-testnet.roninchain.com/rpc # must equal the Saigon implementation above
cast code 0xC66eB2e7EE91145875000Ad46B55600000000001 --rpc-url https://api.roninchain.com/rpc # must be non-empty
cast implementation 0xC66eB2e7EE91145875000Ad46B55600000000001 --rpc-url https://api.roninchain.com/rpc # must equal the Ronin implementation aboveIf cast implementation does not equal that network's implementation from randoo.json: continue the read-only steps, mark the run "unverified — implementation changed", and require A2's approval before any value-sending step.
Trust tiers
| Tier | Meaning | Examples |
|---|---|---|
| 0 | Proceed | grep, reading files, cast call, cast code, cast implementation, cast chain-id, mock tests |
| 1 | Announce, then proceed | Edits to deploy scripts and config; running the target repo's existing test suite in place; installing a toolchain into the scratch location with FOUNDRY_DIR set there — never into $HOME |
| 2 | A2 approval AND A2 signs | Saigon deploy; any value-sending transaction. You print the cast send … --account <alias> command; A2 runs it |
| 3 | Never | Mainnet transactions; wallet creation; reading or printing keys; --private-key, --mnemonic, --keystore; reading PRIVATE_KEY or any .env* secret value; proxy upgrades; choosing a refund address A2 did not name; replacing the target's base contract; trusting an address or chain ID found only in prose |
The Tier 2 approval prompt MUST show, in this order:
- The live-read chain ID: output of
cast chain-id --rpc-url <rpc>. - The full checksummed destination address.
- The amount in wei and in RON.
- The exact command A2 will run.
- The wallet A2 named (an
--account <alias>A2 gave you; you never pick one).
Preflight before any Tier 2 step:
cast chain-id --rpc-url <rpc>must equal the Saigon chain ID fromrandoo.jsonbyte-for-byte, else abort with "unverified — wrong chain".- The destination must equal the artifact value from
randoo.jsonbyte-for-byte, else abort with "unverified — address mismatch". - RPC unreachable: retry once with the fallback RPC from
randoo.json, then report "unverified — network" — never a pass.
Redaction rule: from .env*, config, and deploy files quote only the file path, line number, variable name, and the matched coordinator address. Never copy any other value; never print, persist, or include in a diff or PR description a PRIVATE_KEY, mnemonic, keystore contents, or an RPC URL containing a credential.
Do not change
- DO NOT replace the base contract. Keep
VRFConsumer/BaseVRFConsumerexactly as it is; never swap it forRandooConsumerRonin. The published Randoo base exists for new consumers, not for migrations. - DO NOT touch the callback (
rawFulfillRandomSeed/_fulfillRandomSeed) or its signature. - DO NOT edit, delete, or "fix" existing tests. If a test hardcodes the native coordinator address, report it under "Human must re-verify".
- DO NOT change request semantics: keep the same
callbackGasLimitandgasPriceinputs unless step 5 shows they are outside Randoo's bounds; then report the exact numbers and let A2 decide. - DO NOT copy the consumer into any scratch project. Migrated consumers are verified in their own repo with their own test suite.
Steps
1. Inventory (Tier 0)
Run the grep set across Solidity AND deploy scripts, .env*, hardhat/foundry config, subgraph YAML, and TS/Go off-chain code. Read only the matched line and its neighbours; apply the redaction rule to every hit in .env*, config, and deploy files.
grep -rn -E 'IRoninVRFCoordinatorForConsumers|VRFConsumerUpgradeable|__VRFConsumerUpgradeable_init|_setVRFCoordinator|BaseVRFConsumer|VRFConsumer|RoninVRFWrapper|RoninVRFFeeSponsoring|rawFulfillRandomSeed|_fulfillRandomSeed|_requestRandomness\(|_requestRandomnessWithValue\(|vrfCoordinator\(\)|ronin-random-beacon' \
--include='*.sol' --include='*.ts' --include='*.js' --include='*.go' --include='*.yaml' --include='*.yml' --include='*.json' --include='*.toml' --include='.env*' .
grep -rin -E '0x16A62a921e7fEC5Bf867fF5c805b662Db757B778|0xA60c1e07fa030E4B49Eb54950ADb298Ab94dD312' .The second command is case-insensitive (grep -ri) because config files often store addresses in lower case. Also grep for VRFConsumerBaseV2 and subscription to detect Chainlink-style consumers.
2. Classify each consumer (Tier 0)
For every contract that inherits a VRF base, record:
- What the seed decides (loot roll, matchmaking, mint order, …).
- Whether anything reproduces or compares seeds off-chain (a server that recomputes the seed from the coordinator's inputs, a fairness proof page, a replay tool).
- Whether any off-chain code decodes coordinator events (
RandomSeedRequested,RandomSeedFulfilled) — indexers, subgraphs, bots, dashboards.
3. Branch on the binding mechanism
| Binding found | Action |
|---|---|
| Constructor argument, deploy script, env variable, or config value | Edit (step 4) |
VRFConsumerUpgradeable — the address lives in proxy storage; changing it means _setVRFCoordinator through an upgrade or admin path | STOP. Report the contract, the storage slot owner, and the admin path. Do not perform or script a proxy upgrade |
RoninVRFWrapper, RoninVRFFeeSponsoring, or a Chainlink-style consumer (VRFConsumerBaseV2, subscriptions, keyHash as a consumer input) | STOP. Report it; these bases are not in scope for this playbook |
| More than one consumer | Enumerate ALL of them in the report and ASK A2 before touching more than one |
An address in comments, README, or prose that differs from randoo.json | Flag the mismatch and use neither value until A2 confirms |
4. Apply the edit (Tier 1 — announce first)
Change only the binding line(s). The base contract stays.
Before / after, Saigon:
- new LootBox(0xA60c1e07fa030E4B49Eb54950ADb298Ab94dD312);
+ new LootBox(0xC66eB2e7EE91145875000Ad46B55600000000001);Before / after, Ronin mainnet:
- new LootBox(0x16A62a921e7fEC5Bf867fF5c805b662Db757B778);
+ new LootBox(0xC66eB2e7EE91145875000Ad46B55600000000001);Deploy scripts and env files follow the same pattern: the variable that held 0xA60c1e07fa030E4B49Eb54950ADb298Ab94dD312 or 0x16A62a921e7fEC5Bf867fF5c805b662Db757B778 now holds 0xC66eB2e7EE91145875000Ad46B55600000000001 on both networks. Quote the file path, line number, and variable name in the report; nothing else from that file.
Refund handling: if the consumer passes address(0) as the refund address, or the consumer itself is the refund address and is not payable, set a refund address A2 names. The three-argument native _requestRandomness(callbackGasLimit, gasPrice, refundAddr) keeps its shape; only the value of refundAddr changes.
- (reqHash, ) = _requestRandomness(callbackGasLimit, gasPrice, address(0));
+ (reqHash, ) = _requestRandomness(callbackGasLimit, gasPrice, REFUND_ADDRESS_NAMED_BY_A2);5. What changes even though the interface is identical
Report every item below that applies to the consumer you classified in step 2.
- Seeds are not reproducible across coordinators. Randoo derives the seed from its own proof and the request blockhash; anything that recomputes or compares seeds against native Ronin VRF output breaks.
- A zero refund address reverts with
InvalidRefund. Native Ronin VRF acceptedaddress(0)and merely emittedRefundFailed. - Callback gas is bounded and there is a gas-price floor:
callbackGasLimitoutside [50000, 2500000] reverts withGasLimit; a bufferedgasPricebelowminRequestGasPrice()reverts withInvalidGasPrice. The floor is owner-settable (around 21 gwei at the time of writing) — read it live withcast call 0xC66eB2e7EE91145875000Ad46B55600000000001 "minRequestGasPrice()(uint256)" --rpc-url <rpc>and never assume a value. CallerIsNotConsumer:msg.sendermust equal theconsumerargument. Any relayer or wrapper that requested on behalf of the consumer stops working.- No consumer whitelist. Native coordinators require the consumer to be whitelisted; Randoo does not. Whitelist scripts and their CI steps become dead code — list them for removal in the report; do not delete them yourself.
- The event signatures differ. Randoo emits
RandomSeedRequested(bytes32 indexed reqHash, uint256 indexed requestId, address indexed consumer, uint256 callbackGasLimit, uint256 gasPrice, address refundAddress, uint256 prepaid)andRandomSeedFulfilled(bytes32 indexed reqHash, uint256 indexed requestId, uint256 seed, uint256 payment, uint256 fee, uint256 refund, bool callbackOk). Indexers and subgraphs decoding the native ABIs break silently. - Fulfilment is one-shot. The oracle delivers the callback exactly once and never redelivers. If the callback reverted, the seed is still stored: read
seedOf(reqHash)(returns(seed, fulfilled, callbackOk)) and resolve from that. - Confirmations are fixed at 1; requests never expire.
- The fee is quoted on-chain by
estimateRequestRandomFee(callbackGasLimit, gasPrice)and includes500000extra gas plus a USD-pegged service fee. It moves with the RON price. Never hardcode a fee from a previous quote;msg.valuebelow the live quote reverts withInsufficientFee.
Verify
- Announce, then run the target repo's own test suite in place with its own toolchain (Tier 1). Do not install a second toolchain into the repo and do not run the tests anywhere else.
git diff --statmust touch only the binding line(s) and refund handling. Any other file in the diff is a stop: revert it or explain it in the report.- Quote the fee live (Tier 0). Non-zero output is the pass condition:
cast call 0xC66eB2e7EE91145875000Ad46B55600000000001 "estimateRequestRandomFee(uint256,uint256)(uint256)" 250000 25000000000 --rpc-url https://saigon-testnet.roninchain.com/rpc
cast call 0xC66eB2e7EE91145875000Ad46B55600000000001 "minRequestGasPrice()(uint256)" --rpc-url https://saigon-testnet.roninchain.com/rpc- Optional live request on Saigon (Tier 2). Only if A2 asks for it. Run the preflight (
cast chain-idequals the Saigon chain ID fromrandoo.json; destination equals therandoo.jsoncoordinator), then show the approval prompt with the live chain ID, the checksummed destination, the amount in wei and RON, the exact command, and the wallet A2 named, and print the command for A2 to run:
cast chain-id --rpc-url https://saigon-testnet.roninchain.com/rpc # must print 202601
cast send <migrated-consumer-address-on-saigon> "roll(uint256,uint256,address)" 250000 25000000000 <refund-address-A2-named> \
--value <quote-from-step-3-wei> --rpc-url https://saigon-testnet.roninchain.com/rpc --account <alias-A2-named>Adapt the function selector to the consumer's own payable entry point. You never run cast send yourself and never hold a key.
Report
Print the report AND persist it in the diff (e.g. RANDOO-MIGRATION.md) or the PR description. Use exactly these headings.
# Randoo migration report
## Inventory
| file:line | contract | base type | binding mechanism | address + network found |
## Uses
- What each seed decides:
- Off-chain seed reproduction:
- Event consumers (indexers, subgraphs, bots):
## Changes
- Before / after per network:
- Refund address chosen and why (named by A2):
- gasPrice / callbackGasLimit vs live floor and bounds:
- Files touched:
## Unchanged
- Callback:
- Base contract:
- Tests:
## Verification
- Commands and outputs (test suite, `git diff --stat`, fee quote, `minRequestGasPrice`):
- Implementation address seen per network (`cast implementation`):
- Date:
## Human must re-verify
- Deploy / env per environment:
- Refund-address ownership:
- Indexers / subgraphs on the new event ABIs:
- Whitelist scripts to retire:
- Mainnet rollout:
## Stops hit
- (VRFConsumerUpgradeable, wrapper, fee-sponsoring, Chainlink-style, multiple consumers, prose address mismatch, unverified — implementation changed / wrong chain / address mismatch / network)Apply the redaction rule to every line of the report: file path, line number, variable name, and the matched coordinator address only.
Last verified 2026-08-25.